diff options
Diffstat (limited to 'Lib/test/test_cmd_line.py')
| -rw-r--r-- | Lib/test/test_cmd_line.py | 364 |
1 files changed, 310 insertions, 54 deletions
diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 12f26d9..c10bd76 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -1,32 +1,32 @@ # Tests invocation of the interpreter with various command line arguments -# All tests are executed with environment variables ignored +# Most tests are executed with environment variables ignored # See test_cmd_line_script.py for testing of script execution -import test.test_support +import test.support, unittest +import os import sys -import unittest -from test.script_helper import ( - assert_python_ok, assert_python_failure, spawn_python, kill_python, - python_exit_code -) +import subprocess +import tempfile +from test.script_helper import (spawn_python, kill_python, assert_python_ok, + assert_python_failure) -class CmdLineTest(unittest.TestCase): - def start_python(self, *args): - p = spawn_python(*args) - return kill_python(p) - - def exit_code(self, *args): - return python_exit_code(*args) +# XXX (ncoghlan): Move to script_helper and make consistent with run_python +def _kill_python_and_exit_code(p): + data = kill_python(p) + returncode = p.wait() + return data, returncode +class CmdLineTest(unittest.TestCase): def test_directories(self): - self.assertNotEqual(self.exit_code('.'), 0) - self.assertNotEqual(self.exit_code('< .'), 0) + assert_python_failure('.') + assert_python_failure('< .') def verify_valid_flag(self, cmd_line): - data = self.start_python(cmd_line) - self.assertTrue(data == '' or data.endswith('\n')) - self.assertNotIn('Traceback', data) + rc, out, err = assert_python_ok(*cmd_line) + self.assertTrue(out == b'' or out.endswith(b'\n')) + self.assertNotIn(b'Traceback', out) + self.assertNotIn(b'Traceback', err) def test_optimize(self): self.verify_valid_flag('-O') @@ -42,53 +42,307 @@ class CmdLineTest(unittest.TestCase): self.verify_valid_flag('-S') def test_usage(self): - self.assertIn('usage', self.start_python('-h')) + rc, out, err = assert_python_ok('-h') + self.assertIn(b'usage', out) def test_version(self): - version = 'Python %d.%d' % sys.version_info[:2] - self.assertTrue(self.start_python('-V').startswith(version)) + version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii") + rc, out, err = assert_python_ok('-V') + self.assertTrue(err.startswith(version)) + + def test_verbose(self): + # -v causes imports to write to stderr. If the write to + # stderr itself causes an import to happen (for the output + # codec), a recursion loop can occur. + rc, out, err = assert_python_ok('-v') + self.assertNotIn(b'stack overflow', err) + rc, out, err = assert_python_ok('-vv') + self.assertNotIn(b'stack overflow', err) + + def test_xoptions(self): + rc, out, err = assert_python_ok('-c', 'import sys; print(sys._xoptions)') + opts = eval(out.splitlines()[0]) + self.assertEqual(opts, {}) + rc, out, err = assert_python_ok( + '-Xa', '-Xb=c,d=e', '-c', 'import sys; print(sys._xoptions)') + opts = eval(out.splitlines()[0]) + self.assertEqual(opts, {'a': True, 'b': 'c,d=e'}) def test_run_module(self): # Test expected operation of the '-m' switch # Switch needs an argument - self.assertNotEqual(self.exit_code('-m'), 0) + assert_python_failure('-m') # Check we get an error for a nonexistent module - self.assertNotEqual( - self.exit_code('-m', 'fnord43520xyz'), - 0) + assert_python_failure('-m', 'fnord43520xyz') # Check the runpy module also gives an error for # a nonexistent module - self.assertNotEqual( - self.exit_code('-m', 'runpy', 'fnord43520xyz'), - 0) + assert_python_failure('-m', 'runpy', 'fnord43520xyz'), # All good if module is located and run successfully - self.assertEqual( - self.exit_code('-m', 'timeit', '-n', '1'), - 0) + assert_python_ok('-m', 'timeit', '-n', '1'), def test_run_module_bug1764407(self): # -m and -i need to play well together # Runs the timeit module and checks the __main__ # namespace has been populated appropriately p = spawn_python('-i', '-m', 'timeit', '-n', '1') - p.stdin.write('Timer\n') - p.stdin.write('exit()\n') + p.stdin.write(b'Timer\n') + p.stdin.write(b'exit()\n') data = kill_python(p) - self.assertTrue(data.startswith('1 loop')) - self.assertIn('__main__.Timer', data) + self.assertTrue(data.find(b'1 loop') != -1) + self.assertTrue(data.find(b'__main__.Timer') != -1) def test_run_code(self): # Test expected operation of the '-c' switch # Switch needs an argument - self.assertNotEqual(self.exit_code('-c'), 0) + assert_python_failure('-c') # Check we get an error for an uncaught exception - self.assertNotEqual( - self.exit_code('-c', 'raise Exception'), - 0) + assert_python_failure('-c', 'raise Exception') # All good if execution is successful - self.assertEqual( - self.exit_code('-c', 'pass'), - 0) + assert_python_ok('-c', 'pass') + + @unittest.skipIf(sys.getfilesystemencoding() == 'ascii', + 'need a filesystem encoding different than ASCII') + def test_non_ascii(self): + # Test handling of non-ascii data + command = "assert(ord('\xe9') == 0xe9)" + assert_python_ok('-c', command) + + # On Windows, pass bytes to subprocess doesn't test how Python decodes the + # command line, but how subprocess does decode bytes to unicode. Python + # doesn't decode the command line because Windows provides directly the + # arguments as unicode (using wmain() instead of main()). + @unittest.skipIf(sys.platform == 'win32', + 'Windows has a native unicode API') + def test_undecodable_code(self): + undecodable = b"\xff" + env = os.environ.copy() + # Use C locale to get ascii for the locale encoding + env['LC_ALL'] = 'C' + code = ( + b'import locale; ' + b'print(ascii("' + undecodable + b'"), ' + b'locale.getpreferredencoding())') + p = subprocess.Popen( + [sys.executable, "-c", code], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env=env) + stdout, stderr = p.communicate() + if p.returncode == 1: + # _Py_char2wchar() decoded b'\xff' as '\udcff' (b'\xff' is not + # decodable from ASCII) and run_command() failed on + # PyUnicode_AsUTF8String(). This is the expected behaviour on + # Linux. + pattern = b"Unable to decode the command from the command line:" + elif p.returncode == 0: + # _Py_char2wchar() decoded b'\xff' as '\xff' even if the locale is + # C and the locale encoding is ASCII. It occurs on FreeBSD, Solaris + # and Mac OS X. + pattern = b"'\\xff' " + # The output is followed by the encoding name, an alias to ASCII. + # Examples: "US-ASCII" or "646" (ISO 646, on Solaris). + else: + raise AssertionError("Unknown exit code: %s, output=%a" % (p.returncode, stdout)) + if not stdout.startswith(pattern): + raise AssertionError("%a doesn't start with %a" % (stdout, pattern)) + + @unittest.skipUnless(sys.platform == 'darwin', 'test specific to Mac OS X') + def test_osx_utf8(self): + def check_output(text): + decoded = text.decode('utf8', 'surrogateescape') + expected = ascii(decoded).encode('ascii') + b'\n' + + env = os.environ.copy() + # C locale gives ASCII locale encoding, but Python uses UTF-8 + # to parse the command line arguments on Mac OS X + env['LC_ALL'] = 'C' + + p = subprocess.Popen( + (sys.executable, "-c", "import sys; print(ascii(sys.argv[1]))", text), + stdout=subprocess.PIPE, + env=env) + stdout, stderr = p.communicate() + self.assertEqual(stdout, expected) + self.assertEqual(p.returncode, 0) + + # test valid utf-8 + text = 'e:\xe9, euro:\u20ac, non-bmp:\U0010ffff'.encode('utf-8') + check_output(text) + + # test invalid utf-8 + text = ( + b'\xff' # invalid byte + b'\xc3\xa9' # valid utf-8 character + b'\xc3\xff' # invalid byte sequence + b'\xed\xa0\x80' # lone surrogate character (invalid) + ) + check_output(text) + + def test_unbuffered_output(self): + # Test expected operation of the '-u' switch + for stream in ('stdout', 'stderr'): + # Binary is unbuffered + code = ("import os, sys; sys.%s.buffer.write(b'x'); os._exit(0)" + % stream) + rc, out, err = assert_python_ok('-u', '-c', code) + data = err if stream == 'stderr' else out + self.assertEqual(data, b'x', "binary %s not unbuffered" % stream) + # Text is line-buffered + code = ("import os, sys; sys.%s.write('x\\n'); os._exit(0)" + % stream) + rc, out, err = assert_python_ok('-u', '-c', code) + data = err if stream == 'stderr' else out + self.assertEqual(data.strip(), b'x', + "text %s not line-buffered" % stream) + + def test_unbuffered_input(self): + # sys.stdin still works with '-u' + code = ("import sys; sys.stdout.write(sys.stdin.read(1))") + p = spawn_python('-u', '-c', code) + p.stdin.write(b'x') + p.stdin.flush() + data, rc = _kill_python_and_exit_code(p) + self.assertEqual(rc, 0) + self.assertTrue(data.startswith(b'x'), data) + + def test_large_PYTHONPATH(self): + path1 = "ABCDE" * 100 + path2 = "FGHIJ" * 100 + path = path1 + os.pathsep + path2 + + code = """if 1: + import sys + path = ":".join(sys.path) + path = path.encode("ascii", "backslashreplace") + sys.stdout.buffer.write(path)""" + rc, out, err = assert_python_ok('-S', '-c', code, + PYTHONPATH=path) + self.assertIn(path1.encode('ascii'), out) + self.assertIn(path2.encode('ascii'), out) + + def test_displayhook_unencodable(self): + for encoding in ('ascii', 'latin1', 'utf8'): + env = os.environ.copy() + env['PYTHONIOENCODING'] = encoding + p = subprocess.Popen( + [sys.executable, '-i'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env) + # non-ascii, surrogate, non-BMP printable, non-BMP unprintable + text = "a=\xe9 b=\uDC80 c=\U00010000 d=\U0010FFFF" + p.stdin.write(ascii(text).encode('ascii') + b"\n") + p.stdin.write(b'exit()\n') + data = kill_python(p) + escaped = repr(text).encode(encoding, 'backslashreplace') + self.assertIn(escaped, data) + + def check_input(self, code, expected): + with tempfile.NamedTemporaryFile("wb+") as stdin: + sep = os.linesep.encode('ASCII') + stdin.write(sep.join((b'abc', b'def'))) + stdin.flush() + stdin.seek(0) + with subprocess.Popen( + (sys.executable, "-c", code), + stdin=stdin, stdout=subprocess.PIPE) as proc: + stdout, stderr = proc.communicate() + self.assertEqual(stdout.rstrip(), expected) + + def test_stdin_readline(self): + # Issue #11272: check that sys.stdin.readline() replaces '\r\n' by '\n' + # on Windows (sys.stdin is opened in binary mode) + self.check_input( + "import sys; print(repr(sys.stdin.readline()))", + b"'abc\\n'") + + def test_builtin_input(self): + # Issue #11272: check that input() strips newlines ('\n' or '\r\n') + self.check_input( + "print(repr(input()))", + b"'abc'") + + def test_output_newline(self): + # Issue 13119 Newline for print() should be \r\n on Windows. + code = """if 1: + import sys + print(1) + print(2) + print(3, file=sys.stderr) + print(4, file=sys.stderr)""" + rc, out, err = assert_python_ok('-c', code) + + if sys.platform == 'win32': + self.assertEqual(b'1\r\n2\r\n', out) + self.assertEqual(b'3\r\n4', err) + else: + self.assertEqual(b'1\n2\n', out) + self.assertEqual(b'3\n4', err) + + def test_unmached_quote(self): + # Issue #10206: python program starting with unmatched quote + # spewed spaces to stdout + rc, out, err = assert_python_failure('-c', "'") + self.assertRegex(err.decode('ascii', 'ignore'), 'SyntaxError') + self.assertEqual(b'', out) + + def test_stdout_flush_at_shutdown(self): + # Issue #5319: if stdout.flush() fails at shutdown, an error should + # be printed out. + code = """if 1: + import os, sys + sys.stdout.write('x') + os.close(sys.stdout.fileno())""" + rc, out, err = assert_python_ok('-c', code) + self.assertEqual(b'', out) + self.assertRegex(err.decode('ascii', 'ignore'), + 'Exception IOError: .* ignored') + + def test_closed_stdout(self): + # Issue #13444: if stdout has been explicitly closed, we should + # not attempt to flush it at shutdown. + code = "import sys; sys.stdout.close()" + rc, out, err = assert_python_ok('-c', code) + self.assertEqual(b'', err) + + # Issue #7111: Python should work without standard streams + + @unittest.skipIf(os.name != 'posix', "test needs POSIX semantics") + def _test_no_stdio(self, streams): + code = """if 1: + import os, sys + for i, s in enumerate({streams}): + if getattr(sys, s) is not None: + os._exit(i + 1) + os._exit(42)""".format(streams=streams) + def preexec(): + if 'stdin' in streams: + os.close(0) + if 'stdout' in streams: + os.close(1) + if 'stderr' in streams: + os.close(2) + p = subprocess.Popen( + [sys.executable, "-E", "-c", code], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn=preexec) + out, err = p.communicate() + self.assertEqual(test.support.strip_python_stderr(err), b'') + self.assertEqual(p.returncode, 42) + + def test_no_stdin(self): + self._test_no_stdio(['stdin']) + + def test_no_stdout(self): + self._test_no_stdio(['stdout']) + + def test_no_stderr(self): + self._test_no_stdio(['stderr']) + + def test_no_std_streams(self): + self._test_no_stdio(['stdin', 'stdout', 'stderr']) def test_hash_randomization(self): # Verify that -R enables hash randomization: @@ -96,24 +350,26 @@ class CmdLineTest(unittest.TestCase): hashes = [] for i in range(2): code = 'print(hash("spam"))' - data = self.start_python('-R', '-c', code) - hashes.append(data) + rc, out, err = assert_python_ok('-R', '-c', code) + self.assertEqual(rc, 0) + hashes.append(out) self.assertNotEqual(hashes[0], hashes[1]) # Verify that sys.flags contains hash_randomization - code = 'import sys; print sys.flags' - data = self.start_python('-R', '-c', code) - self.assertTrue('hash_randomization=1' in data) + code = 'import sys; print("random is", sys.flags.hash_randomization)' + rc, out, err = assert_python_ok('-R', '-c', code) + self.assertEqual(rc, 0) + self.assertIn(b'random is 1', out) def test_del___main__(self): # Issue #15001: PyRun_SimpleFileExFlags() did crash because it kept a # borrowed reference to the dict of __main__ module and later modify # the dict whereas the module was destroyed - filename = test.test_support.TESTFN - self.addCleanup(test.test_support.unlink, filename) + filename = test.support.TESTFN + self.addCleanup(test.support.unlink, filename) with open(filename, "w") as script: - print >>script, "import sys" - print >>script, "del sys.modules['__main__']" + print("import sys", file=script) + print("del sys.modules['__main__']", file=script) assert_python_ok(filename) def test_unknown_options(self): @@ -136,8 +392,8 @@ class CmdLineTest(unittest.TestCase): def test_main(): - test.test_support.run_unittest(CmdLineTest) - test.test_support.reap_children() + test.support.run_unittest(CmdLineTest) + test.support.reap_children() if __name__ == "__main__": test_main() |
