• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Run the tests in Programs/_testembed.c (tests for the CPython embedding APIs)
2from test import support
3from test.support import import_helper, os_helper, threading_helper, MS_WINDOWS
4import unittest
5
6from collections import namedtuple
7import contextlib
8import io
9import json
10import os
11import os.path
12import re
13import shutil
14import subprocess
15import sys
16import sysconfig
17import tempfile
18import textwrap
19
20if not support.has_subprocess_support:
21    raise unittest.SkipTest("test module requires subprocess")
22
23
24try:
25    import _testinternalcapi
26except ImportError:
27    _testinternalcapi = None
28
29
30MACOS = (sys.platform == 'darwin')
31PYMEM_ALLOCATOR_NOT_SET = 0
32PYMEM_ALLOCATOR_DEBUG = 2
33PYMEM_ALLOCATOR_MALLOC = 3
34PYMEM_ALLOCATOR_MIMALLOC = 7
35if support.Py_GIL_DISABLED:
36    ALLOCATOR_FOR_CONFIG = PYMEM_ALLOCATOR_MIMALLOC
37else:
38    ALLOCATOR_FOR_CONFIG = PYMEM_ALLOCATOR_MALLOC
39
40Py_STATS = hasattr(sys, '_stats_on')
41
42# _PyCoreConfig_InitCompatConfig()
43API_COMPAT = 1
44# _PyCoreConfig_InitPythonConfig()
45API_PYTHON = 2
46# _PyCoreConfig_InitIsolatedConfig()
47API_ISOLATED = 3
48
49INIT_LOOPS = 4
50MAX_HASH_SEED = 4294967295
51
52ABI_THREAD = 't' if sysconfig.get_config_var('Py_GIL_DISABLED') else ''
53
54
55# If we are running from a build dir, but the stdlib has been installed,
56# some tests need to expect different results.
57STDLIB_INSTALL = os.path.join(sys.prefix, sys.platlibdir,
58    f'python{sys.version_info.major}.{sys.version_info.minor}')
59if not os.path.isfile(os.path.join(STDLIB_INSTALL, 'os.py')):
60    STDLIB_INSTALL = None
61
62def debug_build(program):
63    program = os.path.basename(program)
64    name = os.path.splitext(program)[0]
65    return name.casefold().endswith("_d".casefold())
66
67
68def remove_python_envvars():
69    env = dict(os.environ)
70    # Remove PYTHON* environment variables to get deterministic environment
71    for key in list(env):
72        if key.startswith('PYTHON'):
73            del env[key]
74    return env
75
76
77class EmbeddingTestsMixin:
78    def setUp(self):
79        exename = "_testembed"
80        builddir = os.path.dirname(sys.executable)
81        if MS_WINDOWS:
82            ext = ("_d" if debug_build(sys.executable) else "") + ".exe"
83            exename += ext
84            exepath = builddir
85        else:
86            exepath = os.path.join(builddir, 'Programs')
87        self.test_exe = exe = os.path.join(exepath, exename)
88        if not os.path.exists(exe):
89            self.skipTest("%r doesn't exist" % exe)
90        # This is needed otherwise we get a fatal error:
91        # "Py_Initialize: Unable to get the locale encoding
92        # LookupError: no codec search functions registered: can't find encoding"
93        self.oldcwd = os.getcwd()
94        os.chdir(builddir)
95
96    def tearDown(self):
97        os.chdir(self.oldcwd)
98
99    def run_embedded_interpreter(self, *args, env=None,
100                                 timeout=None, returncode=0, input=None,
101                                 cwd=None):
102        """Runs a test in the embedded interpreter"""
103        cmd = [self.test_exe]
104        cmd.extend(args)
105        if env is not None and MS_WINDOWS:
106            # Windows requires at least the SYSTEMROOT environment variable to
107            # start Python.
108            env = env.copy()
109            env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
110
111        p = subprocess.Popen(cmd,
112                             stdout=subprocess.PIPE,
113                             stderr=subprocess.PIPE,
114                             universal_newlines=True,
115                             env=env,
116                             cwd=cwd)
117        try:
118            (out, err) = p.communicate(input=input, timeout=timeout)
119        except:
120            p.terminate()
121            p.wait()
122            raise
123        if p.returncode != returncode and support.verbose:
124            print(f"--- {cmd} failed ---")
125            print(f"stdout:\n{out}")
126            print(f"stderr:\n{err}")
127            print("------")
128
129        self.assertEqual(p.returncode, returncode,
130                         "bad returncode %d, stderr is %r" %
131                         (p.returncode, err))
132        return out, err
133
134    def run_repeated_init_and_subinterpreters(self):
135        out, err = self.run_embedded_interpreter("test_repeated_init_and_subinterpreters")
136        self.assertEqual(err, "")
137
138        # The output from _testembed looks like this:
139        # --- Pass 1 ---
140        # interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
141        # interp 1 <0x1d4f690>, thread state <0x1d35350>: id(modules) = 139650431165784
142        # interp 2 <0x1d5a690>, thread state <0x1d99ed0>: id(modules) = 139650413140368
143        # interp 3 <0x1d4f690>, thread state <0x1dc3340>: id(modules) = 139650412862200
144        # interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
145        # --- Pass 2 ---
146        # ...
147
148        interp_pat = (r"^interp (\d+) <(0x[\dA-F]+)>, "
149                      r"thread state <(0x[\dA-F]+)>: "
150                      r"id\(modules\) = ([\d]+)$")
151        Interp = namedtuple("Interp", "id interp tstate modules")
152
153        numloops = 1
154        current_run = []
155        for line in out.splitlines():
156            if line == "--- Pass {} ---".format(numloops):
157                self.assertEqual(len(current_run), 0)
158                if support.verbose > 1:
159                    print(line)
160                numloops += 1
161                continue
162
163            self.assertLess(len(current_run), 5)
164            match = re.match(interp_pat, line)
165            if match is None:
166                self.assertRegex(line, interp_pat)
167
168            # Parse the line from the loop.  The first line is the main
169            # interpreter and the 3 afterward are subinterpreters.
170            interp = Interp(*match.groups())
171            if support.verbose > 2:
172                # 5 lines per pass is super-spammy, so limit that to -vvv
173                print(interp)
174            self.assertTrue(interp.interp)
175            self.assertTrue(interp.tstate)
176            self.assertTrue(interp.modules)
177            current_run.append(interp)
178
179            # The last line in the loop should be the same as the first.
180            if len(current_run) == 5:
181                main = current_run[0]
182                self.assertEqual(interp, main)
183                yield current_run
184                current_run = []
185
186
187class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
188    maxDiff = 100 * 50
189
190    def test_subinterps_main(self):
191        for run in self.run_repeated_init_and_subinterpreters():
192            main = run[0]
193
194            self.assertEqual(main.id, '0')
195
196    def test_subinterps_different_ids(self):
197        for run in self.run_repeated_init_and_subinterpreters():
198            main, *subs, _ = run
199
200            mainid = int(main.id)
201            for i, sub in enumerate(subs):
202                self.assertEqual(sub.id, str(mainid + i + 1))
203
204    def test_subinterps_distinct_state(self):
205        for run in self.run_repeated_init_and_subinterpreters():
206            main, *subs, _ = run
207
208            if '0x0' in main:
209                # XXX Fix on Windows (and other platforms): something
210                # is going on with the pointers in Programs/_testembed.c.
211                # interp.interp is 0x0 and interp.modules is the same
212                # between interpreters.
213                raise unittest.SkipTest('platform prints pointers as 0x0')
214
215            for sub in subs:
216                # A new subinterpreter may have the same
217                # PyInterpreterState pointer as a previous one if
218                # the earlier one has already been destroyed.  So
219                # we compare with the main interpreter.  The same
220                # applies to tstate.
221                self.assertNotEqual(sub.interp, main.interp)
222                self.assertNotEqual(sub.tstate, main.tstate)
223                self.assertNotEqual(sub.modules, main.modules)
224
225    def test_repeated_init_and_inittab(self):
226        out, err = self.run_embedded_interpreter("test_repeated_init_and_inittab")
227        self.assertEqual(err, "")
228
229        lines = [f"--- Pass {i} ---" for i in range(1, INIT_LOOPS+1)]
230        lines = "\n".join(lines) + "\n"
231        self.assertEqual(out, lines)
232
233    def test_forced_io_encoding(self):
234        # Checks forced configuration of embedded interpreter IO streams
235        env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
236        out, err = self.run_embedded_interpreter("test_forced_io_encoding", env=env)
237        if support.verbose > 1:
238            print()
239            print(out)
240            print(err)
241        expected_stream_encoding = "utf-8"
242        expected_errors = "surrogateescape"
243        expected_output = '\n'.join([
244        "--- Use defaults ---",
245        "Expected encoding: default",
246        "Expected errors: default",
247        "stdin: {in_encoding}:{errors}",
248        "stdout: {out_encoding}:{errors}",
249        "stderr: {out_encoding}:backslashreplace",
250        "--- Set errors only ---",
251        "Expected encoding: default",
252        "Expected errors: ignore",
253        "stdin: {in_encoding}:ignore",
254        "stdout: {out_encoding}:ignore",
255        "stderr: {out_encoding}:backslashreplace",
256        "--- Set encoding only ---",
257        "Expected encoding: iso8859-1",
258        "Expected errors: default",
259        "stdin: iso8859-1:{errors}",
260        "stdout: iso8859-1:{errors}",
261        "stderr: iso8859-1:backslashreplace",
262        "--- Set encoding and errors ---",
263        "Expected encoding: iso8859-1",
264        "Expected errors: replace",
265        "stdin: iso8859-1:replace",
266        "stdout: iso8859-1:replace",
267        "stderr: iso8859-1:backslashreplace"])
268        expected_output = expected_output.format(
269                                in_encoding=expected_stream_encoding,
270                                out_encoding=expected_stream_encoding,
271                                errors=expected_errors)
272        # This is useful if we ever trip over odd platform behaviour
273        self.maxDiff = None
274        self.assertEqual(out.strip(), expected_output)
275
276    def test_pre_initialization_api(self):
277        """
278        Checks some key parts of the C-API that need to work before the runtime
279        is initialized (via Py_Initialize()).
280        """
281        env = dict(os.environ, PYTHONPATH=os.pathsep.join(sys.path))
282        out, err = self.run_embedded_interpreter("test_pre_initialization_api", env=env)
283        if support.verbose > 1:
284            print()
285            print(out)
286            print(err)
287        if MS_WINDOWS:
288            expected_path = self.test_exe
289        else:
290            expected_path = os.path.join(os.getcwd(), "spam")
291        expected_output = f"sys.executable: {expected_path}\n"
292        self.assertIn(expected_output, out)
293        self.assertEqual(err, '')
294
295    def test_pre_initialization_sys_options(self):
296        """
297        Checks that sys.warnoptions and sys._xoptions can be set before the
298        runtime is initialized (otherwise they won't be effective).
299        """
300        env = remove_python_envvars()
301        env['PYTHONPATH'] = os.pathsep.join(sys.path)
302        out, err = self.run_embedded_interpreter(
303                        "test_pre_initialization_sys_options", env=env)
304        if support.verbose > 1:
305            print()
306            print(out)
307            print(err)
308        expected_output = (
309            "sys.warnoptions: ['once', 'module', 'default']\n"
310            "sys._xoptions: {'not_an_option': '1', 'also_not_an_option': '2'}\n"
311            "warnings.filters[:3]: ['default', 'module', 'once']\n"
312        )
313        self.assertIn(expected_output, out)
314        self.assertEqual(err, '')
315
316    def test_bpo20891(self):
317        """
318        bpo-20891: Calling PyGILState_Ensure in a non-Python thread must not
319        crash.
320        """
321        out, err = self.run_embedded_interpreter("test_bpo20891")
322        self.assertEqual(out, '')
323        self.assertEqual(err, '')
324
325    def test_initialize_twice(self):
326        """
327        bpo-33932: Calling Py_Initialize() twice should do nothing (and not
328        crash!).
329        """
330        out, err = self.run_embedded_interpreter("test_initialize_twice")
331        self.assertEqual(out, '')
332        self.assertEqual(err, '')
333
334    def test_initialize_pymain(self):
335        """
336        bpo-34008: Calling Py_Main() after Py_Initialize() must not fail.
337        """
338        out, err = self.run_embedded_interpreter("test_initialize_pymain")
339        self.assertEqual(out.rstrip(), "Py_Main() after Py_Initialize: sys.argv=['-c', 'arg2']")
340        self.assertEqual(err, '')
341
342    def test_run_main(self):
343        out, err = self.run_embedded_interpreter("test_run_main")
344        self.assertEqual(out.rstrip(), "Py_RunMain(): sys.argv=['-c', 'arg2']")
345        self.assertEqual(err, '')
346
347    def test_run_main_loop(self):
348        # bpo-40413: Calling Py_InitializeFromConfig()+Py_RunMain() multiple
349        # times must not crash.
350        nloop = 5
351        out, err = self.run_embedded_interpreter("test_run_main_loop")
352        self.assertEqual(out, "Py_RunMain(): sys.argv=['-c', 'arg2']\n" * nloop)
353        self.assertEqual(err, '')
354
355    def test_finalize_structseq(self):
356        # bpo-46417: Py_Finalize() clears structseq static types. Check that
357        # sys attributes using struct types still work when
358        # Py_Finalize()/Py_Initialize() is called multiple times.
359        # print() calls type->tp_repr(instance) and so checks that the types
360        # are still working properly.
361        script = support.findfile('_test_embed_structseq.py')
362        with open(script, encoding="utf-8") as fp:
363            code = fp.read()
364        out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
365        self.assertEqual(out, 'Tests passed\n' * INIT_LOOPS)
366
367    def test_simple_initialization_api(self):
368        # _testembed now uses Py_InitializeFromConfig by default
369        # This case specifically checks Py_Initialize(Ex) still works
370        out, err = self.run_embedded_interpreter("test_repeated_simple_init")
371        self.assertEqual(out, 'Finalized\n' * INIT_LOOPS)
372
373    @support.requires_specialization
374    @unittest.skipUnless(support.TEST_MODULES_ENABLED, "requires test modules")
375    def test_specialized_static_code_gets_unspecialized_at_Py_FINALIZE(self):
376        # https://github.com/python/cpython/issues/92031
377
378        code = textwrap.dedent("""\
379            import dis
380            import importlib._bootstrap
381            import opcode
382            import test.test_dis
383
384            def is_specialized(f):
385                for instruction in dis.get_instructions(f, adaptive=True):
386                    opname = instruction.opname
387                    if (
388                        opname in opcode._specialized_opmap
389                        # Exclude superinstructions:
390                        and "__" not in opname
391                    ):
392                        return True
393                return False
394
395            func = importlib._bootstrap._handle_fromlist
396
397            # "copy" the code to un-specialize it:
398            func.__code__ = func.__code__.replace()
399
400            assert not is_specialized(func), "specialized instructions found"
401
402            for i in range(test.test_dis.ADAPTIVE_WARMUP_DELAY):
403                func(importlib._bootstrap, ["x"], lambda *args: None)
404
405            assert is_specialized(func), "no specialized instructions found"
406
407            print("Tests passed")
408        """)
409        run = self.run_embedded_interpreter
410        out, err = run("test_repeated_init_exec", code)
411        self.assertEqual(out, 'Tests passed\n' * INIT_LOOPS)
412
413    def test_ucnhash_capi_reset(self):
414        # bpo-47182: unicodeobject.c:ucnhash_capi was not reset on shutdown.
415        code = "print('\\N{digit nine}')"
416        out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
417        self.assertEqual(out, '9\n' * INIT_LOOPS)
418
419    def test_datetime_reset_strptime(self):
420        code = (
421            "import datetime;"
422            "d = datetime.datetime.strptime('2000-01-01', '%Y-%m-%d');"
423            "print(d.strftime('%Y%m%d'))"
424        )
425        out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
426        self.assertEqual(out, '20000101\n' * INIT_LOOPS)
427
428    def test_static_types_inherited_slots(self):
429        script = textwrap.dedent("""
430            import test.support
431
432            results = {}
433            def add(cls, slot, own):
434                value = getattr(cls, slot)
435                try:
436                    subresults = results[cls.__name__]
437                except KeyError:
438                    subresults = results[cls.__name__] = {}
439                subresults[slot] = [repr(value), own]
440
441            for cls in test.support.iter_builtin_types():
442                for slot, own in test.support.iter_slot_wrappers(cls):
443                    add(cls, slot, own)
444            """)
445
446        ns = {}
447        exec(script, ns, ns)
448        all_expected = ns['results']
449        del ns
450
451        script += textwrap.dedent("""
452            import json
453            import sys
454            text = json.dumps(results)
455            print(text, file=sys.stderr)
456            """)
457        out, err = self.run_embedded_interpreter(
458                "test_repeated_init_exec", script, script)
459        results = err.split('--- Loop #')[1:]
460        results = [res.rpartition(' ---\n')[-1] for res in results]
461
462        self.maxDiff = None
463        for i, text in enumerate(results, start=1):
464            result = json.loads(text)
465            for classname, expected in all_expected.items():
466                with self.subTest(loop=i, cls=classname):
467                    slots = result.pop(classname)
468                    self.assertEqual(slots, expected)
469            self.assertEqual(result, {})
470        self.assertEqual(out, '')
471
472    def test_getargs_reset_static_parser(self):
473        # Test _PyArg_Parser initializations via _PyArg_UnpackKeywords()
474        # https://github.com/python/cpython/issues/122334
475        code = textwrap.dedent("""
476            try:
477                import _ssl
478            except ModuleNotFoundError:
479                _ssl = None
480            if _ssl is not None:
481                _ssl.txt2obj(txt='1.3')
482            print('1')
483
484            import _queue
485            _queue.SimpleQueue().put_nowait(item=None)
486            print('2')
487
488            import _zoneinfo
489            _zoneinfo.ZoneInfo.clear_cache(only_keys=['Foo/Bar'])
490            print('3')
491        """)
492        out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
493        self.assertEqual(out, '1\n2\n3\n' * INIT_LOOPS)
494
495
496@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
497class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
498    maxDiff = 4096
499    UTF8_MODE_ERRORS = ('surrogatepass' if MS_WINDOWS else 'surrogateescape')
500
501    # Marker to read the default configuration: get_default_config()
502    GET_DEFAULT_CONFIG = object()
503
504    # Marker to ignore a configuration parameter
505    IGNORE_CONFIG = object()
506
507    PRE_CONFIG_COMPAT = {
508        '_config_init': API_COMPAT,
509        'allocator': PYMEM_ALLOCATOR_NOT_SET,
510        'parse_argv': 0,
511        'configure_locale': 1,
512        'coerce_c_locale': 0,
513        'coerce_c_locale_warn': 0,
514        'utf8_mode': 0,
515    }
516    if MS_WINDOWS:
517        PRE_CONFIG_COMPAT.update({
518            'legacy_windows_fs_encoding': 0,
519        })
520    PRE_CONFIG_PYTHON = dict(PRE_CONFIG_COMPAT,
521        _config_init=API_PYTHON,
522        parse_argv=1,
523        coerce_c_locale=GET_DEFAULT_CONFIG,
524        utf8_mode=GET_DEFAULT_CONFIG,
525    )
526    PRE_CONFIG_ISOLATED = dict(PRE_CONFIG_COMPAT,
527        _config_init=API_ISOLATED,
528        configure_locale=0,
529        isolated=1,
530        use_environment=0,
531        utf8_mode=0,
532        dev_mode=0,
533        coerce_c_locale=0,
534    )
535
536    COPY_PRE_CONFIG = [
537        'dev_mode',
538        'isolated',
539        'use_environment',
540    ]
541
542    CONFIG_COMPAT = {
543        '_config_init': API_COMPAT,
544        'isolated': False,
545        'use_environment': True,
546        'dev_mode': False,
547
548        'install_signal_handlers': True,
549        'use_hash_seed': False,
550        'hash_seed': 0,
551        'int_max_str_digits': sys.int_info.default_max_str_digits,
552        'cpu_count': -1,
553        'faulthandler': False,
554        'tracemalloc': 0,
555        'perf_profiling': False,
556        'import_time': False,
557        'code_debug_ranges': True,
558        'show_ref_count': False,
559        'dump_refs': False,
560        'dump_refs_file': None,
561        'malloc_stats': False,
562
563        'filesystem_encoding': GET_DEFAULT_CONFIG,
564        'filesystem_errors': GET_DEFAULT_CONFIG,
565
566        'pycache_prefix': None,
567        'program_name': GET_DEFAULT_CONFIG,
568        'parse_argv': False,
569        'argv': [""],
570        'orig_argv': [],
571
572        'xoptions': [],
573        'warnoptions': [],
574
575        'pythonpath_env': None,
576        'home': None,
577        'executable': GET_DEFAULT_CONFIG,
578        'base_executable': GET_DEFAULT_CONFIG,
579
580        'prefix': GET_DEFAULT_CONFIG,
581        'base_prefix': GET_DEFAULT_CONFIG,
582        'exec_prefix': GET_DEFAULT_CONFIG,
583        'base_exec_prefix': GET_DEFAULT_CONFIG,
584        'module_search_paths': GET_DEFAULT_CONFIG,
585        'module_search_paths_set': True,
586        'platlibdir': sys.platlibdir,
587        'stdlib_dir': GET_DEFAULT_CONFIG,
588
589        'site_import': True,
590        'bytes_warning': 0,
591        'warn_default_encoding': False,
592        'inspect': False,
593        'interactive': False,
594        'optimization_level': 0,
595        'parser_debug': False,
596        'write_bytecode': True,
597        'verbose': 0,
598        'quiet': False,
599        'user_site_directory': True,
600        'configure_c_stdio': False,
601        'buffered_stdio': True,
602
603        'stdio_encoding': GET_DEFAULT_CONFIG,
604        'stdio_errors': GET_DEFAULT_CONFIG,
605
606        'skip_source_first_line': False,
607        'run_command': None,
608        'run_module': None,
609        'run_filename': None,
610        'sys_path_0': None,
611
612        '_install_importlib': True,
613        'check_hash_pycs_mode': 'default',
614        'pathconfig_warnings': True,
615        '_init_main': True,
616        'use_frozen_modules': not support.Py_DEBUG,
617        'safe_path': False,
618        '_is_python_build': IGNORE_CONFIG,
619    }
620    if Py_STATS:
621        CONFIG_COMPAT['_pystats'] = 0
622    if support.Py_DEBUG:
623        CONFIG_COMPAT['run_presite'] = None
624    if support.Py_GIL_DISABLED:
625        CONFIG_COMPAT['enable_gil'] = -1
626    if MS_WINDOWS:
627        CONFIG_COMPAT.update({
628            'legacy_windows_stdio': 0,
629        })
630
631    CONFIG_PYTHON = dict(CONFIG_COMPAT,
632        _config_init=API_PYTHON,
633        configure_c_stdio=True,
634        parse_argv=True,
635    )
636    CONFIG_ISOLATED = dict(CONFIG_COMPAT,
637        _config_init=API_ISOLATED,
638        isolated=True,
639        use_environment=False,
640        user_site_directory=False,
641        safe_path=True,
642        dev_mode=False,
643        install_signal_handlers=False,
644        use_hash_seed=False,
645        faulthandler=False,
646        tracemalloc=0,
647        perf_profiling=False,
648        pathconfig_warnings=False,
649    )
650    if MS_WINDOWS:
651        CONFIG_ISOLATED['legacy_windows_stdio'] = 0
652
653    # global config
654    DEFAULT_GLOBAL_CONFIG = {
655        'Py_HasFileSystemDefaultEncoding': 0,
656        'Py_HashRandomizationFlag': 1,
657        '_Py_HasFileSystemDefaultEncodeErrors': 0,
658    }
659    COPY_GLOBAL_PRE_CONFIG = [
660        ('Py_UTF8Mode', 'utf8_mode'),
661    ]
662    COPY_GLOBAL_CONFIG = [
663        # Copy core config to global config for expected values
664        # True means that the core config value is inverted (0 => 1 and 1 => 0)
665        ('Py_BytesWarningFlag', 'bytes_warning'),
666        ('Py_DebugFlag', 'parser_debug'),
667        ('Py_DontWriteBytecodeFlag', 'write_bytecode', True),
668        ('Py_FileSystemDefaultEncodeErrors', 'filesystem_errors'),
669        ('Py_FileSystemDefaultEncoding', 'filesystem_encoding'),
670        ('Py_FrozenFlag', 'pathconfig_warnings', True),
671        ('Py_IgnoreEnvironmentFlag', 'use_environment', True),
672        ('Py_InspectFlag', 'inspect'),
673        ('Py_InteractiveFlag', 'interactive'),
674        ('Py_IsolatedFlag', 'isolated'),
675        ('Py_NoSiteFlag', 'site_import', True),
676        ('Py_NoUserSiteDirectory', 'user_site_directory', True),
677        ('Py_OptimizeFlag', 'optimization_level'),
678        ('Py_QuietFlag', 'quiet'),
679        ('Py_UnbufferedStdioFlag', 'buffered_stdio', True),
680        ('Py_VerboseFlag', 'verbose'),
681    ]
682    if MS_WINDOWS:
683        COPY_GLOBAL_PRE_CONFIG.extend((
684            ('Py_LegacyWindowsFSEncodingFlag', 'legacy_windows_fs_encoding'),
685        ))
686        COPY_GLOBAL_CONFIG.extend((
687            ('Py_LegacyWindowsStdioFlag', 'legacy_windows_stdio'),
688        ))
689
690    EXPECTED_CONFIG = None
691
692    @classmethod
693    def tearDownClass(cls):
694        # clear cache
695        cls.EXPECTED_CONFIG = None
696
697    def main_xoptions(self, xoptions_list):
698        xoptions = {}
699        for opt in xoptions_list:
700            if '=' in opt:
701                key, value = opt.split('=', 1)
702                xoptions[key] = value
703            else:
704                xoptions[opt] = True
705        return xoptions
706
707    def _get_expected_config_impl(self):
708        env = remove_python_envvars()
709        code = textwrap.dedent('''
710            import json
711            import sys
712            import _testinternalcapi
713
714            configs = _testinternalcapi.get_configs()
715
716            data = json.dumps(configs)
717            data = data.encode('utf-8')
718            sys.stdout.buffer.write(data)
719            sys.stdout.buffer.flush()
720        ''')
721
722        # Use -S to not import the site module: get the proper configuration
723        # when test_embed is run from a venv (bpo-35313)
724        args = [sys.executable, '-S', '-c', code]
725        proc = subprocess.run(args, env=env,
726                              stdout=subprocess.PIPE,
727                              stderr=subprocess.PIPE)
728        if proc.returncode:
729            raise Exception(f"failed to get the default config: "
730                            f"stdout={proc.stdout!r} stderr={proc.stderr!r}")
731        stdout = proc.stdout.decode('utf-8')
732        # ignore stderr
733        try:
734            return json.loads(stdout)
735        except json.JSONDecodeError:
736            self.fail(f"fail to decode stdout: {stdout!r}")
737
738    def _get_expected_config(self):
739        cls = InitConfigTests
740        if cls.EXPECTED_CONFIG is None:
741            cls.EXPECTED_CONFIG = self._get_expected_config_impl()
742
743        # get a copy
744        configs = {}
745        for config_key, config_value in cls.EXPECTED_CONFIG.items():
746            config = {}
747            for key, value in config_value.items():
748                if isinstance(value, list):
749                    value = value.copy()
750                config[key] = value
751            configs[config_key] = config
752        return configs
753
754    def get_expected_config(self, expected_preconfig, expected,
755                            env, api, modify_path_cb=None):
756        configs = self._get_expected_config()
757
758        pre_config = configs['pre_config']
759        for key, value in expected_preconfig.items():
760            if value is self.GET_DEFAULT_CONFIG:
761                expected_preconfig[key] = pre_config[key]
762
763        if not expected_preconfig['configure_locale'] or api == API_COMPAT:
764            # there is no easy way to get the locale encoding before
765            # setlocale(LC_CTYPE, "") is called: don't test encodings
766            for key in ('filesystem_encoding', 'filesystem_errors',
767                        'stdio_encoding', 'stdio_errors'):
768                expected[key] = self.IGNORE_CONFIG
769
770        if not expected_preconfig['configure_locale']:
771            # UTF-8 Mode depends on the locale. There is no easy way
772            # to guess if UTF-8 Mode will be enabled or not if the locale
773            # is not configured.
774            expected_preconfig['utf8_mode'] = self.IGNORE_CONFIG
775
776        if expected_preconfig['utf8_mode'] == 1:
777            if expected['filesystem_encoding'] is self.GET_DEFAULT_CONFIG:
778                expected['filesystem_encoding'] = 'utf-8'
779            if expected['filesystem_errors'] is self.GET_DEFAULT_CONFIG:
780                expected['filesystem_errors'] = self.UTF8_MODE_ERRORS
781            if expected['stdio_encoding'] is self.GET_DEFAULT_CONFIG:
782                expected['stdio_encoding'] = 'utf-8'
783            if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG:
784                expected['stdio_errors'] = 'surrogateescape'
785
786        if MS_WINDOWS:
787            default_executable = self.test_exe
788        elif expected['program_name'] is not self.GET_DEFAULT_CONFIG:
789            default_executable = os.path.abspath(expected['program_name'])
790        else:
791            default_executable = os.path.join(os.getcwd(), '_testembed')
792        if expected['executable'] is self.GET_DEFAULT_CONFIG:
793            expected['executable'] = default_executable
794        if expected['base_executable'] is self.GET_DEFAULT_CONFIG:
795            expected['base_executable'] = default_executable
796        if expected['program_name'] is self.GET_DEFAULT_CONFIG:
797            expected['program_name'] = './_testembed'
798
799        config = configs['config']
800        for key, value in expected.items():
801            if value is self.GET_DEFAULT_CONFIG:
802                expected[key] = config[key]
803
804        if expected['module_search_paths'] is not self.IGNORE_CONFIG:
805            pythonpath_env = expected['pythonpath_env']
806            if pythonpath_env is not None:
807                paths = pythonpath_env.split(os.path.pathsep)
808                expected['module_search_paths'] = [*paths, *expected['module_search_paths']]
809            if modify_path_cb is not None:
810                expected['module_search_paths'] = expected['module_search_paths'].copy()
811                modify_path_cb(expected['module_search_paths'])
812
813        for key in self.COPY_PRE_CONFIG:
814            if key not in expected_preconfig:
815                expected_preconfig[key] = expected[key]
816
817    def check_pre_config(self, configs, expected):
818        pre_config = dict(configs['pre_config'])
819        for key, value in list(expected.items()):
820            if value is self.IGNORE_CONFIG:
821                pre_config.pop(key, None)
822                del expected[key]
823        self.assertEqual(pre_config, expected)
824
825    def check_config(self, configs, expected):
826        config = dict(configs['config'])
827        if MS_WINDOWS:
828            value = config.get(key := 'program_name')
829            if value and isinstance(value, str):
830                value = value[:len(value.lower().removesuffix('.exe'))]
831                if debug_build(sys.executable):
832                    value = value[:len(value.lower().removesuffix('_d'))]
833                config[key] = value
834        for key, value in list(expected.items()):
835            if value is self.IGNORE_CONFIG:
836                config.pop(key, None)
837                del expected[key]
838            # Resolve bool/int mismatches to reduce noise in diffs
839            if isinstance(value, (bool, int)) and isinstance(config.get(key), (bool, int)):
840                expected[key] = type(config[key])(expected[key])
841        self.assertEqual(config, expected)
842
843    def check_global_config(self, configs):
844        pre_config = configs['pre_config']
845        config = configs['config']
846
847        expected = dict(self.DEFAULT_GLOBAL_CONFIG)
848        for item in self.COPY_GLOBAL_CONFIG:
849            if len(item) == 3:
850                global_key, core_key, opposite = item
851                expected[global_key] = 0 if config[core_key] else 1
852            else:
853                global_key, core_key = item
854                expected[global_key] = config[core_key]
855        for item in self.COPY_GLOBAL_PRE_CONFIG:
856            if len(item) == 3:
857                global_key, core_key, opposite = item
858                expected[global_key] = 0 if pre_config[core_key] else 1
859            else:
860                global_key, core_key = item
861                expected[global_key] = pre_config[core_key]
862
863        self.assertEqual(configs['global_config'], expected)
864
865    def check_all_configs(self, testname, expected_config=None,
866                          expected_preconfig=None,
867                          modify_path_cb=None,
868                          stderr=None, *, api, preconfig_api=None,
869                          env=None, ignore_stderr=False, cwd=None):
870        new_env = remove_python_envvars()
871        if env is not None:
872            new_env.update(env)
873        env = new_env
874
875        if preconfig_api is None:
876            preconfig_api = api
877        if preconfig_api == API_ISOLATED:
878            default_preconfig = self.PRE_CONFIG_ISOLATED
879        elif preconfig_api == API_PYTHON:
880            default_preconfig = self.PRE_CONFIG_PYTHON
881        else:
882            default_preconfig = self.PRE_CONFIG_COMPAT
883        if expected_preconfig is None:
884            expected_preconfig = {}
885        expected_preconfig = dict(default_preconfig, **expected_preconfig)
886
887        if expected_config is None:
888            expected_config = {}
889
890        if api == API_PYTHON:
891            default_config = self.CONFIG_PYTHON
892        elif api == API_ISOLATED:
893            default_config = self.CONFIG_ISOLATED
894        else:
895            default_config = self.CONFIG_COMPAT
896        expected_config = dict(default_config, **expected_config)
897
898        self.get_expected_config(expected_preconfig,
899                                 expected_config,
900                                 env,
901                                 api, modify_path_cb)
902
903        out, err = self.run_embedded_interpreter(testname,
904                                                 env=env, cwd=cwd)
905        if stderr is None and not expected_config['verbose']:
906            stderr = ""
907        if stderr is not None and not ignore_stderr:
908            self.assertEqual(err.rstrip(), stderr)
909        try:
910            configs = json.loads(out)
911        except json.JSONDecodeError:
912            self.fail(f"fail to decode stdout: {out!r}")
913
914        self.check_pre_config(configs, expected_preconfig)
915        self.check_config(configs, expected_config)
916        self.check_global_config(configs)
917        return configs
918
919    def test_init_default_config(self):
920        self.check_all_configs("test_init_initialize_config", api=API_COMPAT)
921
922    def test_preinit_compat_config(self):
923        self.check_all_configs("test_preinit_compat_config", api=API_COMPAT)
924
925    def test_init_compat_config(self):
926        self.check_all_configs("test_init_compat_config", api=API_COMPAT)
927
928    def test_init_global_config(self):
929        preconfig = {
930            'utf8_mode': 1,
931        }
932        config = {
933            'program_name': './globalvar',
934            'site_import': 0,
935            'bytes_warning': 1,
936            'warnoptions': ['default::BytesWarning'],
937            'inspect': 1,
938            'interactive': 1,
939            'optimization_level': 2,
940            'write_bytecode': 0,
941            'verbose': 1,
942            'quiet': 1,
943            'buffered_stdio': 0,
944
945            'user_site_directory': 0,
946            'pathconfig_warnings': 0,
947        }
948        self.check_all_configs("test_init_global_config", config, preconfig,
949                               api=API_COMPAT)
950
951    def test_init_from_config(self):
952        preconfig = {
953            'allocator': ALLOCATOR_FOR_CONFIG,
954            'utf8_mode': 1,
955        }
956        config = {
957            'install_signal_handlers': False,
958            'use_hash_seed': True,
959            'hash_seed': 123,
960            'tracemalloc': 2,
961            'perf_profiling': False,
962            'import_time': True,
963            'code_debug_ranges': False,
964            'show_ref_count': True,
965            'malloc_stats': True,
966
967            'stdio_encoding': 'iso8859-1',
968            'stdio_errors': 'replace',
969
970            'pycache_prefix': 'conf_pycache_prefix',
971            'program_name': './conf_program_name',
972            'argv': ['-c', 'arg2'],
973            'orig_argv': ['python3',
974                          '-W', 'cmdline_warnoption',
975                          '-X', 'cmdline_xoption',
976                          '-c', 'pass',
977                          'arg2'],
978            'parse_argv': True,
979            'xoptions': [
980                'config_xoption1=3',
981                'config_xoption2=',
982                'config_xoption3',
983                'cmdline_xoption',
984            ],
985            'warnoptions': [
986                'cmdline_warnoption',
987                'default::BytesWarning',
988                'config_warnoption',
989            ],
990            'run_command': 'pass\n',
991
992            'site_import': False,
993            'bytes_warning': 1,
994            'inspect': True,
995            'interactive': True,
996            'optimization_level': 2,
997            'write_bytecode': False,
998            'verbose': 1,
999            'quiet': True,
1000            'configure_c_stdio': True,
1001            'buffered_stdio': False,
1002            'user_site_directory': False,
1003            'faulthandler': True,
1004            'platlibdir': 'my_platlibdir',
1005            'module_search_paths': self.IGNORE_CONFIG,
1006            'safe_path': True,
1007            'int_max_str_digits': 31337,
1008            'cpu_count': 4321,
1009
1010            'check_hash_pycs_mode': 'always',
1011            'pathconfig_warnings': False,
1012        }
1013        if Py_STATS:
1014            config['_pystats'] = 1
1015        self.check_all_configs("test_init_from_config", config, preconfig,
1016                               api=API_COMPAT)
1017
1018    def test_init_compat_env(self):
1019        preconfig = {
1020            'allocator': ALLOCATOR_FOR_CONFIG,
1021        }
1022        config = {
1023            'use_hash_seed': True,
1024            'hash_seed': 42,
1025            'tracemalloc': 2,
1026            'perf_profiling': False,
1027            'import_time': True,
1028            'code_debug_ranges': False,
1029            'malloc_stats': True,
1030            'inspect': True,
1031            'optimization_level': 2,
1032            'pythonpath_env': '/my/path',
1033            'pycache_prefix': 'env_pycache_prefix',
1034            'write_bytecode': False,
1035            'verbose': 1,
1036            'buffered_stdio': False,
1037            'stdio_encoding': 'iso8859-1',
1038            'stdio_errors': 'replace',
1039            'user_site_directory': False,
1040            'faulthandler': True,
1041            'warnoptions': ['EnvVar'],
1042            'platlibdir': 'env_platlibdir',
1043            'module_search_paths': self.IGNORE_CONFIG,
1044            'safe_path': True,
1045            'int_max_str_digits': 4567,
1046        }
1047        if Py_STATS:
1048            config['_pystats'] = 1
1049        self.check_all_configs("test_init_compat_env", config, preconfig,
1050                               api=API_COMPAT)
1051
1052    def test_init_python_env(self):
1053        preconfig = {
1054            'allocator': ALLOCATOR_FOR_CONFIG,
1055            'utf8_mode': 1,
1056        }
1057        config = {
1058            'use_hash_seed': True,
1059            'hash_seed': 42,
1060            'tracemalloc': 2,
1061            'perf_profiling': False,
1062            'import_time': True,
1063            'code_debug_ranges': False,
1064            'malloc_stats': True,
1065            'inspect': True,
1066            'optimization_level': 2,
1067            'pythonpath_env': '/my/path',
1068            'pycache_prefix': 'env_pycache_prefix',
1069            'write_bytecode': False,
1070            'verbose': 1,
1071            'buffered_stdio': False,
1072            'stdio_encoding': 'iso8859-1',
1073            'stdio_errors': 'replace',
1074            'user_site_directory': False,
1075            'faulthandler': True,
1076            'warnoptions': ['EnvVar'],
1077            'platlibdir': 'env_platlibdir',
1078            'module_search_paths': self.IGNORE_CONFIG,
1079            'safe_path': True,
1080            'int_max_str_digits': 4567,
1081        }
1082        if Py_STATS:
1083            config['_pystats'] = True
1084        self.check_all_configs("test_init_python_env", config, preconfig,
1085                               api=API_PYTHON)
1086
1087    def test_init_env_dev_mode(self):
1088        preconfig = dict(allocator=PYMEM_ALLOCATOR_DEBUG)
1089        config = dict(dev_mode=1,
1090                      faulthandler=1,
1091                      warnoptions=['default'])
1092        self.check_all_configs("test_init_env_dev_mode", config, preconfig,
1093                               api=API_COMPAT)
1094
1095    def test_init_env_dev_mode_alloc(self):
1096        preconfig = dict(allocator=ALLOCATOR_FOR_CONFIG)
1097        config = dict(dev_mode=1,
1098                      faulthandler=1,
1099                      warnoptions=['default'])
1100        self.check_all_configs("test_init_env_dev_mode_alloc", config, preconfig,
1101                               api=API_COMPAT)
1102
1103    def test_init_dev_mode(self):
1104        preconfig = {
1105            'allocator': PYMEM_ALLOCATOR_DEBUG,
1106        }
1107        config = {
1108            'faulthandler': True,
1109            'dev_mode': True,
1110            'warnoptions': ['default'],
1111        }
1112        self.check_all_configs("test_init_dev_mode", config, preconfig,
1113                               api=API_PYTHON)
1114
1115    def test_preinit_parse_argv(self):
1116        # Pre-initialize implicitly using argv: make sure that -X dev
1117        # is used to configure the allocation in preinitialization
1118        preconfig = {
1119            'allocator': PYMEM_ALLOCATOR_DEBUG,
1120        }
1121        config = {
1122            'argv': ['script.py'],
1123            'orig_argv': ['python3', '-X', 'dev', '-P', 'script.py'],
1124            'run_filename': os.path.abspath('script.py'),
1125            'dev_mode': True,
1126            'faulthandler': True,
1127            'warnoptions': ['default'],
1128            'xoptions': ['dev'],
1129            'safe_path': True,
1130        }
1131        self.check_all_configs("test_preinit_parse_argv", config, preconfig,
1132                               api=API_PYTHON)
1133
1134    def test_preinit_dont_parse_argv(self):
1135        # -X dev must be ignored by isolated preconfiguration
1136        preconfig = {
1137            'isolated': 0,
1138        }
1139        argv = ["python3",
1140               "-E", "-I", "-P",
1141               "-X", "dev",
1142               "-X", "utf8",
1143               "script.py"]
1144        config = {
1145            'argv': argv,
1146            'orig_argv': argv,
1147            'isolated': 0,
1148        }
1149        self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig,
1150                               api=API_ISOLATED)
1151
1152    def test_init_isolated_flag(self):
1153        config = {
1154            'isolated': True,
1155            'safe_path': True,
1156            'use_environment': False,
1157            'user_site_directory': False,
1158        }
1159        self.check_all_configs("test_init_isolated_flag", config, api=API_PYTHON)
1160
1161    def test_preinit_isolated1(self):
1162        # _PyPreConfig.isolated=1, _PyCoreConfig.isolated not set
1163        config = {
1164            'isolated': True,
1165            'safe_path': True,
1166            'use_environment': False,
1167            'user_site_directory': False,
1168        }
1169        self.check_all_configs("test_preinit_isolated1", config, api=API_COMPAT)
1170
1171    def test_preinit_isolated2(self):
1172        # _PyPreConfig.isolated=0, _PyCoreConfig.isolated=1
1173        config = {
1174            'isolated': True,
1175            'safe_path': True,
1176            'use_environment': False,
1177            'user_site_directory': False,
1178        }
1179        self.check_all_configs("test_preinit_isolated2", config, api=API_COMPAT)
1180
1181    def test_preinit_isolated_config(self):
1182        self.check_all_configs("test_preinit_isolated_config", api=API_ISOLATED)
1183
1184    def test_init_isolated_config(self):
1185        self.check_all_configs("test_init_isolated_config", api=API_ISOLATED)
1186
1187    def test_preinit_python_config(self):
1188        self.check_all_configs("test_preinit_python_config", api=API_PYTHON)
1189
1190    def test_init_python_config(self):
1191        self.check_all_configs("test_init_python_config", api=API_PYTHON)
1192
1193    def test_init_dont_configure_locale(self):
1194        # _PyPreConfig.configure_locale=0
1195        preconfig = {
1196            'configure_locale': 0,
1197            'coerce_c_locale': 0,
1198        }
1199        self.check_all_configs("test_init_dont_configure_locale", {}, preconfig,
1200                               api=API_PYTHON)
1201
1202    @unittest.skip('as of 3.11 this test no longer works because '
1203                   'path calculations do not occur on read')
1204    def test_init_read_set(self):
1205        config = {
1206            'program_name': './init_read_set',
1207            'executable': 'my_executable',
1208            'base_executable': 'my_executable',
1209        }
1210        def modify_path(path):
1211            path.insert(1, "test_path_insert1")
1212            path.append("test_path_append")
1213        self.check_all_configs("test_init_read_set", config,
1214                               api=API_PYTHON,
1215                               modify_path_cb=modify_path)
1216
1217    def test_init_sys_add(self):
1218        config = {
1219            'faulthandler': 1,
1220            'xoptions': [
1221                'config_xoption',
1222                'cmdline_xoption',
1223                'sysadd_xoption',
1224                'faulthandler',
1225            ],
1226            'warnoptions': [
1227                'ignore:::cmdline_warnoption',
1228                'ignore:::sysadd_warnoption',
1229                'ignore:::config_warnoption',
1230            ],
1231            'orig_argv': ['python3',
1232                          '-W', 'ignore:::cmdline_warnoption',
1233                          '-X', 'cmdline_xoption'],
1234        }
1235        self.check_all_configs("test_init_sys_add", config, api=API_PYTHON)
1236
1237    def test_init_run_main(self):
1238        code = ('import _testinternalcapi, json; '
1239                'print(json.dumps(_testinternalcapi.get_configs()))')
1240        config = {
1241            'argv': ['-c', 'arg2'],
1242            'orig_argv': ['python3', '-c', code, 'arg2'],
1243            'program_name': './python3',
1244            'run_command': code + '\n',
1245            'parse_argv': True,
1246            'sys_path_0': '',
1247        }
1248        self.check_all_configs("test_init_run_main", config, api=API_PYTHON)
1249
1250    def test_init_main(self):
1251        code = ('import _testinternalcapi, json; '
1252                'print(json.dumps(_testinternalcapi.get_configs()))')
1253        config = {
1254            'argv': ['-c', 'arg2'],
1255            'orig_argv': ['python3',
1256                          '-c', code,
1257                          'arg2'],
1258            'program_name': './python3',
1259            'run_command': code + '\n',
1260            'parse_argv': True,
1261            '_init_main': 0,
1262            'sys_path_0': '',
1263        }
1264        self.check_all_configs("test_init_main", config,
1265                               api=API_PYTHON,
1266                               stderr="Run Python code before _Py_InitializeMain")
1267
1268    def test_init_parse_argv(self):
1269        config = {
1270            'parse_argv': True,
1271            'argv': ['-c', 'arg1', '-v', 'arg3'],
1272            'orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
1273            'program_name': './argv0',
1274            'run_command': 'pass\n',
1275            'use_environment': False,
1276        }
1277        self.check_all_configs("test_init_parse_argv", config, api=API_PYTHON)
1278
1279    def test_init_dont_parse_argv(self):
1280        pre_config = {
1281            'parse_argv': 0,
1282        }
1283        config = {
1284            'parse_argv': False,
1285            'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
1286            'orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
1287            'program_name': './argv0',
1288        }
1289        self.check_all_configs("test_init_dont_parse_argv", config, pre_config,
1290                               api=API_PYTHON)
1291
1292    def default_program_name(self, config):
1293        if MS_WINDOWS:
1294            program_name = 'python'
1295            executable = self.test_exe
1296        else:
1297            program_name = 'python3'
1298            if MACOS:
1299                executable = self.test_exe
1300            else:
1301                executable = shutil.which(program_name) or ''
1302        config.update({
1303            'program_name': program_name,
1304            'base_executable': executable,
1305            'executable': executable,
1306        })
1307
1308    def test_init_setpath(self):
1309        # Test Py_SetPath()
1310        config = self._get_expected_config()
1311        paths = config['config']['module_search_paths']
1312
1313        config = {
1314            'module_search_paths': paths,
1315            'prefix': '',
1316            'base_prefix': '',
1317            'exec_prefix': '',
1318            'base_exec_prefix': '',
1319             # The current getpath.c doesn't determine the stdlib dir
1320             # in this case.
1321            'stdlib_dir': '',
1322        }
1323        self.default_program_name(config)
1324        env = {'TESTPATH': os.path.pathsep.join(paths)}
1325
1326        self.check_all_configs("test_init_setpath", config,
1327                               api=API_COMPAT, env=env,
1328                               ignore_stderr=True)
1329
1330    def test_init_setpath_config(self):
1331        # Test Py_SetPath() with PyConfig
1332        config = self._get_expected_config()
1333        paths = config['config']['module_search_paths']
1334
1335        config = {
1336            # set by Py_SetPath()
1337            'module_search_paths': paths,
1338            'prefix': '',
1339            'base_prefix': '',
1340            'exec_prefix': '',
1341            'base_exec_prefix': '',
1342             # The current getpath.c doesn't determine the stdlib dir
1343             # in this case.
1344            'stdlib_dir': '',
1345            'use_frozen_modules': not support.Py_DEBUG,
1346            # overridden by PyConfig
1347            'program_name': 'conf_program_name',
1348            'base_executable': 'conf_executable',
1349            'executable': 'conf_executable',
1350        }
1351        env = {'TESTPATH': os.path.pathsep.join(paths)}
1352        self.check_all_configs("test_init_setpath_config", config,
1353                               api=API_PYTHON, env=env, ignore_stderr=True)
1354
1355    def module_search_paths(self, prefix=None, exec_prefix=None):
1356        config = self._get_expected_config()
1357        if prefix is None:
1358            prefix = config['config']['prefix']
1359        if exec_prefix is None:
1360            exec_prefix = config['config']['prefix']
1361        if MS_WINDOWS:
1362            return config['config']['module_search_paths']
1363        else:
1364            ver = sys.version_info
1365            return [
1366                os.path.join(prefix, sys.platlibdir,
1367                             f'python{ver.major}{ver.minor}{ABI_THREAD}.zip'),
1368                os.path.join(prefix, sys.platlibdir,
1369                             f'python{ver.major}.{ver.minor}{ABI_THREAD}'),
1370                os.path.join(exec_prefix, sys.platlibdir,
1371                             f'python{ver.major}.{ver.minor}{ABI_THREAD}', 'lib-dynload'),
1372            ]
1373
1374    @contextlib.contextmanager
1375    def tmpdir_with_python(self, subdir=None):
1376        # Temporary directory with a copy of the Python program
1377        with tempfile.TemporaryDirectory() as tmpdir:
1378            # bpo-38234: On macOS and FreeBSD, the temporary directory
1379            # can be symbolic link. For example, /tmp can be a symbolic link
1380            # to /var/tmp. Call realpath() to resolve all symbolic links.
1381            tmpdir = os.path.realpath(tmpdir)
1382            if subdir:
1383                tmpdir = os.path.normpath(os.path.join(tmpdir, subdir))
1384                os.makedirs(tmpdir)
1385
1386            if MS_WINDOWS:
1387                # Copy pythonXY.dll (or pythonXY_d.dll)
1388                import fnmatch
1389                exedir = os.path.dirname(self.test_exe)
1390                for f in os.listdir(exedir):
1391                    if fnmatch.fnmatch(f, '*.dll'):
1392                        shutil.copyfile(os.path.join(exedir, f), os.path.join(tmpdir, f))
1393
1394            # Copy Python program
1395            exec_copy = os.path.join(tmpdir, os.path.basename(self.test_exe))
1396            shutil.copyfile(self.test_exe, exec_copy)
1397            shutil.copystat(self.test_exe, exec_copy)
1398            self.test_exe = exec_copy
1399
1400            yield tmpdir
1401
1402    def test_init_setpythonhome(self):
1403        # Test Py_SetPythonHome(home) with PYTHONPATH env var
1404        config = self._get_expected_config()
1405        paths = config['config']['module_search_paths']
1406        paths_str = os.path.pathsep.join(paths)
1407
1408        for path in paths:
1409            if not os.path.isdir(path):
1410                continue
1411            if os.path.exists(os.path.join(path, 'os.py')):
1412                home = os.path.dirname(path)
1413                break
1414        else:
1415            self.fail(f"Unable to find home in {paths!r}")
1416
1417        prefix = exec_prefix = home
1418        if MS_WINDOWS:
1419            stdlib = os.path.join(home, "Lib")
1420            # Because we are specifying 'home', module search paths
1421            # are fairly static
1422            expected_paths = [paths[0], os.path.join(home, 'DLLs'), stdlib]
1423        else:
1424            version = f'{sys.version_info.major}.{sys.version_info.minor}'
1425            stdlib = os.path.join(home, sys.platlibdir, f'python{version}{ABI_THREAD}')
1426            expected_paths = self.module_search_paths(prefix=home, exec_prefix=home)
1427
1428        config = {
1429            'home': home,
1430            'module_search_paths': expected_paths,
1431            'prefix': prefix,
1432            'base_prefix': prefix,
1433            'exec_prefix': exec_prefix,
1434            'base_exec_prefix': exec_prefix,
1435            'pythonpath_env': paths_str,
1436            'stdlib_dir': stdlib,
1437        }
1438        self.default_program_name(config)
1439        env = {'TESTHOME': home, 'PYTHONPATH': paths_str}
1440        self.check_all_configs("test_init_setpythonhome", config,
1441                               api=API_COMPAT, env=env)
1442
1443    def test_init_is_python_build_with_home(self):
1444        # Test _Py_path_config._is_python_build configuration (gh-91985)
1445        config = self._get_expected_config()
1446        paths = config['config']['module_search_paths']
1447        paths_str = os.path.pathsep.join(paths)
1448
1449        for path in paths:
1450            if not os.path.isdir(path):
1451                continue
1452            if os.path.exists(os.path.join(path, 'os.py')):
1453                home = os.path.dirname(path)
1454                break
1455        else:
1456            self.fail(f"Unable to find home in {paths!r}")
1457
1458        prefix = exec_prefix = home
1459        if MS_WINDOWS:
1460            stdlib = os.path.join(home, "Lib")
1461            # Because we are specifying 'home', module search paths
1462            # are fairly static
1463            expected_paths = [paths[0], os.path.join(home, 'DLLs'), stdlib]
1464        else:
1465            version = f'{sys.version_info.major}.{sys.version_info.minor}'
1466            stdlib = os.path.join(home, sys.platlibdir, f'python{version}{ABI_THREAD}')
1467            expected_paths = self.module_search_paths(prefix=home, exec_prefix=home)
1468
1469        config = {
1470            'home': home,
1471            'module_search_paths': expected_paths,
1472            'prefix': prefix,
1473            'base_prefix': prefix,
1474            'exec_prefix': exec_prefix,
1475            'base_exec_prefix': exec_prefix,
1476            'pythonpath_env': paths_str,
1477            'stdlib_dir': stdlib,
1478        }
1479        # The code above is taken from test_init_setpythonhome()
1480        env = {'TESTHOME': home, 'PYTHONPATH': paths_str}
1481
1482        env['NEGATIVE_ISPYTHONBUILD'] = '1'
1483        config['_is_python_build'] = 0
1484        self.check_all_configs("test_init_is_python_build", config,
1485                               api=API_COMPAT, env=env)
1486
1487        env['NEGATIVE_ISPYTHONBUILD'] = '0'
1488        config['_is_python_build'] = 1
1489        exedir = os.path.dirname(sys.executable)
1490        with open(os.path.join(exedir, 'pybuilddir.txt'), encoding='utf8') as f:
1491            expected_paths[1 if MS_WINDOWS else 2] = os.path.normpath(
1492                os.path.join(exedir, f'{f.read()}\n$'.splitlines()[0]))
1493        if not MS_WINDOWS:
1494            # PREFIX (default) is set when running in build directory
1495            prefix = exec_prefix = sys.prefix
1496            # stdlib calculation (/Lib) is not yet supported
1497            expected_paths[0] = self.module_search_paths(prefix=prefix)[0]
1498            config.update(prefix=prefix, base_prefix=prefix,
1499                          exec_prefix=exec_prefix, base_exec_prefix=exec_prefix)
1500        self.check_all_configs("test_init_is_python_build", config,
1501                               api=API_COMPAT, env=env)
1502
1503    def copy_paths_by_env(self, config):
1504        all_configs = self._get_expected_config()
1505        paths = all_configs['config']['module_search_paths']
1506        paths_str = os.path.pathsep.join(paths)
1507        config['pythonpath_env'] = paths_str
1508        env = {'PYTHONPATH': paths_str}
1509        return env
1510
1511    @unittest.skipIf(MS_WINDOWS, 'See test_init_pybuilddir_win32')
1512    def test_init_pybuilddir(self):
1513        # Test path configuration with pybuilddir.txt configuration file
1514
1515        with self.tmpdir_with_python() as tmpdir:
1516            # pybuilddir.txt is a sub-directory relative to the current
1517            # directory (tmpdir)
1518            vpath = sysconfig.get_config_var("VPATH") or ''
1519            subdir = 'libdir'
1520            libdir = os.path.join(tmpdir, subdir)
1521            # The stdlib dir is dirname(executable) + VPATH + 'Lib'
1522            stdlibdir = os.path.normpath(os.path.join(tmpdir, vpath, 'Lib'))
1523            os.mkdir(libdir)
1524
1525            filename = os.path.join(tmpdir, 'pybuilddir.txt')
1526            with open(filename, "w", encoding="utf8") as fp:
1527                fp.write(subdir)
1528
1529            module_search_paths = self.module_search_paths()
1530            module_search_paths[-2] = stdlibdir
1531            module_search_paths[-1] = libdir
1532
1533            executable = self.test_exe
1534            config = {
1535                'base_exec_prefix': sysconfig.get_config_var("exec_prefix"),
1536                'base_prefix': sysconfig.get_config_var("prefix"),
1537                'base_executable': executable,
1538                'executable': executable,
1539                'module_search_paths': module_search_paths,
1540                'stdlib_dir': stdlibdir,
1541            }
1542            env = self.copy_paths_by_env(config)
1543            self.check_all_configs("test_init_compat_config", config,
1544                                   api=API_COMPAT, env=env,
1545                                   ignore_stderr=True, cwd=tmpdir)
1546
1547    @unittest.skipUnless(MS_WINDOWS, 'See test_init_pybuilddir')
1548    def test_init_pybuilddir_win32(self):
1549        # Test path configuration with pybuilddir.txt configuration file
1550
1551        vpath = sysconfig.get_config_var("VPATH")
1552        subdir = r'PCbuild\arch'
1553        if os.path.normpath(vpath).count(os.sep) == 2:
1554            subdir = os.path.join(subdir, 'instrumented')
1555
1556        with self.tmpdir_with_python(subdir) as tmpdir:
1557            # The prefix is dirname(executable) + VPATH
1558            prefix = os.path.normpath(os.path.join(tmpdir, vpath))
1559            # The stdlib dir is dirname(executable) + VPATH + 'Lib'
1560            stdlibdir = os.path.normpath(os.path.join(tmpdir, vpath, 'Lib'))
1561
1562            filename = os.path.join(tmpdir, 'pybuilddir.txt')
1563            with open(filename, "w", encoding="utf8") as fp:
1564                fp.write(tmpdir)
1565
1566            module_search_paths = self.module_search_paths()
1567            module_search_paths[-3] = os.path.join(tmpdir, os.path.basename(module_search_paths[-3]))
1568            module_search_paths[-2] = tmpdir
1569            module_search_paths[-1] = stdlibdir
1570
1571            executable = self.test_exe
1572            config = {
1573                'base_exec_prefix': prefix,
1574                'base_prefix': prefix,
1575                'base_executable': executable,
1576                'executable': executable,
1577                'prefix': prefix,
1578                'exec_prefix': prefix,
1579                'module_search_paths': module_search_paths,
1580                'stdlib_dir': stdlibdir,
1581            }
1582            env = self.copy_paths_by_env(config)
1583            self.check_all_configs("test_init_compat_config", config,
1584                                   api=API_COMPAT, env=env,
1585                                   ignore_stderr=False, cwd=tmpdir)
1586
1587    def test_init_pyvenv_cfg(self):
1588        # Test path configuration with pyvenv.cfg configuration file
1589
1590        with self.tmpdir_with_python() as tmpdir, \
1591             tempfile.TemporaryDirectory() as pyvenv_home:
1592            ver = sys.version_info
1593
1594            if not MS_WINDOWS:
1595                lib_dynload = os.path.join(pyvenv_home,
1596                                           sys.platlibdir,
1597                                           f'python{ver.major}.{ver.minor}{ABI_THREAD}',
1598                                           'lib-dynload')
1599                os.makedirs(lib_dynload)
1600            else:
1601                lib_folder = os.path.join(pyvenv_home, 'Lib')
1602                os.makedirs(lib_folder)
1603                # getpath.py uses Lib\os.py as the LANDMARK
1604                shutil.copyfile(
1605                    os.path.join(support.STDLIB_DIR, 'os.py'),
1606                    os.path.join(lib_folder, 'os.py'),
1607                )
1608
1609            filename = os.path.join(tmpdir, 'pyvenv.cfg')
1610            with open(filename, "w", encoding="utf8") as fp:
1611                print("home = %s" % pyvenv_home, file=fp)
1612                print("include-system-site-packages = false", file=fp)
1613
1614            paths = self.module_search_paths()
1615            if not MS_WINDOWS:
1616                paths[-1] = lib_dynload
1617            else:
1618                paths = [
1619                    os.path.join(tmpdir, os.path.basename(paths[0])),
1620                    pyvenv_home,
1621                    os.path.join(pyvenv_home, "Lib"),
1622                ]
1623
1624            executable = self.test_exe
1625            base_executable = os.path.join(pyvenv_home, os.path.basename(executable))
1626            exec_prefix = pyvenv_home
1627            config = {
1628                'base_prefix': sysconfig.get_config_var("prefix"),
1629                'base_exec_prefix': exec_prefix,
1630                'exec_prefix': exec_prefix,
1631                'base_executable': base_executable,
1632                'executable': executable,
1633                'module_search_paths': paths,
1634            }
1635            if MS_WINDOWS:
1636                config['base_prefix'] = pyvenv_home
1637                config['prefix'] = pyvenv_home
1638                config['stdlib_dir'] = os.path.join(pyvenv_home, 'Lib')
1639                config['use_frozen_modules'] = int(not support.Py_DEBUG)
1640            else:
1641                # cannot reliably assume stdlib_dir here because it
1642                # depends too much on our build. But it ought to be found
1643                config['stdlib_dir'] = self.IGNORE_CONFIG
1644                config['use_frozen_modules'] = int(not support.Py_DEBUG)
1645
1646            env = self.copy_paths_by_env(config)
1647            self.check_all_configs("test_init_compat_config", config,
1648                                   api=API_COMPAT, env=env,
1649                                   ignore_stderr=True, cwd=tmpdir)
1650
1651    @unittest.skipUnless(MS_WINDOWS, 'specific to Windows')
1652    def test_getpath_abspath_win32(self):
1653        # Check _Py_abspath() is passed a backslashed path not to fall back to
1654        # GetFullPathNameW() on startup, which (re-)normalizes the path overly.
1655        # Currently, _Py_normpath() doesn't trim trailing dots and spaces.
1656        CASES = [
1657            ("C:/a. . .",  "C:\\a. . ."),
1658            ("C:\\a. . .", "C:\\a. . ."),
1659            ("\\\\?\\C:////a////b. . .", "\\\\?\\C:\\a\\b. . ."),
1660            ("//a/b/c. . .", "\\\\a\\b\\c. . ."),
1661            ("\\\\a\\b\\c. . .", "\\\\a\\b\\c. . ."),
1662            ("a. . .", f"{os.getcwd()}\\a"),  # relpath gets fully normalized
1663        ]
1664        out, err = self.run_embedded_interpreter(
1665            "test_init_initialize_config",
1666            env={**remove_python_envvars(),
1667                 "PYTHONPATH": os.path.pathsep.join(c[0] for c in CASES)}
1668        )
1669        self.assertEqual(err, "")
1670        try:
1671            out = json.loads(out)
1672        except json.JSONDecodeError:
1673            self.fail(f"fail to decode stdout: {out!r}")
1674
1675        results = out['config']["module_search_paths"]
1676        for (_, expected), result in zip(CASES, results):
1677            self.assertEqual(result, expected)
1678
1679    def test_global_pathconfig(self):
1680        # Test C API functions getting the path configuration:
1681        #
1682        # - Py_GetExecPrefix()
1683        # - Py_GetPath()
1684        # - Py_GetPrefix()
1685        # - Py_GetProgramFullPath()
1686        # - Py_GetProgramName()
1687        # - Py_GetPythonHome()
1688        #
1689        # The global path configuration (_Py_path_config) must be a copy
1690        # of the path configuration of PyInterpreter.config (PyConfig).
1691        ctypes = import_helper.import_module('ctypes')
1692
1693        def get_func(name):
1694            func = getattr(ctypes.pythonapi, name)
1695            func.argtypes = ()
1696            func.restype = ctypes.c_wchar_p
1697            return func
1698
1699        Py_GetPath = get_func('Py_GetPath')
1700        Py_GetPrefix = get_func('Py_GetPrefix')
1701        Py_GetExecPrefix = get_func('Py_GetExecPrefix')
1702        Py_GetProgramName = get_func('Py_GetProgramName')
1703        Py_GetProgramFullPath = get_func('Py_GetProgramFullPath')
1704        Py_GetPythonHome = get_func('Py_GetPythonHome')
1705
1706        config = _testinternalcapi.get_configs()['config']
1707
1708        self.assertEqual(Py_GetPath().split(os.path.pathsep),
1709                         config['module_search_paths'])
1710        self.assertEqual(Py_GetPrefix(), config['prefix'])
1711        self.assertEqual(Py_GetExecPrefix(), config['exec_prefix'])
1712        self.assertEqual(Py_GetProgramName(), config['program_name'])
1713        self.assertEqual(Py_GetProgramFullPath(), config['executable'])
1714        self.assertEqual(Py_GetPythonHome(), config['home'])
1715
1716    def test_init_warnoptions(self):
1717        # lowest to highest priority
1718        warnoptions = [
1719            'ignore:::PyConfig_Insert0',      # PyWideStringList_Insert(0)
1720            'default',                        # PyConfig.dev_mode=1
1721            'ignore:::env1',                  # PYTHONWARNINGS env var
1722            'ignore:::env2',                  # PYTHONWARNINGS env var
1723            'ignore:::cmdline1',              # -W opt command line option
1724            'ignore:::cmdline2',              # -W opt command line option
1725            'default::BytesWarning',          # PyConfig.bytes_warnings=1
1726            'ignore:::PySys_AddWarnOption1',  # PySys_AddWarnOption()
1727            'ignore:::PySys_AddWarnOption2',  # PySys_AddWarnOption()
1728            'ignore:::PyConfig_BeforeRead',   # PyConfig.warnoptions
1729            'ignore:::PyConfig_AfterRead']    # PyWideStringList_Append()
1730        preconfig = dict(allocator=PYMEM_ALLOCATOR_DEBUG)
1731        config = {
1732            'dev_mode': 1,
1733            'faulthandler': 1,
1734            'bytes_warning': 1,
1735            'warnoptions': warnoptions,
1736            'orig_argv': ['python3',
1737                          '-Wignore:::cmdline1',
1738                          '-Wignore:::cmdline2'],
1739        }
1740        self.check_all_configs("test_init_warnoptions", config, preconfig,
1741                               api=API_PYTHON)
1742
1743    def test_init_set_config(self):
1744        config = {
1745            '_init_main': 0,
1746            'bytes_warning': 2,
1747            'warnoptions': ['error::BytesWarning'],
1748        }
1749        self.check_all_configs("test_init_set_config", config,
1750                               api=API_ISOLATED)
1751
1752    def test_get_argc_argv(self):
1753        self.run_embedded_interpreter("test_get_argc_argv")
1754        # ignore output
1755
1756    def test_init_use_frozen_modules(self):
1757        tests = {
1758            ('=on', True),
1759            ('=off', False),
1760            ('=', True),
1761            ('', True),
1762        }
1763        for raw, expected in tests:
1764            optval = f'frozen_modules{raw}'
1765            config = {
1766                'parse_argv': True,
1767                'argv': ['-c'],
1768                'orig_argv': ['./argv0', '-X', optval, '-c', 'pass'],
1769                'program_name': './argv0',
1770                'run_command': 'pass\n',
1771                'use_environment': True,
1772                'xoptions': [optval],
1773                'use_frozen_modules': expected,
1774            }
1775            env = {'TESTFROZEN': raw[1:]} if raw else None
1776            with self.subTest(repr(raw)):
1777                self.check_all_configs("test_init_use_frozen_modules", config,
1778                                       api=API_PYTHON, env=env)
1779
1780    def test_init_main_interpreter_settings(self):
1781        OBMALLOC = 1<<5
1782        EXTENSIONS = 1<<8
1783        THREADS = 1<<10
1784        DAEMON_THREADS = 1<<11
1785        FORK = 1<<15
1786        EXEC = 1<<16
1787        expected = {
1788            # All optional features should be enabled.
1789            'feature_flags':
1790                OBMALLOC | FORK | EXEC | THREADS | DAEMON_THREADS,
1791            'own_gil': True,
1792        }
1793        out, err = self.run_embedded_interpreter(
1794            'test_init_main_interpreter_settings',
1795        )
1796        self.assertEqual(err, '')
1797        try:
1798            out = json.loads(out)
1799        except json.JSONDecodeError:
1800            self.fail(f'fail to decode stdout: {out!r}')
1801
1802        self.assertEqual(out, expected)
1803
1804    @threading_helper.requires_working_threading()
1805    def test_init_in_background_thread(self):
1806        # gh-123022: Check that running Py_Initialize() in a background
1807        # thread doesn't crash.
1808        out, err = self.run_embedded_interpreter("test_init_in_background_thread")
1809        self.assertEqual(err, "")
1810
1811
1812class SetConfigTests(unittest.TestCase):
1813    def test_set_config(self):
1814        # bpo-42260: Test _PyInterpreterState_SetConfig()
1815        import_helper.import_module('_testcapi')
1816        cmd = [sys.executable, '-X', 'utf8', '-I', '-m', 'test._test_embed_set_config']
1817        proc = subprocess.run(cmd,
1818                              stdout=subprocess.PIPE,
1819                              stderr=subprocess.PIPE,
1820                              encoding='utf-8', errors='backslashreplace')
1821        if proc.returncode and support.verbose:
1822            print(proc.stdout)
1823            print(proc.stderr)
1824        self.assertEqual(proc.returncode, 0,
1825                         (proc.returncode, proc.stdout, proc.stderr))
1826
1827
1828class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
1829    def test_open_code_hook(self):
1830        self.run_embedded_interpreter("test_open_code_hook")
1831
1832    def test_audit(self):
1833        self.run_embedded_interpreter("test_audit")
1834
1835    def test_audit_tuple(self):
1836        self.run_embedded_interpreter("test_audit_tuple")
1837
1838    def test_audit_subinterpreter(self):
1839        self.run_embedded_interpreter("test_audit_subinterpreter")
1840
1841    def test_audit_run_command(self):
1842        self.run_embedded_interpreter("test_audit_run_command",
1843                                      timeout=support.SHORT_TIMEOUT,
1844                                      returncode=1)
1845
1846    def test_audit_run_file(self):
1847        self.run_embedded_interpreter("test_audit_run_file",
1848                                      timeout=support.SHORT_TIMEOUT,
1849                                      returncode=1)
1850
1851    def test_audit_run_interactivehook(self):
1852        startup = os.path.join(self.oldcwd, os_helper.TESTFN) + ".py"
1853        with open(startup, "w", encoding="utf-8") as f:
1854            print("import sys", file=f)
1855            print("sys.__interactivehook__ = lambda: None", file=f)
1856        try:
1857            env = {**remove_python_envvars(), "PYTHONSTARTUP": startup}
1858            self.run_embedded_interpreter("test_audit_run_interactivehook",
1859                                          timeout=support.SHORT_TIMEOUT,
1860                                          returncode=10, env=env)
1861        finally:
1862            os.unlink(startup)
1863
1864    def test_audit_run_startup(self):
1865        startup = os.path.join(self.oldcwd, os_helper.TESTFN) + ".py"
1866        with open(startup, "w", encoding="utf-8") as f:
1867            print("pass", file=f)
1868        try:
1869            env = {**remove_python_envvars(), "PYTHONSTARTUP": startup}
1870            self.run_embedded_interpreter("test_audit_run_startup",
1871                                          timeout=support.SHORT_TIMEOUT,
1872                                          returncode=10, env=env)
1873        finally:
1874            os.unlink(startup)
1875
1876    def test_audit_run_stdin(self):
1877        self.run_embedded_interpreter("test_audit_run_stdin",
1878                                      timeout=support.SHORT_TIMEOUT,
1879                                      returncode=1)
1880
1881    def test_get_incomplete_frame(self):
1882        self.run_embedded_interpreter("test_get_incomplete_frame")
1883
1884
1885class MiscTests(EmbeddingTestsMixin, unittest.TestCase):
1886    def test_unicode_id_init(self):
1887        # bpo-42882: Test that _PyUnicode_FromId() works
1888        # when Python is initialized multiples times.
1889        self.run_embedded_interpreter("test_unicode_id_init")
1890
1891    # See bpo-44133
1892    @unittest.skipIf(os.name == 'nt',
1893                     'Py_FrozenMain is not exported on Windows')
1894    @unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
1895    def test_frozenmain(self):
1896        env = dict(os.environ)
1897        env['PYTHONUNBUFFERED'] = '1'
1898        out, err = self.run_embedded_interpreter("test_frozenmain", env=env)
1899        executable = os.path.realpath('./argv0')
1900        expected = textwrap.dedent(f"""
1901            Frozen Hello World
1902            sys.argv ['./argv0', '-E', 'arg1', 'arg2']
1903            config program_name: ./argv0
1904            config executable: {executable}
1905            config use_environment: True
1906            config configure_c_stdio: True
1907            config buffered_stdio: False
1908        """).lstrip()
1909        self.assertEqual(out, expected)
1910
1911    @unittest.skipUnless(support.Py_DEBUG,
1912                         '-X showrefcount requires a Python debug build')
1913    def test_no_memleak(self):
1914        # bpo-1635741: Python must release all memory at exit
1915        tests = (
1916            ('off', 'pass'),
1917            ('on', 'pass'),
1918            ('off', 'import __hello__'),
1919            ('on', 'import __hello__'),
1920        )
1921        for flag, stmt in tests:
1922            xopt = f"frozen_modules={flag}"
1923            cmd = [sys.executable, "-I", "-X", "showrefcount", "-X", xopt, "-c", stmt]
1924            proc = subprocess.run(cmd,
1925                                  stdout=subprocess.PIPE,
1926                                  stderr=subprocess.STDOUT,
1927                                  text=True)
1928            self.assertEqual(proc.returncode, 0)
1929            out = proc.stdout.rstrip()
1930            match = re.match(r'^\[(-?\d+) refs, (-?\d+) blocks\]', out)
1931            if not match:
1932                self.fail(f"unexpected output: {out!a}")
1933            refs = int(match.group(1))
1934            blocks = int(match.group(2))
1935            with self.subTest(frozen_modules=flag, stmt=stmt):
1936                self.assertEqual(refs, 0, out)
1937                self.assertEqual(blocks, 0, out)
1938
1939    @unittest.skipUnless(support.Py_DEBUG,
1940                         '-X presite requires a Python debug build')
1941    def test_presite(self):
1942        cmd = [sys.executable, "-I", "-X", "presite=test.reperf", "-c", "print('cmd')"]
1943        proc = subprocess.run(
1944            cmd,
1945            stdout=subprocess.PIPE,
1946            stderr=subprocess.STDOUT,
1947            text=True,
1948        )
1949        self.assertEqual(proc.returncode, 0)
1950        out = proc.stdout.strip()
1951        self.assertIn("10 times sub", out)
1952        self.assertIn("CPU seconds", out)
1953        self.assertIn("cmd", out)
1954
1955
1956class StdPrinterTests(EmbeddingTestsMixin, unittest.TestCase):
1957    # Test PyStdPrinter_Type which is used by _PySys_SetPreliminaryStderr():
1958    #   "Set up a preliminary stderr printer until we have enough
1959    #    infrastructure for the io module in place."
1960
1961    STDOUT_FD = 1
1962
1963    def create_printer(self, fd):
1964        ctypes = import_helper.import_module('ctypes')
1965        PyFile_NewStdPrinter = ctypes.pythonapi.PyFile_NewStdPrinter
1966        PyFile_NewStdPrinter.argtypes = (ctypes.c_int,)
1967        PyFile_NewStdPrinter.restype = ctypes.py_object
1968        return PyFile_NewStdPrinter(fd)
1969
1970    def test_write(self):
1971        message = "unicode:\xe9-\u20ac-\udc80!\n"
1972
1973        stdout_fd = self.STDOUT_FD
1974        stdout_fd_copy = os.dup(stdout_fd)
1975        self.addCleanup(os.close, stdout_fd_copy)
1976
1977        rfd, wfd = os.pipe()
1978        self.addCleanup(os.close, rfd)
1979        self.addCleanup(os.close, wfd)
1980        try:
1981            # PyFile_NewStdPrinter() only accepts fileno(stdout)
1982            # or fileno(stderr) file descriptor.
1983            os.dup2(wfd, stdout_fd)
1984
1985            printer = self.create_printer(stdout_fd)
1986            printer.write(message)
1987        finally:
1988            os.dup2(stdout_fd_copy, stdout_fd)
1989
1990        data = os.read(rfd, 100)
1991        self.assertEqual(data, message.encode('utf8', 'backslashreplace'))
1992
1993    def test_methods(self):
1994        fd = self.STDOUT_FD
1995        printer = self.create_printer(fd)
1996        self.assertEqual(printer.fileno(), fd)
1997        self.assertEqual(printer.isatty(), os.isatty(fd))
1998        printer.flush()  # noop
1999        printer.close()  # noop
2000
2001    def test_disallow_instantiation(self):
2002        fd = self.STDOUT_FD
2003        printer = self.create_printer(fd)
2004        support.check_disallow_instantiation(self, type(printer))
2005
2006
2007if __name__ == "__main__":
2008    unittest.main()
2009