1# Tests invocation of the interpreter with various command line arguments 2# Most tests are executed with environment variables ignored 3# See test_cmd_line_script.py for testing of script execution 4 5import os 6import subprocess 7import sys 8import tempfile 9import textwrap 10import unittest 11from test import support 12from test.support import os_helper 13from test.support.script_helper import ( 14 spawn_python, kill_python, assert_python_ok, assert_python_failure, 15 interpreter_requires_environment 16) 17 18if not support.has_subprocess_support: 19 raise unittest.SkipTest("test module requires subprocess") 20 21# Debug build? 22Py_DEBUG = hasattr(sys, "gettotalrefcount") 23 24 25# XXX (ncoghlan): Move to script_helper and make consistent with run_python 26def _kill_python_and_exit_code(p): 27 data = kill_python(p) 28 returncode = p.wait() 29 return data, returncode 30 31 32class CmdLineTest(unittest.TestCase): 33 def test_directories(self): 34 assert_python_failure('.') 35 assert_python_failure('< .') 36 37 def verify_valid_flag(self, cmd_line): 38 rc, out, err = assert_python_ok(cmd_line) 39 self.assertTrue(out == b'' or out.endswith(b'\n')) 40 self.assertNotIn(b'Traceback', out) 41 self.assertNotIn(b'Traceback', err) 42 return out 43 44 def test_help(self): 45 self.verify_valid_flag('-h') 46 self.verify_valid_flag('-?') 47 out = self.verify_valid_flag('--help') 48 lines = out.splitlines() 49 self.assertIn(b'usage', lines[0]) 50 self.assertNotIn(b'PYTHONHOME', out) 51 self.assertNotIn(b'-X dev', out) 52 self.assertLess(len(lines), 50) 53 54 def test_help_env(self): 55 out = self.verify_valid_flag('--help-env') 56 self.assertIn(b'PYTHONHOME', out) 57 58 def test_help_xoptions(self): 59 out = self.verify_valid_flag('--help-xoptions') 60 self.assertIn(b'-X dev', out) 61 62 def test_help_all(self): 63 out = self.verify_valid_flag('--help-all') 64 lines = out.splitlines() 65 self.assertIn(b'usage', lines[0]) 66 self.assertIn(b'PYTHONHOME', out) 67 self.assertIn(b'-X dev', out) 68 69 # The first line contains the program name, 70 # but the rest should be ASCII-only 71 b''.join(lines[1:]).decode('ascii') 72 73 def test_optimize(self): 74 self.verify_valid_flag('-O') 75 self.verify_valid_flag('-OO') 76 77 def test_site_flag(self): 78 self.verify_valid_flag('-S') 79 80 def test_version(self): 81 version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii") 82 for switch in '-V', '--version', '-VV': 83 rc, out, err = assert_python_ok(switch) 84 self.assertFalse(err.startswith(version)) 85 self.assertTrue(out.startswith(version)) 86 87 def test_verbose(self): 88 # -v causes imports to write to stderr. If the write to 89 # stderr itself causes an import to happen (for the output 90 # codec), a recursion loop can occur. 91 rc, out, err = assert_python_ok('-v') 92 self.assertNotIn(b'stack overflow', err) 93 rc, out, err = assert_python_ok('-vv') 94 self.assertNotIn(b'stack overflow', err) 95 96 @unittest.skipIf(interpreter_requires_environment(), 97 'Cannot run -E tests when PYTHON env vars are required.') 98 def test_xoptions(self): 99 def get_xoptions(*args): 100 # use subprocess module directly because test.support.script_helper adds 101 # "-X faulthandler" to the command line 102 args = (sys.executable, '-E') + args 103 args += ('-c', 'import sys; print(sys._xoptions)') 104 out = subprocess.check_output(args) 105 opts = eval(out.splitlines()[0]) 106 return opts 107 108 opts = get_xoptions() 109 self.assertEqual(opts, {}) 110 111 opts = get_xoptions('-Xa', '-Xb=c,d=e') 112 self.assertEqual(opts, {'a': True, 'b': 'c,d=e'}) 113 114 def test_showrefcount(self): 115 def run_python(*args): 116 # this is similar to assert_python_ok but doesn't strip 117 # the refcount from stderr. It can be replaced once 118 # assert_python_ok stops doing that. 119 cmd = [sys.executable] 120 cmd.extend(args) 121 PIPE = subprocess.PIPE 122 p = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE) 123 out, err = p.communicate() 124 p.stdout.close() 125 p.stderr.close() 126 rc = p.returncode 127 self.assertEqual(rc, 0) 128 return rc, out, err 129 code = 'import sys; print(sys._xoptions)' 130 # normally the refcount is hidden 131 rc, out, err = run_python('-c', code) 132 self.assertEqual(out.rstrip(), b'{}') 133 self.assertEqual(err, b'') 134 # "-X showrefcount" shows the refcount, but only in debug builds 135 rc, out, err = run_python('-I', '-X', 'showrefcount', '-c', code) 136 self.assertEqual(out.rstrip(), b"{'showrefcount': True}") 137 if Py_DEBUG: 138 # bpo-46417: Tolerate negative reference count which can occur 139 # because of bugs in C extensions. This test is only about checking 140 # the showrefcount feature. 141 self.assertRegex(err, br'^\[-?\d+ refs, \d+ blocks\]') 142 else: 143 self.assertEqual(err, b'') 144 145 def test_xoption_frozen_modules(self): 146 tests = { 147 ('=on', 'FrozenImporter'), 148 ('=off', 'SourceFileLoader'), 149 ('=', 'FrozenImporter'), 150 ('', 'FrozenImporter'), 151 } 152 for raw, expected in tests: 153 cmd = ['-X', f'frozen_modules{raw}', 154 '-c', 'import os; print(os.__spec__.loader, end="")'] 155 with self.subTest(raw): 156 res = assert_python_ok(*cmd) 157 self.assertRegex(res.out.decode('utf-8'), expected) 158 159 def test_run_module(self): 160 # Test expected operation of the '-m' switch 161 # Switch needs an argument 162 assert_python_failure('-m') 163 # Check we get an error for a nonexistent module 164 assert_python_failure('-m', 'fnord43520xyz') 165 # Check the runpy module also gives an error for 166 # a nonexistent module 167 assert_python_failure('-m', 'runpy', 'fnord43520xyz') 168 # All good if module is located and run successfully 169 assert_python_ok('-m', 'timeit', '-n', '1') 170 171 def test_run_module_bug1764407(self): 172 # -m and -i need to play well together 173 # Runs the timeit module and checks the __main__ 174 # namespace has been populated appropriately 175 p = spawn_python('-i', '-m', 'timeit', '-n', '1') 176 p.stdin.write(b'Timer\n') 177 p.stdin.write(b'exit()\n') 178 data = kill_python(p) 179 self.assertTrue(data.find(b'1 loop') != -1) 180 self.assertTrue(data.find(b'__main__.Timer') != -1) 181 182 def test_relativedir_bug46421(self): 183 # Test `python -m unittest` with a relative directory beginning with ./ 184 # Note: We have to switch to the project's top module's directory, as per 185 # the python unittest wiki. We will switch back when we are done. 186 projectlibpath = os.path.dirname(__file__).removesuffix("test") 187 with os_helper.change_cwd(projectlibpath): 188 # Testing with and without ./ 189 assert_python_ok('-m', 'unittest', "test/test_longexp.py") 190 assert_python_ok('-m', 'unittest', "./test/test_longexp.py") 191 192 def test_run_code(self): 193 # Test expected operation of the '-c' switch 194 # Switch needs an argument 195 assert_python_failure('-c') 196 # Check we get an error for an uncaught exception 197 assert_python_failure('-c', 'raise Exception') 198 # All good if execution is successful 199 assert_python_ok('-c', 'pass') 200 201 @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') 202 def test_non_ascii(self): 203 # Test handling of non-ascii data 204 command = ("assert(ord(%r) == %s)" 205 % (os_helper.FS_NONASCII, ord(os_helper.FS_NONASCII))) 206 assert_python_ok('-c', command) 207 208 @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') 209 def test_coding(self): 210 # bpo-32381: the -c command ignores the coding cookie 211 ch = os_helper.FS_NONASCII 212 cmd = f"# coding: latin1\nprint(ascii('{ch}'))" 213 res = assert_python_ok('-c', cmd) 214 self.assertEqual(res.out.rstrip(), ascii(ch).encode('ascii')) 215 216 # On Windows, pass bytes to subprocess doesn't test how Python decodes the 217 # command line, but how subprocess does decode bytes to unicode. Python 218 # doesn't decode the command line because Windows provides directly the 219 # arguments as unicode (using wmain() instead of main()). 220 @unittest.skipIf(sys.platform == 'win32', 221 'Windows has a native unicode API') 222 def test_undecodable_code(self): 223 undecodable = b"\xff" 224 env = os.environ.copy() 225 # Use C locale to get ascii for the locale encoding 226 env['LC_ALL'] = 'C' 227 env['PYTHONCOERCECLOCALE'] = '0' 228 code = ( 229 b'import locale; ' 230 b'print(ascii("' + undecodable + b'"), ' 231 b'locale.getencoding())') 232 p = subprocess.Popen( 233 [sys.executable, "-c", code], 234 stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 235 env=env) 236 stdout, stderr = p.communicate() 237 if p.returncode == 1: 238 # _Py_char2wchar() decoded b'\xff' as '\udcff' (b'\xff' is not 239 # decodable from ASCII) and run_command() failed on 240 # PyUnicode_AsUTF8String(). This is the expected behaviour on 241 # Linux. 242 pattern = b"Unable to decode the command from the command line:" 243 elif p.returncode == 0: 244 # _Py_char2wchar() decoded b'\xff' as '\xff' even if the locale is 245 # C and the locale encoding is ASCII. It occurs on FreeBSD, Solaris 246 # and Mac OS X. 247 pattern = b"'\\xff' " 248 # The output is followed by the encoding name, an alias to ASCII. 249 # Examples: "US-ASCII" or "646" (ISO 646, on Solaris). 250 else: 251 raise AssertionError("Unknown exit code: %s, output=%a" % (p.returncode, stdout)) 252 if not stdout.startswith(pattern): 253 raise AssertionError("%a doesn't start with %a" % (stdout, pattern)) 254 255 @unittest.skipIf(sys.platform == 'win32', 256 'Windows has a native unicode API') 257 def test_invalid_utf8_arg(self): 258 # bpo-35883: Py_DecodeLocale() must escape b'\xfd\xbf\xbf\xbb\xba\xba' 259 # byte sequence with surrogateescape rather than decoding it as the 260 # U+7fffbeba character which is outside the [U+0000; U+10ffff] range of 261 # Python Unicode characters. 262 # 263 # Test with default config, in the C locale, in the Python UTF-8 Mode. 264 code = 'import sys, os; s=os.fsencode(sys.argv[1]); print(ascii(s))' 265 266 def run_default(arg): 267 cmd = [sys.executable, '-c', code, arg] 268 return subprocess.run(cmd, stdout=subprocess.PIPE, text=True) 269 270 def run_c_locale(arg): 271 cmd = [sys.executable, '-c', code, arg] 272 env = dict(os.environ) 273 env['LC_ALL'] = 'C' 274 return subprocess.run(cmd, stdout=subprocess.PIPE, 275 text=True, env=env) 276 277 def run_utf8_mode(arg): 278 cmd = [sys.executable, '-X', 'utf8', '-c', code, arg] 279 return subprocess.run(cmd, stdout=subprocess.PIPE, text=True) 280 281 valid_utf8 = 'e:\xe9, euro:\u20ac, non-bmp:\U0010ffff'.encode('utf-8') 282 # invalid UTF-8 byte sequences with a valid UTF-8 sequence 283 # in the middle. 284 invalid_utf8 = ( 285 b'\xff' # invalid byte 286 b'\xc3\xff' # invalid byte sequence 287 b'\xc3\xa9' # valid utf-8: U+00E9 character 288 b'\xed\xa0\x80' # lone surrogate character (invalid) 289 b'\xfd\xbf\xbf\xbb\xba\xba' # character outside [U+0000; U+10ffff] 290 ) 291 test_args = [valid_utf8, invalid_utf8] 292 293 for run_cmd in (run_default, run_c_locale, run_utf8_mode): 294 with self.subTest(run_cmd=run_cmd): 295 for arg in test_args: 296 proc = run_cmd(arg) 297 self.assertEqual(proc.stdout.rstrip(), ascii(arg)) 298 299 @unittest.skipUnless((sys.platform == 'darwin' or 300 support.is_android), 'test specific to Mac OS X and Android') 301 def test_osx_android_utf8(self): 302 text = 'e:\xe9, euro:\u20ac, non-bmp:\U0010ffff'.encode('utf-8') 303 code = "import sys; print(ascii(sys.argv[1]))" 304 305 decoded = text.decode('utf-8', 'surrogateescape') 306 expected = ascii(decoded).encode('ascii') + b'\n' 307 308 env = os.environ.copy() 309 # C locale gives ASCII locale encoding, but Python uses UTF-8 310 # to parse the command line arguments on Mac OS X and Android. 311 env['LC_ALL'] = 'C' 312 313 p = subprocess.Popen( 314 (sys.executable, "-c", code, text), 315 stdout=subprocess.PIPE, 316 env=env) 317 stdout, stderr = p.communicate() 318 self.assertEqual(stdout, expected) 319 self.assertEqual(p.returncode, 0) 320 321 def test_non_interactive_output_buffering(self): 322 code = textwrap.dedent(""" 323 import sys 324 out = sys.stdout 325 print(out.isatty(), out.write_through, out.line_buffering) 326 err = sys.stderr 327 print(err.isatty(), err.write_through, err.line_buffering) 328 """) 329 args = [sys.executable, '-c', code] 330 proc = subprocess.run(args, stdout=subprocess.PIPE, 331 stderr=subprocess.PIPE, text=True, check=True) 332 self.assertEqual(proc.stdout, 333 'False False False\n' 334 'False False True\n') 335 336 def test_unbuffered_output(self): 337 # Test expected operation of the '-u' switch 338 for stream in ('stdout', 'stderr'): 339 # Binary is unbuffered 340 code = ("import os, sys; sys.%s.buffer.write(b'x'); os._exit(0)" 341 % stream) 342 rc, out, err = assert_python_ok('-u', '-c', code) 343 data = err if stream == 'stderr' else out 344 self.assertEqual(data, b'x', "binary %s not unbuffered" % stream) 345 # Text is unbuffered 346 code = ("import os, sys; sys.%s.write('x'); os._exit(0)" 347 % stream) 348 rc, out, err = assert_python_ok('-u', '-c', code) 349 data = err if stream == 'stderr' else out 350 self.assertEqual(data, b'x', "text %s not unbuffered" % stream) 351 352 def test_unbuffered_input(self): 353 # sys.stdin still works with '-u' 354 code = ("import sys; sys.stdout.write(sys.stdin.read(1))") 355 p = spawn_python('-u', '-c', code) 356 p.stdin.write(b'x') 357 p.stdin.flush() 358 data, rc = _kill_python_and_exit_code(p) 359 self.assertEqual(rc, 0) 360 self.assertTrue(data.startswith(b'x'), data) 361 362 def test_large_PYTHONPATH(self): 363 path1 = "ABCDE" * 100 364 path2 = "FGHIJ" * 100 365 path = path1 + os.pathsep + path2 366 367 code = """if 1: 368 import sys 369 path = ":".join(sys.path) 370 path = path.encode("ascii", "backslashreplace") 371 sys.stdout.buffer.write(path)""" 372 rc, out, err = assert_python_ok('-S', '-c', code, 373 PYTHONPATH=path) 374 self.assertIn(path1.encode('ascii'), out) 375 self.assertIn(path2.encode('ascii'), out) 376 377 @unittest.skipIf(sys.flags.safe_path, 378 'PYTHONSAFEPATH changes default sys.path') 379 def test_empty_PYTHONPATH_issue16309(self): 380 # On Posix, it is documented that setting PATH to the 381 # empty string is equivalent to not setting PATH at all, 382 # which is an exception to the rule that in a string like 383 # "/bin::/usr/bin" the empty string in the middle gets 384 # interpreted as '.' 385 code = """if 1: 386 import sys 387 path = ":".join(sys.path) 388 path = path.encode("ascii", "backslashreplace") 389 sys.stdout.buffer.write(path)""" 390 rc1, out1, err1 = assert_python_ok('-c', code, PYTHONPATH="") 391 rc2, out2, err2 = assert_python_ok('-c', code, __isolated=False) 392 # regarding to Posix specification, outputs should be equal 393 # for empty and unset PYTHONPATH 394 self.assertEqual(out1, out2) 395 396 def test_displayhook_unencodable(self): 397 for encoding in ('ascii', 'latin-1', 'utf-8'): 398 env = os.environ.copy() 399 env['PYTHONIOENCODING'] = encoding 400 p = subprocess.Popen( 401 [sys.executable, '-i'], 402 stdin=subprocess.PIPE, 403 stdout=subprocess.PIPE, 404 stderr=subprocess.STDOUT, 405 env=env) 406 # non-ascii, surrogate, non-BMP printable, non-BMP unprintable 407 text = "a=\xe9 b=\uDC80 c=\U00010000 d=\U0010FFFF" 408 p.stdin.write(ascii(text).encode('ascii') + b"\n") 409 p.stdin.write(b'exit()\n') 410 data = kill_python(p) 411 escaped = repr(text).encode(encoding, 'backslashreplace') 412 self.assertIn(escaped, data) 413 414 def check_input(self, code, expected): 415 with tempfile.NamedTemporaryFile("wb+") as stdin: 416 sep = os.linesep.encode('ASCII') 417 stdin.write(sep.join((b'abc', b'def'))) 418 stdin.flush() 419 stdin.seek(0) 420 with subprocess.Popen( 421 (sys.executable, "-c", code), 422 stdin=stdin, stdout=subprocess.PIPE) as proc: 423 stdout, stderr = proc.communicate() 424 self.assertEqual(stdout.rstrip(), expected) 425 426 def test_stdin_readline(self): 427 # Issue #11272: check that sys.stdin.readline() replaces '\r\n' by '\n' 428 # on Windows (sys.stdin is opened in binary mode) 429 self.check_input( 430 "import sys; print(repr(sys.stdin.readline()))", 431 b"'abc\\n'") 432 433 def test_builtin_input(self): 434 # Issue #11272: check that input() strips newlines ('\n' or '\r\n') 435 self.check_input( 436 "print(repr(input()))", 437 b"'abc'") 438 439 def test_output_newline(self): 440 # Issue 13119 Newline for print() should be \r\n on Windows. 441 code = """if 1: 442 import sys 443 print(1) 444 print(2) 445 print(3, file=sys.stderr) 446 print(4, file=sys.stderr)""" 447 rc, out, err = assert_python_ok('-c', code) 448 449 if sys.platform == 'win32': 450 self.assertEqual(b'1\r\n2\r\n', out) 451 self.assertEqual(b'3\r\n4\r\n', err) 452 else: 453 self.assertEqual(b'1\n2\n', out) 454 self.assertEqual(b'3\n4\n', err) 455 456 def test_unmached_quote(self): 457 # Issue #10206: python program starting with unmatched quote 458 # spewed spaces to stdout 459 rc, out, err = assert_python_failure('-c', "'") 460 self.assertRegex(err.decode('ascii', 'ignore'), 'SyntaxError') 461 self.assertEqual(b'', out) 462 463 def test_stdout_flush_at_shutdown(self): 464 # Issue #5319: if stdout.flush() fails at shutdown, an error should 465 # be printed out. 466 code = """if 1: 467 import os, sys, test.support 468 test.support.SuppressCrashReport().__enter__() 469 sys.stdout.write('x') 470 os.close(sys.stdout.fileno())""" 471 rc, out, err = assert_python_failure('-c', code) 472 self.assertEqual(b'', out) 473 self.assertEqual(120, rc) 474 self.assertRegex(err.decode('ascii', 'ignore'), 475 'Exception ignored in.*\nOSError: .*') 476 477 def test_closed_stdout(self): 478 # Issue #13444: if stdout has been explicitly closed, we should 479 # not attempt to flush it at shutdown. 480 code = "import sys; sys.stdout.close()" 481 rc, out, err = assert_python_ok('-c', code) 482 self.assertEqual(b'', err) 483 484 # Issue #7111: Python should work without standard streams 485 486 @unittest.skipIf(os.name != 'posix', "test needs POSIX semantics") 487 @unittest.skipIf(sys.platform == "vxworks", 488 "test needs preexec support in subprocess.Popen") 489 def _test_no_stdio(self, streams): 490 code = """if 1: 491 import os, sys 492 for i, s in enumerate({streams}): 493 if getattr(sys, s) is not None: 494 os._exit(i + 1) 495 os._exit(42)""".format(streams=streams) 496 def preexec(): 497 if 'stdin' in streams: 498 os.close(0) 499 if 'stdout' in streams: 500 os.close(1) 501 if 'stderr' in streams: 502 os.close(2) 503 p = subprocess.Popen( 504 [sys.executable, "-E", "-c", code], 505 stdin=subprocess.PIPE, 506 stdout=subprocess.PIPE, 507 stderr=subprocess.PIPE, 508 preexec_fn=preexec) 509 out, err = p.communicate() 510 self.assertEqual(err, b'') 511 self.assertEqual(p.returncode, 42) 512 513 def test_no_stdin(self): 514 self._test_no_stdio(['stdin']) 515 516 def test_no_stdout(self): 517 self._test_no_stdio(['stdout']) 518 519 def test_no_stderr(self): 520 self._test_no_stdio(['stderr']) 521 522 def test_no_std_streams(self): 523 self._test_no_stdio(['stdin', 'stdout', 'stderr']) 524 525 def test_hash_randomization(self): 526 # Verify that -R enables hash randomization: 527 self.verify_valid_flag('-R') 528 hashes = [] 529 if os.environ.get('PYTHONHASHSEED', 'random') != 'random': 530 env = dict(os.environ) # copy 531 # We need to test that it is enabled by default without 532 # the environment variable enabling it for us. 533 del env['PYTHONHASHSEED'] 534 env['__cleanenv'] = '1' # consumed by assert_python_ok() 535 else: 536 env = {} 537 for i in range(3): 538 code = 'print(hash("spam"))' 539 rc, out, err = assert_python_ok('-c', code, **env) 540 self.assertEqual(rc, 0) 541 hashes.append(out) 542 hashes = sorted(set(hashes)) # uniq 543 # Rare chance of failure due to 3 random seeds honestly being equal. 544 self.assertGreater(len(hashes), 1, 545 msg='3 runs produced an identical random hash ' 546 ' for "spam": {}'.format(hashes)) 547 548 # Verify that sys.flags contains hash_randomization 549 code = 'import sys; print("random is", sys.flags.hash_randomization)' 550 rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='') 551 self.assertIn(b'random is 1', out) 552 553 rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='random') 554 self.assertIn(b'random is 1', out) 555 556 rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='0') 557 self.assertIn(b'random is 0', out) 558 559 rc, out, err = assert_python_ok('-R', '-c', code, PYTHONHASHSEED='0') 560 self.assertIn(b'random is 1', out) 561 562 def test_del___main__(self): 563 # Issue #15001: PyRun_SimpleFileExFlags() did crash because it kept a 564 # borrowed reference to the dict of __main__ module and later modify 565 # the dict whereas the module was destroyed 566 filename = os_helper.TESTFN 567 self.addCleanup(os_helper.unlink, filename) 568 with open(filename, "w", encoding="utf-8") as script: 569 print("import sys", file=script) 570 print("del sys.modules['__main__']", file=script) 571 assert_python_ok(filename) 572 573 def test_unknown_options(self): 574 rc, out, err = assert_python_failure('-E', '-z') 575 self.assertIn(b'Unknown option: -z', err) 576 self.assertEqual(err.splitlines().count(b'Unknown option: -z'), 1) 577 self.assertEqual(b'', out) 578 # Add "without='-E'" to prevent _assert_python to append -E 579 # to env_vars and change the output of stderr 580 rc, out, err = assert_python_failure('-z', without='-E') 581 self.assertIn(b'Unknown option: -z', err) 582 self.assertEqual(err.splitlines().count(b'Unknown option: -z'), 1) 583 self.assertEqual(b'', out) 584 rc, out, err = assert_python_failure('-a', '-z', without='-E') 585 self.assertIn(b'Unknown option: -a', err) 586 # only the first unknown option is reported 587 self.assertNotIn(b'Unknown option: -z', err) 588 self.assertEqual(err.splitlines().count(b'Unknown option: -a'), 1) 589 self.assertEqual(b'', out) 590 591 @unittest.skipIf(interpreter_requires_environment(), 592 'Cannot run -I tests when PYTHON env vars are required.') 593 def test_isolatedmode(self): 594 self.verify_valid_flag('-I') 595 self.verify_valid_flag('-IEPs') 596 rc, out, err = assert_python_ok('-I', '-c', 597 'from sys import flags as f; ' 598 'print(f.no_user_site, f.ignore_environment, f.isolated, f.safe_path)', 599 # dummyvar to prevent extraneous -E 600 dummyvar="") 601 self.assertEqual(out.strip(), b'1 1 1 True') 602 with os_helper.temp_cwd() as tmpdir: 603 fake = os.path.join(tmpdir, "uuid.py") 604 main = os.path.join(tmpdir, "main.py") 605 with open(fake, "w", encoding="utf-8") as f: 606 f.write("raise RuntimeError('isolated mode test')\n") 607 with open(main, "w", encoding="utf-8") as f: 608 f.write("import uuid\n") 609 f.write("print('ok')\n") 610 # Use -E to ignore PYTHONSAFEPATH env var 611 self.assertRaises(subprocess.CalledProcessError, 612 subprocess.check_output, 613 [sys.executable, '-E', main], cwd=tmpdir, 614 stderr=subprocess.DEVNULL) 615 out = subprocess.check_output([sys.executable, "-I", main], 616 cwd=tmpdir) 617 self.assertEqual(out.strip(), b"ok") 618 619 def test_sys_flags_set(self): 620 # Issue 31845: a startup refactoring broke reading flags from env vars 621 for value, expected in (("", 0), ("1", 1), ("text", 1), ("2", 2)): 622 env_vars = dict( 623 PYTHONDEBUG=value, 624 PYTHONOPTIMIZE=value, 625 PYTHONDONTWRITEBYTECODE=value, 626 PYTHONVERBOSE=value, 627 ) 628 dont_write_bytecode = int(bool(value)) 629 code = ( 630 "import sys; " 631 "sys.stderr.write(str(sys.flags)); " 632 f"""sys.exit(not ( 633 sys.flags.debug == sys.flags.optimize == 634 sys.flags.verbose == 635 {expected} 636 and sys.flags.dont_write_bytecode == {dont_write_bytecode} 637 ))""" 638 ) 639 with self.subTest(envar_value=value): 640 assert_python_ok('-c', code, **env_vars) 641 642 def test_set_pycache_prefix(self): 643 # sys.pycache_prefix can be set from either -X pycache_prefix or 644 # PYTHONPYCACHEPREFIX env var, with the former taking precedence. 645 NO_VALUE = object() # `-X pycache_prefix` with no `=PATH` 646 cases = [ 647 # (PYTHONPYCACHEPREFIX, -X pycache_prefix, sys.pycache_prefix) 648 (None, None, None), 649 ('foo', None, 'foo'), 650 (None, 'bar', 'bar'), 651 ('foo', 'bar', 'bar'), 652 ('foo', '', None), 653 ('foo', NO_VALUE, None), 654 ] 655 for envval, opt, expected in cases: 656 exp_clause = "is None" if expected is None else f'== "{expected}"' 657 code = f"import sys; sys.exit(not sys.pycache_prefix {exp_clause})" 658 args = ['-c', code] 659 env = {} if envval is None else {'PYTHONPYCACHEPREFIX': envval} 660 if opt is NO_VALUE: 661 args[:0] = ['-X', 'pycache_prefix'] 662 elif opt is not None: 663 args[:0] = ['-X', f'pycache_prefix={opt}'] 664 with self.subTest(envval=envval, opt=opt): 665 with os_helper.temp_cwd(): 666 assert_python_ok(*args, **env) 667 668 def run_xdev(self, *args, check_exitcode=True, xdev=True): 669 env = dict(os.environ) 670 env.pop('PYTHONWARNINGS', None) 671 env.pop('PYTHONDEVMODE', None) 672 env.pop('PYTHONMALLOC', None) 673 674 if xdev: 675 args = (sys.executable, '-X', 'dev', *args) 676 else: 677 args = (sys.executable, *args) 678 proc = subprocess.run(args, 679 stdout=subprocess.PIPE, 680 stderr=subprocess.STDOUT, 681 universal_newlines=True, 682 env=env) 683 if check_exitcode: 684 self.assertEqual(proc.returncode, 0, proc) 685 return proc.stdout.rstrip() 686 687 def test_xdev(self): 688 # sys.flags.dev_mode 689 code = "import sys; print(sys.flags.dev_mode)" 690 out = self.run_xdev("-c", code, xdev=False) 691 self.assertEqual(out, "False") 692 out = self.run_xdev("-c", code) 693 self.assertEqual(out, "True") 694 695 # Warnings 696 code = ("import warnings; " 697 "print(' '.join('%s::%s' % (f[0], f[2].__name__) " 698 "for f in warnings.filters))") 699 if Py_DEBUG: 700 expected_filters = "default::Warning" 701 else: 702 expected_filters = ("default::Warning " 703 "default::DeprecationWarning " 704 "ignore::DeprecationWarning " 705 "ignore::PendingDeprecationWarning " 706 "ignore::ImportWarning " 707 "ignore::ResourceWarning") 708 709 out = self.run_xdev("-c", code) 710 self.assertEqual(out, expected_filters) 711 712 out = self.run_xdev("-b", "-c", code) 713 self.assertEqual(out, f"default::BytesWarning {expected_filters}") 714 715 out = self.run_xdev("-bb", "-c", code) 716 self.assertEqual(out, f"error::BytesWarning {expected_filters}") 717 718 out = self.run_xdev("-Werror", "-c", code) 719 self.assertEqual(out, f"error::Warning {expected_filters}") 720 721 # Memory allocator debug hooks 722 try: 723 import _testcapi 724 except ImportError: 725 pass 726 else: 727 code = "import _testcapi; print(_testcapi.pymem_getallocatorsname())" 728 with support.SuppressCrashReport(): 729 out = self.run_xdev("-c", code, check_exitcode=False) 730 if support.with_pymalloc(): 731 alloc_name = "pymalloc_debug" 732 else: 733 alloc_name = "malloc_debug" 734 self.assertEqual(out, alloc_name) 735 736 # Faulthandler 737 try: 738 import faulthandler 739 except ImportError: 740 pass 741 else: 742 code = "import faulthandler; print(faulthandler.is_enabled())" 743 out = self.run_xdev("-c", code) 744 self.assertEqual(out, "True") 745 746 def check_warnings_filters(self, cmdline_option, envvar, use_pywarning=False): 747 if use_pywarning: 748 code = ("import sys; from test.support.import_helper import " 749 "import_fresh_module; " 750 "warnings = import_fresh_module('warnings', blocked=['_warnings']); ") 751 else: 752 code = "import sys, warnings; " 753 code += ("print(' '.join('%s::%s' % (f[0], f[2].__name__) " 754 "for f in warnings.filters))") 755 args = (sys.executable, '-W', cmdline_option, '-bb', '-c', code) 756 env = dict(os.environ) 757 env.pop('PYTHONDEVMODE', None) 758 env["PYTHONWARNINGS"] = envvar 759 proc = subprocess.run(args, 760 stdout=subprocess.PIPE, 761 stderr=subprocess.STDOUT, 762 universal_newlines=True, 763 env=env) 764 self.assertEqual(proc.returncode, 0, proc) 765 return proc.stdout.rstrip() 766 767 def test_warnings_filter_precedence(self): 768 expected_filters = ("error::BytesWarning " 769 "once::UserWarning " 770 "always::UserWarning") 771 if not Py_DEBUG: 772 expected_filters += (" " 773 "default::DeprecationWarning " 774 "ignore::DeprecationWarning " 775 "ignore::PendingDeprecationWarning " 776 "ignore::ImportWarning " 777 "ignore::ResourceWarning") 778 779 out = self.check_warnings_filters("once::UserWarning", 780 "always::UserWarning") 781 self.assertEqual(out, expected_filters) 782 783 out = self.check_warnings_filters("once::UserWarning", 784 "always::UserWarning", 785 use_pywarning=True) 786 self.assertEqual(out, expected_filters) 787 788 def check_pythonmalloc(self, env_var, name): 789 code = 'import _testcapi; print(_testcapi.pymem_getallocatorsname())' 790 env = dict(os.environ) 791 env.pop('PYTHONDEVMODE', None) 792 if env_var is not None: 793 env['PYTHONMALLOC'] = env_var 794 else: 795 env.pop('PYTHONMALLOC', None) 796 args = (sys.executable, '-c', code) 797 proc = subprocess.run(args, 798 stdout=subprocess.PIPE, 799 stderr=subprocess.STDOUT, 800 universal_newlines=True, 801 env=env) 802 self.assertEqual(proc.stdout.rstrip(), name) 803 self.assertEqual(proc.returncode, 0) 804 805 def test_pythonmalloc(self): 806 # Test the PYTHONMALLOC environment variable 807 pymalloc = support.with_pymalloc() 808 if pymalloc: 809 default_name = 'pymalloc_debug' if Py_DEBUG else 'pymalloc' 810 default_name_debug = 'pymalloc_debug' 811 else: 812 default_name = 'malloc_debug' if Py_DEBUG else 'malloc' 813 default_name_debug = 'malloc_debug' 814 815 tests = [ 816 (None, default_name), 817 ('debug', default_name_debug), 818 ('malloc', 'malloc'), 819 ('malloc_debug', 'malloc_debug'), 820 ] 821 if pymalloc: 822 tests.extend(( 823 ('pymalloc', 'pymalloc'), 824 ('pymalloc_debug', 'pymalloc_debug'), 825 )) 826 827 for env_var, name in tests: 828 with self.subTest(env_var=env_var, name=name): 829 self.check_pythonmalloc(env_var, name) 830 831 def test_pythondevmode_env(self): 832 # Test the PYTHONDEVMODE environment variable 833 code = "import sys; print(sys.flags.dev_mode)" 834 env = dict(os.environ) 835 env.pop('PYTHONDEVMODE', None) 836 args = (sys.executable, '-c', code) 837 838 proc = subprocess.run(args, stdout=subprocess.PIPE, 839 universal_newlines=True, env=env) 840 self.assertEqual(proc.stdout.rstrip(), 'False') 841 self.assertEqual(proc.returncode, 0, proc) 842 843 env['PYTHONDEVMODE'] = '1' 844 proc = subprocess.run(args, stdout=subprocess.PIPE, 845 universal_newlines=True, env=env) 846 self.assertEqual(proc.stdout.rstrip(), 'True') 847 self.assertEqual(proc.returncode, 0, proc) 848 849 @unittest.skipUnless(sys.platform == 'win32', 850 'bpo-32457 only applies on Windows') 851 def test_argv0_normalization(self): 852 args = sys.executable, '-c', 'print(0)' 853 prefix, exe = os.path.split(sys.executable) 854 executable = prefix + '\\.\\.\\.\\' + exe 855 856 proc = subprocess.run(args, stdout=subprocess.PIPE, 857 executable=executable) 858 self.assertEqual(proc.returncode, 0, proc) 859 self.assertEqual(proc.stdout.strip(), b'0') 860 861 def test_parsing_error(self): 862 args = [sys.executable, '-I', '--unknown-option'] 863 proc = subprocess.run(args, 864 stdout=subprocess.PIPE, 865 stderr=subprocess.PIPE, 866 text=True) 867 err_msg = "unknown option --unknown-option\nusage: " 868 self.assertTrue(proc.stderr.startswith(err_msg), proc.stderr) 869 self.assertNotEqual(proc.returncode, 0) 870 871 def test_int_max_str_digits(self): 872 code = "import sys; print(sys.flags.int_max_str_digits, sys.get_int_max_str_digits())" 873 874 assert_python_failure('-X', 'int_max_str_digits', '-c', code) 875 assert_python_failure('-X', 'int_max_str_digits=foo', '-c', code) 876 assert_python_failure('-X', 'int_max_str_digits=100', '-c', code) 877 assert_python_failure('-X', 'int_max_str_digits', '-c', code, 878 PYTHONINTMAXSTRDIGITS='4000') 879 880 assert_python_failure('-c', code, PYTHONINTMAXSTRDIGITS='foo') 881 assert_python_failure('-c', code, PYTHONINTMAXSTRDIGITS='100') 882 883 def res2int(res): 884 out = res.out.strip().decode("utf-8") 885 return tuple(int(i) for i in out.split()) 886 887 res = assert_python_ok('-c', code) 888 self.assertEqual(res2int(res), (-1, sys.get_int_max_str_digits())) 889 res = assert_python_ok('-X', 'int_max_str_digits=0', '-c', code) 890 self.assertEqual(res2int(res), (0, 0)) 891 res = assert_python_ok('-X', 'int_max_str_digits=4000', '-c', code) 892 self.assertEqual(res2int(res), (4000, 4000)) 893 res = assert_python_ok('-X', 'int_max_str_digits=100000', '-c', code) 894 self.assertEqual(res2int(res), (100000, 100000)) 895 896 res = assert_python_ok('-c', code, PYTHONINTMAXSTRDIGITS='0') 897 self.assertEqual(res2int(res), (0, 0)) 898 res = assert_python_ok('-c', code, PYTHONINTMAXSTRDIGITS='4000') 899 self.assertEqual(res2int(res), (4000, 4000)) 900 res = assert_python_ok( 901 '-X', 'int_max_str_digits=6000', '-c', code, 902 PYTHONINTMAXSTRDIGITS='4000' 903 ) 904 self.assertEqual(res2int(res), (6000, 6000)) 905 906 907@unittest.skipIf(interpreter_requires_environment(), 908 'Cannot run -I tests when PYTHON env vars are required.') 909class IgnoreEnvironmentTest(unittest.TestCase): 910 911 def run_ignoring_vars(self, predicate, **env_vars): 912 # Runs a subprocess with -E set, even though we're passing 913 # specific environment variables 914 # Logical inversion to match predicate check to a zero return 915 # code indicating success 916 code = "import sys; sys.stderr.write(str(sys.flags)); sys.exit(not ({}))".format(predicate) 917 return assert_python_ok('-E', '-c', code, **env_vars) 918 919 def test_ignore_PYTHONPATH(self): 920 path = "should_be_ignored" 921 self.run_ignoring_vars("'{}' not in sys.path".format(path), 922 PYTHONPATH=path) 923 924 def test_ignore_PYTHONHASHSEED(self): 925 self.run_ignoring_vars("sys.flags.hash_randomization == 1", 926 PYTHONHASHSEED="0") 927 928 def test_sys_flags_not_set(self): 929 # Issue 31845: a startup refactoring broke reading flags from env vars 930 expected_outcome = """ 931 (sys.flags.debug == sys.flags.optimize == 932 sys.flags.dont_write_bytecode == 933 sys.flags.verbose == sys.flags.safe_path == 0) 934 """ 935 self.run_ignoring_vars( 936 expected_outcome, 937 PYTHONDEBUG="1", 938 PYTHONOPTIMIZE="1", 939 PYTHONDONTWRITEBYTECODE="1", 940 PYTHONVERBOSE="1", 941 PYTHONSAFEPATH="1", 942 ) 943 944 945class SyntaxErrorTests(unittest.TestCase): 946 def check_string(self, code): 947 proc = subprocess.run([sys.executable, "-"], input=code, 948 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 949 self.assertNotEqual(proc.returncode, 0) 950 self.assertNotEqual(proc.stderr, None) 951 self.assertIn(b"\nSyntaxError", proc.stderr) 952 953 def test_tokenizer_error_with_stdin(self): 954 self.check_string(b"(1+2+3") 955 956 def test_decoding_error_at_the_end_of_the_line(self): 957 self.check_string(br"'\u1f'") 958 959 960def tearDownModule(): 961 support.reap_children() 962 963 964if __name__ == "__main__": 965 unittest.main() 966