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