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