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