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 # Debug build?
22 Py_DEBUG = hasattr(sys, "gettotalrefcount")
23 
24 
25 # XXX (ncoghlan): Move to script_helper and make consistent with run_python
26 def _kill_python_and_exit_code(p):
27     data = kill_python(p)
28     returncode = p.wait()
29     return data, returncode
30 
31 
32 class 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.')
909 class 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 
945 class 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 
960 def tearDownModule():
961     support.reap_children()
962 
963 
964 if __name__ == "__main__":
965     unittest.main()
966