From: david-sarah <david-sarah@jacaranda.org>
Date: Sun, 25 Jul 2010 08:32:16 +0000 (-0700)
Subject: Changes to Tahoe needed to work with new zetuptoolz (that does not use .exe wrappers... 
X-Git-Tag: allmydata-tahoe-1.8.0b2~59
X-Git-Url: https://git.rkrishnan.org/pf/content/en/footer/class-simplejson.JSONDecoder.html?a=commitdiff_plain;h=37b07a545f17f8bb76ad024de7cf81f7e81f36de;p=tahoe-lafs%2Ftahoe-lafs.git

Changes to Tahoe needed to work with new zetuptoolz (that does not use .exe wrappers on Windows), and to support Unicode arguments and stdout/stderr -- v5
---

diff --git a/bin/tahoe-script.template b/bin/tahoe-script.template
index 69d54c92..48bdbf18 100644
--- a/bin/tahoe-script.template
+++ b/bin/tahoe-script.template
@@ -5,12 +5,17 @@ import errno, sys, os, subprocess
 where = os.path.realpath(sys.argv[0])
 base = os.path.dirname(os.path.dirname(where))
 
+if sys.platform == "win32":
+    installed_tahoe = os.path.join(os.path.dirname(sys.executable), 'Scripts', 'tahoe.pyscript')
+else:
+    installed_tahoe = "/usr/bin/tahoe"
+
 whoami = '''\
-I am a "bin/tahoe" executable who is only for the convenience of running
+I am a "bin%stahoe" executable who is only for the convenience of running
 Tahoe from its source distribution -- I work only when invoked as the "tahoe"
 script that lives in the "bin/" subdirectory of a Tahoe source code
 distribution, and only if you have already run "make".
-'''
+''' % (os.path.sep,)
 
 # look for Tahoe.home .
 homemarker = os.path.join(base, "Tahoe.home")
@@ -19,9 +24,9 @@ if not os.path.exists(homemarker):
     print '''\
 I just tried to run and found that I am not living in such a directory, so I
 am stopping now. To run Tahoe after it has been is installed, please execute
-my brother, also named "tahoe", who gets installed into the appropriate place
-for executables when you run "make install" (perhaps as /usr/bin/tahoe).
-'''
+my brother, who gets installed into the appropriate place for executables
+when you run "make install" (perhaps as "%s").
+''' % (installed_tahoe,)
     sys.exit(1)
 
 # we've found our home. Put the tahoe support/lib etc. in our PYTHONPATH.
@@ -41,30 +46,50 @@ else:
     pp = supportdir
 os.environ["PYTHONPATH"] = pp
 
-# find the location of the tahoe executable.
-bin_dir = "bin"
+# find commandline args and the location of the tahoe executable.
 if sys.platform == "win32":
-    bin_dir = "Scripts"
-executable = os.path.join(base, "support", bin_dir, "tahoe")
+    import re
+    from ctypes import WINFUNCTYPE, POINTER, byref, c_wchar_p, c_int, windll
+
+    GetCommandLineW = WINFUNCTYPE(c_wchar_p)(("GetCommandLineW", windll.kernel32))
+    CommandLineToArgvW = WINFUNCTYPE(POINTER(c_wchar_p), c_wchar_p, POINTER(c_int)) \
+                            (("CommandLineToArgvW", windll.shell32))
+
+    argc = c_int(0)
+    argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
+
+    # See src/allmydata/scripts/runner.py for the corresponding unmangler.
+    # Note that this doesn't escape \x7F. If it did, test_unicode_arguments_and_output
+    # in test_runner.py wouldn't work.
+    def mangle(s):
+        return str(re.sub(ur'[^\x20-\x7F]', lambda m: u'\x7F%x;' % (ord(m.group(0)),), s))
+
+    argv = [mangle(argv_unicode[i]) for i in xrange(1, argc.value)]
+    local_tahoe = "Scripts\\tahoe.pyscript"
+else:
+    argv = sys.argv
+    local_tahoe = "bin/tahoe"
+
+script = os.path.join(base, "support", local_tahoe)
 
 try:
-    res = subprocess.call([executable] + sys.argv[1:], env=os.environ)
+    res = subprocess.call([sys.executable, script] + argv[1:], env=os.environ)
 except (OSError, IOError), le:
     if le.args[0] == errno.ENOENT:
         print whoami
         print '''\
-I just tried to run and could not find my brother, named
-"../support/bin/tahoe". To run Tahoe when it is installed, please execute my
-brother, also named "tahoe", who gets installed into the appropriate place
-for executables when you run "make install" (perhaps as /usr/bin/tahoe).
-'''
+I just tried to run and could not find my brother at
+"%s". To run Tahoe when it is installed, please execute my
+brother, who gets installed into the appropriate place for executables
+when you run "make install" (perhaps as "%s").
+''' % (script, installed_tahoe)
         raise
 except Exception, le:
     print whoami
     print '''\
-I just tried to invoke my brother, named "../support/bin/tahoe" and got an
-exception.
-'''
+I just tried to invoke my brother at "%s"
+and got an exception.
+''' % (script,)
     raise
 else:
     sys.exit(res)
diff --git a/setup.py b/setup.py
index 26d09b0c..0e5eb7c9 100644
--- a/setup.py
+++ b/setup.py
@@ -235,54 +235,46 @@ class MakeExecutable(Command):
     def run(self):
         bin_tahoe_template = os.path.join("bin", "tahoe-script.template")
 
-        # Create the 'tahoe-script.py' file under the 'bin' directory. The
-        # 'tahoe-script.py' file is exactly the same as the
-        # 'tahoe-script.template' script except that the shebang line is
-        # rewritten to use our sys.executable for the interpreter. On
-        # Windows, create a tahoe.exe will execute it. On non-Windows, make a
-        # symlink to it from 'tahoe'. The tahoe.exe will be copied from the
-        # setuptools egg's cli.exe and this will work from a zip-safe and
-        # non-zip-safe setuptools egg.
+        if sys.platform == 'win32':
+            # 'tahoe' script is needed for cygwin
+            script_names = ["tahoe.pyscript", "tahoe"]
+        else:
+            script_names = ["tahoe"]
+
+        # Create the tahoe script file under the 'bin' directory. This
+        # file is exactly the same as the 'tahoe-script.template' script
+        # except that the shebang line is rewritten to use our sys.executable
+        # for the interpreter.
         f = open(bin_tahoe_template, "rU")
         script_lines = f.readlines()
         f.close()
-        script_lines[0] = "#!%s\n" % sys.executable
-        tahoe_script = os.path.join("bin", "tahoe-script.py")
-        f = open(tahoe_script, "w")
-        for line in script_lines:
-            f.write(line)
-        f.close()
-        if sys.platform == "win32":
-            from pkg_resources import require
-            setuptools_egg = require("setuptools")[0].location
-            if os.path.isfile(setuptools_egg):
-                z = zipfile.ZipFile(setuptools_egg, 'r')
-                for filename in z.namelist():
-                    if 'cli.exe' in filename:
-                        cli_exe = z.read(filename)
-            else:
-                cli_exe = os.path.join(setuptools_egg, 'setuptools', 'cli.exe')
-            tahoe_exe = os.path.join("bin", "tahoe.exe")
-            if os.path.isfile(setuptools_egg):
-                f = open(tahoe_exe, 'wb')
-                f.write(cli_exe)
-                f.close()
-            else:
-                shutil.copy(cli_exe, tahoe_exe)
-        else:
+        script_lines[0] = '#!%s\n' % (sys.executable,)
+        for script_name in script_names:
+            tahoe_script = os.path.join("bin", script_name)
             try:
-                os.remove(os.path.join('bin', 'tahoe'))
-            except:
-                # okay, probably it was already gone
-                pass
-            os.symlink('tahoe-script.py', os.path.join('bin', 'tahoe'))
-
-        # chmod +x bin/tahoe-script.py
-        old_mode = stat.S_IMODE(os.stat(tahoe_script)[stat.ST_MODE])
-        new_mode = old_mode | (stat.S_IXUSR | stat.S_IRUSR |
-                               stat.S_IXGRP | stat.S_IRGRP |
-                               stat.S_IXOTH | stat.S_IROTH )
-        os.chmod(tahoe_script, new_mode)
+                os.remove(tahoe_script)
+            except Exception:
+                if os.path.exists(tahoe_script):
+                   raise
+            f = open(tahoe_script, "wb")
+            for line in script_lines:
+                f.write(line)
+            f.close()
+
+            # chmod +x
+            old_mode = stat.S_IMODE(os.stat(tahoe_script)[stat.ST_MODE])
+            new_mode = old_mode | (stat.S_IXUSR | stat.S_IRUSR |
+                                   stat.S_IXGRP | stat.S_IRGRP |
+                                   stat.S_IXOTH | stat.S_IROTH )
+            os.chmod(tahoe_script, new_mode)
+
+        old_tahoe_exe = os.path.join("bin", "tahoe.exe")
+        try:
+            os.remove(old_tahoe_exe)
+        except Exception:
+            if os.path.exists(old_tahoe_exe):
+                raise
+
 
 class MySdist(sdist.sdist):
     """ A hook in the sdist command so that we can determine whether this the
diff --git a/src/allmydata/scripts/runner.py b/src/allmydata/scripts/runner.py
index f8c0a096..1da7059a 100644
--- a/src/allmydata/scripts/runner.py
+++ b/src/allmydata/scripts/runner.py
@@ -10,6 +10,7 @@ import allmydata
 pkg_resources.require(allmydata.__appname__)
 from allmydata.scripts.common import BaseOptions
 from allmydata.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer
+from allmydata.util.encodingutil import quote_output, get_argv_encoding
 
 def GROUP(s):
     # Usage.parseOptions compares argv[1] against command[0], so it will
@@ -19,7 +20,7 @@ def GROUP(s):
 
 
 class Options(BaseOptions, usage.Options):
-    synopsis = "Usage:  tahoe <command> [command options]"
+    synopsis = "\nUsage:  tahoe <command> [command options]"
     subCommands = ( GROUP("Administration")
                     +   create_node.subCommands
                     +   keygen.subCommands
@@ -42,9 +43,13 @@ class Options(BaseOptions, usage.Options):
 
 def runner(argv,
            run_by_human=True,
-           stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
+           stdin=None, stdout=None, stderr=None,
            install_node_control=True, additional_commands=None):
 
+    stdin  = stdin  or sys.stdin
+    stdout = stdout or sys.stdout
+    stderr = stderr or sys.stderr
+
     config = Options()
     if install_node_control:
         config.subCommands.extend(startstop_node.subCommands)
@@ -63,8 +68,12 @@ def runner(argv,
         c = config
         while hasattr(c, 'subOptions'):
             c = c.subOptions
-        print str(c)
-        print "%s:  %s" % (sys.argv[0], e)
+        print >>stdout, str(c)
+        try:
+            msg = e.args[0].decode(get_argv_encoding())
+        except Exception:
+            msg = repr(e)
+        print >>stdout, "%s:  %s\n" % (sys.argv[0], quote_output(msg, quotemarks=False))
         return 1
 
     command = config.subCommand
@@ -99,6 +108,11 @@ def runner(argv,
 
     return rc
 
+
 def run(install_node_control=True):
-    rc = runner(sys.argv[1:])
+    if sys.platform == "win32":
+        from allmydata.windows.fixups import initialize
+        initialize()
+
+    rc = runner(sys.argv[1:], install_node_control=install_node_control)
     sys.exit(rc)
diff --git a/src/allmydata/test/__init__.py b/src/allmydata/test/__init__.py
index 3d106a69..625d0423 100644
--- a/src/allmydata/test/__init__.py
+++ b/src/allmydata/test/__init__.py
@@ -24,3 +24,8 @@ def disable_foolscap_incidents():
 
 # we disable incident reporting for all unit tests.
 disable_foolscap_incidents()
+
+import sys
+if sys.platform == "win32":
+    from allmydata.windows.fixups import initialize
+    initialize()
diff --git a/src/allmydata/test/test_encodingutil.py b/src/allmydata/test/test_encodingutil.py
index 38105701..0305b1e6 100644
--- a/src/allmydata/test/test_encodingutil.py
+++ b/src/allmydata/test/test_encodingutil.py
@@ -21,17 +21,23 @@ if __name__ == "__main__":
     if len(sys.argv) != 2:
         print "Usage: %s lumi<e-grave>re" % sys.argv[0]
         sys.exit(1)
-    
+
+    if sys.platform == "win32":
+        try:
+            from allmydata.windows.fixups import initialize
+        except ImportError:
+            print "set PYTHONPATH to the src directory"
+            sys.exit(1)
+        initialize()
+
     print
     print "class MyWeirdOS(EncodingUtil, unittest.TestCase):"
     print "    uname = '%s'" % ' '.join(platform.uname())
-    if sys.platform != "win32":
-        print "    argv = %s" % repr(sys.argv[1])
+    print "    argv = %s" % repr(sys.argv[1])
     print "    platform = '%s'" % sys.platform
     print "    filesystem_encoding = '%s'" % sys.getfilesystemencoding()
     print "    output_encoding = '%s'" % sys.stdout.encoding
-    print "    argv_encoding = '%s'" % (sys.platform == "win32" and 'ascii' or sys.stdout.encoding)
-
+    print "    argv_encoding = '%s'" % sys.stdout.encoding
     try:
         tmpdir = tempfile.mkdtemp()
         for fname in TEST_FILENAMES:
@@ -56,6 +62,7 @@ from mock import patch
 import os, sys, locale
 
 from allmydata.test.common_util import ReallyEqualMixin
+from allmydata.util import encodingutil
 from allmydata.util.encodingutil import argv_to_unicode, unicode_to_url, \
     unicode_to_output, quote_output, unicode_platform, listdir_unicode, \
     FilenameEncodingError, get_output_encoding, get_filesystem_encoding, _reload
@@ -64,8 +71,6 @@ from allmydata.dirnode import normalize
 from twisted.python import usage
 
 class EncodingUtilErrors(ReallyEqualMixin, unittest.TestCase):
-    def tearDown(self):
-        _reload()
 
     @patch('sys.stdout')
     def test_get_output_encoding(self, mock_stdout):
@@ -78,11 +83,16 @@ class EncodingUtilErrors(ReallyEqualMixin, unittest.TestCase):
         self.failUnlessReallyEqual(get_output_encoding(), 'utf-8')
 
         mock_stdout.encoding = 'koi8-r'
+        expected = sys.platform == "win32" and 'utf-8' or 'koi8-r'
         _reload()
-        self.failUnlessReallyEqual(get_output_encoding(), 'koi8-r')
+        self.failUnlessReallyEqual(get_output_encoding(), expected)
 
         mock_stdout.encoding = 'nonexistent_encoding'
-        self.failUnlessRaises(AssertionError, _reload)
+        if sys.platform == "win32":
+            _reload()
+            self.failUnlessReallyEqual(get_output_encoding(), 'utf-8')
+        else:
+            self.failUnlessRaises(AssertionError, _reload)
 
     @patch('locale.getpreferredencoding')
     def test_get_output_encoding_not_from_stdout(self, mock_locale_getpreferredencoding):
@@ -94,12 +104,13 @@ class EncodingUtilErrors(ReallyEqualMixin, unittest.TestCase):
         old_stdout = sys.stdout
         sys.stdout = DummyStdout()
         try:
+            expected = sys.platform == "win32" and 'utf-8' or 'koi8-r'
             _reload()
-            self.failUnlessReallyEqual(get_output_encoding(), 'koi8-r')
+            self.failUnlessReallyEqual(get_output_encoding(), expected)
 
             sys.stdout.encoding = None
             _reload()
-            self.failUnlessReallyEqual(get_output_encoding(), 'koi8-r')
+            self.failUnlessReallyEqual(get_output_encoding(), expected)
 
             mock_locale_getpreferredencoding.return_value = None
             _reload()
@@ -107,20 +118,14 @@ class EncodingUtilErrors(ReallyEqualMixin, unittest.TestCase):
         finally:
             sys.stdout = old_stdout
 
-    @patch('sys.stdout')
-    def test_argv_to_unicode(self, mock):
-        mock.encoding = 'utf-8'
-        _reload()
-
+    def test_argv_to_unicode(self):
+        encodingutil.output_encoding = 'utf-8'
         self.failUnlessRaises(usage.UsageError,
                               argv_to_unicode,
                               lumiere_nfc.encode('latin1'))
 
-    @patch('sys.stdout')
-    def test_unicode_to_output(self, mock):
-        # Encoding koi8-r cannot represent e-grave
-        mock.encoding = 'koi8-r'
-        _reload()
+    def test_unicode_to_output(self):
+        encodingutil.output_encoding = 'koi8-r'
         self.failUnlessRaises(UnicodeEncodeError, unicode_to_output, lumiere_nfc)
 
     @patch('os.listdir')
@@ -171,9 +176,9 @@ class EncodingUtilNonUnicodePlatform(unittest.TestCase):
                               listdir_unicode,
                               u'/' + lumiere_nfc)
 
+
 class EncodingUtil(ReallyEqualMixin):
     def setUp(self):
-        # Mock sys.platform because unicode_platform() uses it
         self.original_platform = sys.platform
         sys.platform = self.platform
 
@@ -197,12 +202,12 @@ class EncodingUtil(ReallyEqualMixin):
 
     @patch('sys.stdout')
     def test_unicode_to_output(self, mock):
-        if 'output' not in dir(self):
+        if 'argv' not in dir(self):
             return
 
         mock.encoding = self.output_encoding
         _reload()
-        self.failUnlessReallyEqual(unicode_to_output(lumiere_nfc), self.output)
+        self.failUnlessReallyEqual(unicode_to_output(lumiere_nfc), self.argv)
 
     def test_unicode_platform(self):
         matrix = {
@@ -287,19 +292,23 @@ class StdlibUnicode(unittest.TestCase):
 
 
 class QuoteOutput(ReallyEqualMixin, unittest.TestCase):
+    def tearDown(self):
+        _reload()
+
     def _check(self, inp, out, enc, optional_quotes):
         out2 = out
         if optional_quotes:
             out2 = out2[1:-1]
         self.failUnlessReallyEqual(quote_output(inp, encoding=enc), out)
         self.failUnlessReallyEqual(quote_output(inp, encoding=enc, quotemarks=False), out2)
-        if out[0:2] != 'b"':
-            if isinstance(inp, str):
-                self.failUnlessReallyEqual(quote_output(unicode(inp), encoding=enc), out)
-                self.failUnlessReallyEqual(quote_output(unicode(inp), encoding=enc, quotemarks=False), out2)
-            else:
-                self.failUnlessReallyEqual(quote_output(inp.encode('utf-8'), encoding=enc), out)
-                self.failUnlessReallyEqual(quote_output(inp.encode('utf-8'), encoding=enc, quotemarks=False), out2)
+        if out[0:2] == 'b"':
+            pass
+        elif isinstance(inp, str):
+            self.failUnlessReallyEqual(quote_output(unicode(inp), encoding=enc), out)
+            self.failUnlessReallyEqual(quote_output(unicode(inp), encoding=enc, quotemarks=False), out2)
+        else:
+            self.failUnlessReallyEqual(quote_output(inp.encode('utf-8'), encoding=enc), out)
+            self.failUnlessReallyEqual(quote_output(inp.encode('utf-8'), encoding=enc, quotemarks=False), out2)
 
     def _test_quote_output_all(self, enc):
         def check(inp, out, optional_quotes=False):
@@ -368,24 +377,19 @@ class QuoteOutput(ReallyEqualMixin, unittest.TestCase):
         check(u"\"\u2621", u"'\"\u2621'")
         check(u"\u2621\"", u"'\u2621\"'", True)
 
-    @patch('sys.stdout')
-    def test_quote_output_mock(self, mock_stdout):
-        mock_stdout.encoding = 'ascii'
-        _reload()
+    def test_quote_output_default(self):
+        encodingutil.output_encoding = 'ascii'
         self.test_quote_output_ascii(None)
 
-        mock_stdout.encoding = 'latin1'
-        _reload()
+        encodingutil.output_encoding = 'latin1'
         self.test_quote_output_latin1(None)
 
-        mock_stdout.encoding = 'utf-8'
-        _reload()
+        encodingutil.output_encoding = 'utf-8'
         self.test_quote_output_utf8(None)
 
 
 class UbuntuKarmicUTF8(EncodingUtil, unittest.TestCase):
     uname = 'Linux korn 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:05:01 UTC 2009 x86_64'
-    output = 'lumi\xc3\xa8re'
     argv = 'lumi\xc3\xa8re'
     platform = 'linux2'
     filesystem_encoding = 'UTF-8'
@@ -395,7 +399,6 @@ class UbuntuKarmicUTF8(EncodingUtil, unittest.TestCase):
 
 class UbuntuKarmicLatin1(EncodingUtil, unittest.TestCase):
     uname = 'Linux korn 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:05:01 UTC 2009 x86_64'
-    output = 'lumi\xe8re'
     argv = 'lumi\xe8re'
     platform = 'linux2'
     filesystem_encoding = 'ISO-8859-1'
@@ -403,37 +406,18 @@ class UbuntuKarmicLatin1(EncodingUtil, unittest.TestCase):
     argv_encoding = 'ISO-8859-1'
     dirlist = ['test_file', 'Blah blah.txt', '\xc4rtonwall.mp3']
 
-class WindowsXP(EncodingUtil, unittest.TestCase):
-    uname = 'Windows XP 5.1.2600 x86 x86 Family 15 Model 75 Step ping 2, AuthenticAMD'
-    output = 'lumi\x8are'
-    platform = 'win32'
-    filesystem_encoding = 'mbcs'
-    output_encoding = 'cp850'
-    argv_encoding = 'ascii'
-    dirlist = [u'Blah blah.txt', u'test_file', u'\xc4rtonwall.mp3']
-
-class WindowsXP_UTF8(EncodingUtil, unittest.TestCase):
+class Windows(EncodingUtil, unittest.TestCase):
     uname = 'Windows XP 5.1.2600 x86 x86 Family 15 Model 75 Step ping 2, AuthenticAMD'
-    output = 'lumi\xc3\xa8re'
-    platform = 'win32'
-    filesystem_encoding = 'mbcs'
-    output_encoding = 'cp65001'
-    argv_encoding = 'ascii'
-    dirlist = [u'Blah blah.txt', u'test_file', u'\xc4rtonwall.mp3']
-
-class WindowsVista(EncodingUtil, unittest.TestCase):
-    uname = 'Windows Vista 6.0.6000 x86 x86 Family 6 Model 15 Stepping 11, GenuineIntel'
-    output = 'lumi\x8are'
+    argv = 'lumi\xc3\xa8re'
     platform = 'win32'
     filesystem_encoding = 'mbcs'
-    output_encoding = 'cp850'
-    argv_encoding = 'ascii'
+    output_encoding = 'utf-8'
+    argv_encoding = 'utf-8'
     dirlist = [u'Blah blah.txt', u'test_file', u'\xc4rtonwall.mp3']
 
 class MacOSXLeopard(EncodingUtil, unittest.TestCase):
     uname = 'Darwin g5.local 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:57:01 PDT 2009; root:xnu-1228.15.4~1/RELEASE_PPC Power Macintosh powerpc'
     output = 'lumi\xc3\xa8re'
-    argv = 'lumi\xc3\xa8re'
     platform = 'darwin'
     filesystem_encoding = 'utf-8'
     output_encoding = 'UTF-8'
diff --git a/src/allmydata/test/test_runner.py b/src/allmydata/test/test_runner.py
index ab986c9f..b5459b48 100644
--- a/src/allmydata/test/test_runner.py
+++ b/src/allmydata/test/test_runner.py
@@ -7,6 +7,7 @@ from twisted.internet import utils
 import os.path, re, sys
 from cStringIO import StringIO
 from allmydata.util import fileutil, pollmixin
+from allmydata.util.encodingutil import unicode_to_argv, unicode_to_output
 from allmydata.scripts import runner
 
 from allmydata.test import common_util
@@ -47,6 +48,24 @@ class BinTahoe(common_util.SignalMixin, unittest.TestCase, SkipMixin):
         d.addCallback(_cb)
         return d
 
+    def test_unicode_arguments_and_output(self):
+        self.skip_if_cannot_run_bintahoe()
+
+        tricky = u"\u2621"
+        try:
+            tricky_arg = unicode_to_argv(tricky, mangle=True)
+            tricky_out = unicode_to_output(tricky)
+        except UnicodeEncodeError:
+            raise unittest.SkipTest("A non-ASCII argument/output could not be encoded on this platform.")
+
+        d = utils.getProcessOutputAndValue(bintahoe, args=[tricky_arg], env=os.environ)
+        def _cb(res):
+            out, err, rc_or_sig = res
+            self.failUnlessEqual(rc_or_sig, 1, str((out, err, rc_or_sig)))
+            self.failUnlessIn("Unknown command: "+tricky_out, out)
+        d.addCallback(_cb)
+        return d
+
     def test_version_no_noise(self):
         self.skip_if_cannot_run_bintahoe()
         import pkg_resources
diff --git a/src/allmydata/util/encodingutil.py b/src/allmydata/util/encodingutil.py
index 5079184a..45eaaf39 100644
--- a/src/allmydata/util/encodingutil.py
+++ b/src/allmydata/util/encodingutil.py
@@ -13,7 +13,7 @@ from allmydata.util import log
 from allmydata.util.fileutil import abspath_expanduser_unicode
 
 
-def _canonical_encoding(encoding):
+def canonical_encoding(encoding):
     if encoding is None:
         log.msg("Warning: falling back to UTF-8 encoding.", level=log.WEIRD)
         encoding = 'utf-8'
@@ -23,6 +23,9 @@ def _canonical_encoding(encoding):
     elif encoding == "us-ascii" or encoding == "646" or encoding == "ansi_x3.4-1968":
         encoding = 'ascii'
 
+    return encoding
+
+def check_encoding(encoding):
     # sometimes Python returns an encoding name that it doesn't support for conversion
     # fail early if this happens
     try:
@@ -30,8 +33,6 @@ def _canonical_encoding(encoding):
     except (LookupError, AttributeError):
         raise AssertionError("The character encoding '%s' is not supported for conversion." % (encoding,))
 
-    return encoding
-
 filesystem_encoding = None
 output_encoding = None
 argv_encoding = None
@@ -40,23 +41,27 @@ is_unicode_platform = False
 def _reload():
     global filesystem_encoding, output_encoding, argv_encoding, is_unicode_platform
 
-    filesystem_encoding = _canonical_encoding(sys.getfilesystemencoding())
-
-    outenc = None
-    if hasattr(sys.stdout, 'encoding'):
-        outenc = sys.stdout.encoding
-    if outenc is None:
-        try:
-            outenc = locale.getpreferredencoding()
-        except Exception:
-            pass  # work around <http://bugs.python.org/issue1443504>
-    output_encoding = _canonical_encoding(outenc)
+    filesystem_encoding = canonical_encoding(sys.getfilesystemencoding())
+    check_encoding(filesystem_encoding)
 
     if sys.platform == 'win32':
-        # Unicode arguments are not supported on Windows yet; see #565 and #1074.
-        argv_encoding = 'ascii'
+        # On Windows we install UTF-8 stream wrappers for sys.stdout and
+        # sys.stderr, and reencode the arguments as UTF-8 (see scripts/runner.py).
+        output_encoding = 'utf-8'
     else:
-        argv_encoding = output_encoding
+        outenc = None
+        if hasattr(sys.stdout, 'encoding'):
+            outenc = sys.stdout.encoding
+        if outenc is None:
+            try:
+                outenc = locale.getpreferredencoding()
+            except Exception:
+                pass  # work around <http://bugs.python.org/issue1443504>
+        output_encoding = canonical_encoding(outenc)
+
+    check_encoding(output_encoding)
+    argv_encoding = output_encoding
+
     is_unicode_platform = sys.platform in ["win32", "darwin"]
 
 _reload()