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