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