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