1# Run the tests in Programs/_testembed.c (tests for the CPython embedding APIs)
2from test import support
3import unittest
4
5from collections import namedtuple
6import contextlib
7import json
8import os
9import re
10import shutil
11import subprocess
12import sys
13import tempfile
14import textwrap
15
16
17MS_WINDOWS = (os.name == 'nt')
18MACOS = (sys.platform == 'darwin')
19
20PYMEM_ALLOCATOR_NOT_SET = 0
21PYMEM_ALLOCATOR_DEBUG = 2
22PYMEM_ALLOCATOR_MALLOC = 3
23
24# _PyCoreConfig_InitCompatConfig()
25API_COMPAT = 1
26# _PyCoreConfig_InitPythonConfig()
27API_PYTHON = 2
28# _PyCoreConfig_InitIsolatedConfig()
29API_ISOLATED = 3
30
31
32def debug_build(program):
33    program = os.path.basename(program)
34    name = os.path.splitext(program)[0]
35    return name.casefold().endswith("_d".casefold())
36
37
38def remove_python_envvars():
39    env = dict(os.environ)
40    # Remove PYTHON* environment variables to get deterministic environment
41    for key in list(env):
42        if key.startswith('PYTHON'):
43            del env[key]
44    return env
45
46
47class EmbeddingTestsMixin:
48    def setUp(self):
49        here = os.path.abspath(__file__)
50        basepath = os.path.dirname(os.path.dirname(os.path.dirname(here)))
51        exename = "_testembed"
52        if MS_WINDOWS:
53            ext = ("_d" if debug_build(sys.executable) else "") + ".exe"
54            exename += ext
55            exepath = os.path.dirname(sys.executable)
56        else:
57            exepath = os.path.join(basepath, "Programs")
58        self.test_exe = exe = os.path.join(exepath, exename)
59        if not os.path.exists(exe):
60            self.skipTest("%r doesn't exist" % exe)
61        # This is needed otherwise we get a fatal error:
62        # "Py_Initialize: Unable to get the locale encoding
63        # LookupError: no codec search functions registered: can't find encoding"
64        self.oldcwd = os.getcwd()
65        os.chdir(basepath)
66
67    def tearDown(self):
68        os.chdir(self.oldcwd)
69
70    def run_embedded_interpreter(self, *args, env=None,
71                                 timeout=None, returncode=0, input=None,
72                                 cwd=None):
73        """Runs a test in the embedded interpreter"""
74        cmd = [self.test_exe]
75        cmd.extend(args)
76        if env is not None and MS_WINDOWS:
77            # Windows requires at least the SYSTEMROOT environment variable to
78            # start Python.
79            env = env.copy()
80            env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
81
82        p = subprocess.Popen(cmd,
83                             stdout=subprocess.PIPE,
84                             stderr=subprocess.PIPE,
85                             universal_newlines=True,
86                             env=env,
87                             cwd=cwd)
88        try:
89            (out, err) = p.communicate(input=input, timeout=timeout)
90        except:
91            p.terminate()
92            p.wait()
93            raise
94        if p.returncode != returncode and support.verbose:
95            print(f"--- {cmd} failed ---")
96            print(f"stdout:\n{out}")
97            print(f"stderr:\n{err}")
98            print(f"------")
99
100        self.assertEqual(p.returncode, returncode,
101                         "bad returncode %d, stderr is %r" %
102                         (p.returncode, err))
103        return out, err
104
105    def run_repeated_init_and_subinterpreters(self):
106        out, err = self.run_embedded_interpreter("test_repeated_init_and_subinterpreters")
107        self.assertEqual(err, "")
108
109        # The output from _testembed looks like this:
110        # --- Pass 0 ---
111        # interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
112        # interp 1 <0x1d4f690>, thread state <0x1d35350>: id(modules) = 139650431165784
113        # interp 2 <0x1d5a690>, thread state <0x1d99ed0>: id(modules) = 139650413140368
114        # interp 3 <0x1d4f690>, thread state <0x1dc3340>: id(modules) = 139650412862200
115        # interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
116        # --- Pass 1 ---
117        # ...
118
119        interp_pat = (r"^interp (\d+) <(0x[\dA-F]+)>, "
120                      r"thread state <(0x[\dA-F]+)>: "
121                      r"id\(modules\) = ([\d]+)$")
122        Interp = namedtuple("Interp", "id interp tstate modules")
123
124        numloops = 0
125        current_run = []
126        for line in out.splitlines():
127            if line == "--- Pass {} ---".format(numloops):
128                self.assertEqual(len(current_run), 0)
129                if support.verbose > 1:
130                    print(line)
131                numloops += 1
132                continue
133
134            self.assertLess(len(current_run), 5)
135            match = re.match(interp_pat, line)
136            if match is None:
137                self.assertRegex(line, interp_pat)
138
139            # Parse the line from the loop.  The first line is the main
140            # interpreter and the 3 afterward are subinterpreters.
141            interp = Interp(*match.groups())
142            if support.verbose > 1:
143                print(interp)
144            self.assertTrue(interp.interp)
145            self.assertTrue(interp.tstate)
146            self.assertTrue(interp.modules)
147            current_run.append(interp)
148
149            # The last line in the loop should be the same as the first.
150            if len(current_run) == 5:
151                main = current_run[0]
152                self.assertEqual(interp, main)
153                yield current_run
154                current_run = []
155
156
157class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
158    def test_subinterps_main(self):
159        for run in self.run_repeated_init_and_subinterpreters():
160            main = run[0]
161
162            self.assertEqual(main.id, '0')
163
164    def test_subinterps_different_ids(self):
165        for run in self.run_repeated_init_and_subinterpreters():
166            main, *subs, _ = run
167
168            mainid = int(main.id)
169            for i, sub in enumerate(subs):
170                self.assertEqual(sub.id, str(mainid + i + 1))
171
172    def test_subinterps_distinct_state(self):
173        for run in self.run_repeated_init_and_subinterpreters():
174            main, *subs, _ = run
175
176            if '0x0' in main:
177                # XXX Fix on Windows (and other platforms): something
178                # is going on with the pointers in Programs/_testembed.c.
179                # interp.interp is 0x0 and interp.modules is the same
180                # between interpreters.
181                raise unittest.SkipTest('platform prints pointers as 0x0')
182
183            for sub in subs:
184                # A new subinterpreter may have the same
185                # PyInterpreterState pointer as a previous one if
186                # the earlier one has already been destroyed.  So
187                # we compare with the main interpreter.  The same
188                # applies to tstate.
189                self.assertNotEqual(sub.interp, main.interp)
190                self.assertNotEqual(sub.tstate, main.tstate)
191                self.assertNotEqual(sub.modules, main.modules)
192
193    def test_forced_io_encoding(self):
194        # Checks forced configuration of embedded interpreter IO streams
195        env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
196        out, err = self.run_embedded_interpreter("test_forced_io_encoding", env=env)
197        if support.verbose > 1:
198            print()
199            print(out)
200            print(err)
201        expected_stream_encoding = "utf-8"
202        expected_errors = "surrogateescape"
203        expected_output = '\n'.join([
204        "--- Use defaults ---",
205        "Expected encoding: default",
206        "Expected errors: default",
207        "stdin: {in_encoding}:{errors}",
208        "stdout: {out_encoding}:{errors}",
209        "stderr: {out_encoding}:backslashreplace",
210        "--- Set errors only ---",
211        "Expected encoding: default",
212        "Expected errors: ignore",
213        "stdin: {in_encoding}:ignore",
214        "stdout: {out_encoding}:ignore",
215        "stderr: {out_encoding}:backslashreplace",
216        "--- Set encoding only ---",
217        "Expected encoding: iso8859-1",
218        "Expected errors: default",
219        "stdin: iso8859-1:{errors}",
220        "stdout: iso8859-1:{errors}",
221        "stderr: iso8859-1:backslashreplace",
222        "--- Set encoding and errors ---",
223        "Expected encoding: iso8859-1",
224        "Expected errors: replace",
225        "stdin: iso8859-1:replace",
226        "stdout: iso8859-1:replace",
227        "stderr: iso8859-1:backslashreplace"])
228        expected_output = expected_output.format(
229                                in_encoding=expected_stream_encoding,
230                                out_encoding=expected_stream_encoding,
231                                errors=expected_errors)
232        # This is useful if we ever trip over odd platform behaviour
233        self.maxDiff = None
234        self.assertEqual(out.strip(), expected_output)
235
236    def test_pre_initialization_api(self):
237        """
238        Checks some key parts of the C-API that need to work before the runtine
239        is initialized (via Py_Initialize()).
240        """
241        env = dict(os.environ, PYTHONPATH=os.pathsep.join(sys.path))
242        out, err = self.run_embedded_interpreter("test_pre_initialization_api", env=env)
243        if MS_WINDOWS:
244            expected_path = self.test_exe
245        else:
246            expected_path = os.path.join(os.getcwd(), "spam")
247        expected_output = f"sys.executable: {expected_path}\n"
248        self.assertIn(expected_output, out)
249        self.assertEqual(err, '')
250
251    def test_pre_initialization_sys_options(self):
252        """
253        Checks that sys.warnoptions and sys._xoptions can be set before the
254        runtime is initialized (otherwise they won't be effective).
255        """
256        env = remove_python_envvars()
257        env['PYTHONPATH'] = os.pathsep.join(sys.path)
258        out, err = self.run_embedded_interpreter(
259                        "test_pre_initialization_sys_options", env=env)
260        expected_output = (
261            "sys.warnoptions: ['once', 'module', 'default']\n"
262            "sys._xoptions: {'not_an_option': '1', 'also_not_an_option': '2'}\n"
263            "warnings.filters[:3]: ['default', 'module', 'once']\n"
264        )
265        self.assertIn(expected_output, out)
266        self.assertEqual(err, '')
267
268    def test_bpo20891(self):
269        """
270        bpo-20891: Calling PyGILState_Ensure in a non-Python thread must not
271        crash.
272        """
273        out, err = self.run_embedded_interpreter("test_bpo20891")
274        self.assertEqual(out, '')
275        self.assertEqual(err, '')
276
277    def test_initialize_twice(self):
278        """
279        bpo-33932: Calling Py_Initialize() twice should do nothing (and not
280        crash!).
281        """
282        out, err = self.run_embedded_interpreter("test_initialize_twice")
283        self.assertEqual(out, '')
284        self.assertEqual(err, '')
285
286    def test_initialize_pymain(self):
287        """
288        bpo-34008: Calling Py_Main() after Py_Initialize() must not fail.
289        """
290        out, err = self.run_embedded_interpreter("test_initialize_pymain")
291        self.assertEqual(out.rstrip(), "Py_Main() after Py_Initialize: sys.argv=['-c', 'arg2']")
292        self.assertEqual(err, '')
293
294    def test_run_main(self):
295        out, err = self.run_embedded_interpreter("test_run_main")
296        self.assertEqual(out.rstrip(), "Py_RunMain(): sys.argv=['-c', 'arg2']")
297        self.assertEqual(err, '')
298
299
300class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
301    maxDiff = 4096
302    UTF8_MODE_ERRORS = ('surrogatepass' if MS_WINDOWS else 'surrogateescape')
303
304    # Marker to read the default configuration: get_default_config()
305    GET_DEFAULT_CONFIG = object()
306
307    # Marker to ignore a configuration parameter
308    IGNORE_CONFIG = object()
309
310    PRE_CONFIG_COMPAT = {
311        '_config_init': API_COMPAT,
312        'allocator': PYMEM_ALLOCATOR_NOT_SET,
313        'parse_argv': 0,
314        'configure_locale': 1,
315        'coerce_c_locale': 0,
316        'coerce_c_locale_warn': 0,
317        'utf8_mode': 0,
318    }
319    if MS_WINDOWS:
320        PRE_CONFIG_COMPAT.update({
321            'legacy_windows_fs_encoding': 0,
322        })
323    PRE_CONFIG_PYTHON = dict(PRE_CONFIG_COMPAT,
324        _config_init=API_PYTHON,
325        parse_argv=1,
326        coerce_c_locale=GET_DEFAULT_CONFIG,
327        utf8_mode=GET_DEFAULT_CONFIG,
328    )
329    PRE_CONFIG_ISOLATED = dict(PRE_CONFIG_COMPAT,
330        _config_init=API_ISOLATED,
331        configure_locale=0,
332        isolated=1,
333        use_environment=0,
334        utf8_mode=0,
335        dev_mode=0,
336        coerce_c_locale=0,
337    )
338
339    COPY_PRE_CONFIG = [
340        'dev_mode',
341        'isolated',
342        'use_environment',
343    ]
344
345    CONFIG_COMPAT = {
346        '_config_init': API_COMPAT,
347        'isolated': 0,
348        'use_environment': 1,
349        'dev_mode': 0,
350        '_use_peg_parser': 1,
351
352        'install_signal_handlers': 1,
353        'use_hash_seed': 0,
354        'hash_seed': 0,
355        'faulthandler': 0,
356        'tracemalloc': 0,
357        'import_time': 0,
358        'show_ref_count': 0,
359        'dump_refs': 0,
360        'malloc_stats': 0,
361
362        'filesystem_encoding': GET_DEFAULT_CONFIG,
363        'filesystem_errors': GET_DEFAULT_CONFIG,
364
365        'pycache_prefix': None,
366        'program_name': GET_DEFAULT_CONFIG,
367        'parse_argv': 0,
368        'argv': [""],
369        '_orig_argv': [],
370
371        'xoptions': [],
372        'warnoptions': [],
373
374        'pythonpath_env': None,
375        'home': None,
376        'executable': GET_DEFAULT_CONFIG,
377        'base_executable': GET_DEFAULT_CONFIG,
378
379        'prefix': GET_DEFAULT_CONFIG,
380        'base_prefix': GET_DEFAULT_CONFIG,
381        'exec_prefix': GET_DEFAULT_CONFIG,
382        'base_exec_prefix': GET_DEFAULT_CONFIG,
383        'module_search_paths': GET_DEFAULT_CONFIG,
384        'platlibdir': sys.platlibdir,
385
386        'site_import': 1,
387        'bytes_warning': 0,
388        'inspect': 0,
389        'interactive': 0,
390        'optimization_level': 0,
391        'parser_debug': 0,
392        'write_bytecode': 1,
393        'verbose': 0,
394        'quiet': 0,
395        'user_site_directory': 1,
396        'configure_c_stdio': 0,
397        'buffered_stdio': 1,
398
399        'stdio_encoding': GET_DEFAULT_CONFIG,
400        'stdio_errors': GET_DEFAULT_CONFIG,
401
402        'skip_source_first_line': 0,
403        'run_command': None,
404        'run_module': None,
405        'run_filename': None,
406
407        '_install_importlib': 1,
408        'check_hash_pycs_mode': 'default',
409        'pathconfig_warnings': 1,
410        '_init_main': 1,
411        '_isolated_interpreter': 0,
412    }
413    if MS_WINDOWS:
414        CONFIG_COMPAT.update({
415            'legacy_windows_stdio': 0,
416        })
417
418    CONFIG_PYTHON = dict(CONFIG_COMPAT,
419        _config_init=API_PYTHON,
420        configure_c_stdio=1,
421        parse_argv=1,
422    )
423    CONFIG_ISOLATED = dict(CONFIG_COMPAT,
424        _config_init=API_ISOLATED,
425        isolated=1,
426        use_environment=0,
427        user_site_directory=0,
428        dev_mode=0,
429        install_signal_handlers=0,
430        use_hash_seed=0,
431        faulthandler=0,
432        tracemalloc=0,
433        pathconfig_warnings=0,
434    )
435    if MS_WINDOWS:
436        CONFIG_ISOLATED['legacy_windows_stdio'] = 0
437
438    # global config
439    DEFAULT_GLOBAL_CONFIG = {
440        'Py_HasFileSystemDefaultEncoding': 0,
441        'Py_HashRandomizationFlag': 1,
442        '_Py_HasFileSystemDefaultEncodeErrors': 0,
443    }
444    COPY_GLOBAL_PRE_CONFIG = [
445        ('Py_UTF8Mode', 'utf8_mode'),
446    ]
447    COPY_GLOBAL_CONFIG = [
448        # Copy core config to global config for expected values
449        # True means that the core config value is inverted (0 => 1 and 1 => 0)
450        ('Py_BytesWarningFlag', 'bytes_warning'),
451        ('Py_DebugFlag', 'parser_debug'),
452        ('Py_DontWriteBytecodeFlag', 'write_bytecode', True),
453        ('Py_FileSystemDefaultEncodeErrors', 'filesystem_errors'),
454        ('Py_FileSystemDefaultEncoding', 'filesystem_encoding'),
455        ('Py_FrozenFlag', 'pathconfig_warnings', True),
456        ('Py_IgnoreEnvironmentFlag', 'use_environment', True),
457        ('Py_InspectFlag', 'inspect'),
458        ('Py_InteractiveFlag', 'interactive'),
459        ('Py_IsolatedFlag', 'isolated'),
460        ('Py_NoSiteFlag', 'site_import', True),
461        ('Py_NoUserSiteDirectory', 'user_site_directory', True),
462        ('Py_OptimizeFlag', 'optimization_level'),
463        ('Py_QuietFlag', 'quiet'),
464        ('Py_UnbufferedStdioFlag', 'buffered_stdio', True),
465        ('Py_VerboseFlag', 'verbose'),
466    ]
467    if MS_WINDOWS:
468        COPY_GLOBAL_PRE_CONFIG.extend((
469            ('Py_LegacyWindowsFSEncodingFlag', 'legacy_windows_fs_encoding'),
470        ))
471        COPY_GLOBAL_CONFIG.extend((
472            ('Py_LegacyWindowsStdioFlag', 'legacy_windows_stdio'),
473        ))
474
475    EXPECTED_CONFIG = None
476
477    @classmethod
478    def tearDownClass(cls):
479        # clear cache
480        cls.EXPECTED_CONFIG = None
481
482    def main_xoptions(self, xoptions_list):
483        xoptions = {}
484        for opt in xoptions_list:
485            if '=' in opt:
486                key, value = opt.split('=', 1)
487                xoptions[key] = value
488            else:
489                xoptions[opt] = True
490        return xoptions
491
492    def _get_expected_config_impl(self):
493        env = remove_python_envvars()
494        code = textwrap.dedent('''
495            import json
496            import sys
497            import _testinternalcapi
498
499            configs = _testinternalcapi.get_configs()
500
501            data = json.dumps(configs)
502            data = data.encode('utf-8')
503            sys.stdout.buffer.write(data)
504            sys.stdout.buffer.flush()
505        ''')
506
507        # Use -S to not import the site module: get the proper configuration
508        # when test_embed is run from a venv (bpo-35313)
509        args = [sys.executable, '-S', '-c', code]
510        proc = subprocess.run(args, env=env,
511                              stdout=subprocess.PIPE,
512                              stderr=subprocess.PIPE)
513        if proc.returncode:
514            raise Exception(f"failed to get the default config: "
515                            f"stdout={proc.stdout!r} stderr={proc.stderr!r}")
516        stdout = proc.stdout.decode('utf-8')
517        # ignore stderr
518        try:
519            return json.loads(stdout)
520        except json.JSONDecodeError:
521            self.fail(f"fail to decode stdout: {stdout!r}")
522
523    def _get_expected_config(self):
524        cls = InitConfigTests
525        if cls.EXPECTED_CONFIG is None:
526            cls.EXPECTED_CONFIG = self._get_expected_config_impl()
527
528        # get a copy
529        configs = {}
530        for config_key, config_value in cls.EXPECTED_CONFIG.items():
531            config = {}
532            for key, value in config_value.items():
533                if isinstance(value, list):
534                    value = value.copy()
535                config[key] = value
536            configs[config_key] = config
537        return configs
538
539    def get_expected_config(self, expected_preconfig, expected, env, api,
540                            modify_path_cb=None):
541        cls = self.__class__
542        configs = self._get_expected_config()
543
544        pre_config = configs['pre_config']
545        for key, value in expected_preconfig.items():
546            if value is self.GET_DEFAULT_CONFIG:
547                expected_preconfig[key] = pre_config[key]
548
549        if not expected_preconfig['configure_locale'] or api == API_COMPAT:
550            # there is no easy way to get the locale encoding before
551            # setlocale(LC_CTYPE, "") is called: don't test encodings
552            for key in ('filesystem_encoding', 'filesystem_errors',
553                        'stdio_encoding', 'stdio_errors'):
554                expected[key] = self.IGNORE_CONFIG
555
556        if not expected_preconfig['configure_locale']:
557            # UTF-8 Mode depends on the locale. There is no easy way
558            # to guess if UTF-8 Mode will be enabled or not if the locale
559            # is not configured.
560            expected_preconfig['utf8_mode'] = self.IGNORE_CONFIG
561
562        if expected_preconfig['utf8_mode'] == 1:
563            if expected['filesystem_encoding'] is self.GET_DEFAULT_CONFIG:
564                expected['filesystem_encoding'] = 'utf-8'
565            if expected['filesystem_errors'] is self.GET_DEFAULT_CONFIG:
566                expected['filesystem_errors'] = self.UTF8_MODE_ERRORS
567            if expected['stdio_encoding'] is self.GET_DEFAULT_CONFIG:
568                expected['stdio_encoding'] = 'utf-8'
569            if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG:
570                expected['stdio_errors'] = 'surrogateescape'
571
572        if MS_WINDOWS:
573            default_executable = self.test_exe
574        elif expected['program_name'] is not self.GET_DEFAULT_CONFIG:
575            default_executable = os.path.abspath(expected['program_name'])
576        else:
577            default_executable = os.path.join(os.getcwd(), '_testembed')
578        if expected['executable'] is self.GET_DEFAULT_CONFIG:
579            expected['executable'] = default_executable
580        if expected['base_executable'] is self.GET_DEFAULT_CONFIG:
581            expected['base_executable'] = default_executable
582        if expected['program_name'] is self.GET_DEFAULT_CONFIG:
583            expected['program_name'] = './_testembed'
584
585        config = configs['config']
586        for key, value in expected.items():
587            if value is self.GET_DEFAULT_CONFIG:
588                expected[key] = config[key]
589
590        if expected['module_search_paths'] is not self.IGNORE_CONFIG:
591            pythonpath_env = expected['pythonpath_env']
592            if pythonpath_env is not None:
593                paths = pythonpath_env.split(os.path.pathsep)
594                expected['module_search_paths'] = [*paths, *expected['module_search_paths']]
595            if modify_path_cb is not None:
596                expected['module_search_paths'] = expected['module_search_paths'].copy()
597                modify_path_cb(expected['module_search_paths'])
598
599        for key in self.COPY_PRE_CONFIG:
600            if key not in expected_preconfig:
601                expected_preconfig[key] = expected[key]
602
603    def check_pre_config(self, configs, expected):
604        pre_config = dict(configs['pre_config'])
605        for key, value in list(expected.items()):
606            if value is self.IGNORE_CONFIG:
607                pre_config.pop(key, None)
608                del expected[key]
609        self.assertEqual(pre_config, expected)
610
611    def check_config(self, configs, expected):
612        config = dict(configs['config'])
613        for key, value in list(expected.items()):
614            if value is self.IGNORE_CONFIG:
615                config.pop(key, None)
616                del expected[key]
617        self.assertEqual(config, expected)
618
619    def check_global_config(self, configs):
620        pre_config = configs['pre_config']
621        config = configs['config']
622
623        expected = dict(self.DEFAULT_GLOBAL_CONFIG)
624        for item in self.COPY_GLOBAL_CONFIG:
625            if len(item) == 3:
626                global_key, core_key, opposite = item
627                expected[global_key] = 0 if config[core_key] else 1
628            else:
629                global_key, core_key = item
630                expected[global_key] = config[core_key]
631        for item in self.COPY_GLOBAL_PRE_CONFIG:
632            if len(item) == 3:
633                global_key, core_key, opposite = item
634                expected[global_key] = 0 if pre_config[core_key] else 1
635            else:
636                global_key, core_key = item
637                expected[global_key] = pre_config[core_key]
638
639        self.assertEqual(configs['global_config'], expected)
640
641    def check_all_configs(self, testname, expected_config=None,
642                          expected_preconfig=None, modify_path_cb=None,
643                          stderr=None, *, api, preconfig_api=None,
644                          env=None, ignore_stderr=False, cwd=None):
645        new_env = remove_python_envvars()
646        if env is not None:
647            new_env.update(env)
648        env = new_env
649
650        if preconfig_api is None:
651            preconfig_api = api
652        if preconfig_api == API_ISOLATED:
653            default_preconfig = self.PRE_CONFIG_ISOLATED
654        elif preconfig_api == API_PYTHON:
655            default_preconfig = self.PRE_CONFIG_PYTHON
656        else:
657            default_preconfig = self.PRE_CONFIG_COMPAT
658        if expected_preconfig is None:
659            expected_preconfig = {}
660        expected_preconfig = dict(default_preconfig, **expected_preconfig)
661        if expected_config is None:
662            expected_config = {}
663
664        if api == API_PYTHON:
665            default_config = self.CONFIG_PYTHON
666        elif api == API_ISOLATED:
667            default_config = self.CONFIG_ISOLATED
668        else:
669            default_config = self.CONFIG_COMPAT
670        expected_config = dict(default_config, **expected_config)
671
672        self.get_expected_config(expected_preconfig,
673                                 expected_config, env,
674                                 api, modify_path_cb)
675
676        out, err = self.run_embedded_interpreter(testname,
677                                                 env=env, cwd=cwd)
678        if stderr is None and not expected_config['verbose']:
679            stderr = ""
680        if stderr is not None and not ignore_stderr:
681            self.assertEqual(err.rstrip(), stderr)
682        try:
683            configs = json.loads(out)
684        except json.JSONDecodeError:
685            self.fail(f"fail to decode stdout: {out!r}")
686
687        self.check_pre_config(configs, expected_preconfig)
688        self.check_config(configs, expected_config)
689        self.check_global_config(configs)
690        return configs
691
692    def test_init_default_config(self):
693        self.check_all_configs("test_init_initialize_config", api=API_COMPAT)
694
695    def test_preinit_compat_config(self):
696        self.check_all_configs("test_preinit_compat_config", api=API_COMPAT)
697
698    def test_init_compat_config(self):
699        self.check_all_configs("test_init_compat_config", api=API_COMPAT)
700
701    def test_init_global_config(self):
702        preconfig = {
703            'utf8_mode': 1,
704        }
705        config = {
706            'program_name': './globalvar',
707            'site_import': 0,
708            'bytes_warning': 1,
709            'warnoptions': ['default::BytesWarning'],
710            'inspect': 1,
711            'interactive': 1,
712            'optimization_level': 2,
713            'write_bytecode': 0,
714            'verbose': 1,
715            'quiet': 1,
716            'buffered_stdio': 0,
717
718            'user_site_directory': 0,
719            'pathconfig_warnings': 0,
720        }
721        self.check_all_configs("test_init_global_config", config, preconfig,
722                               api=API_COMPAT)
723
724    def test_init_from_config(self):
725        preconfig = {
726            'allocator': PYMEM_ALLOCATOR_MALLOC,
727            'utf8_mode': 1,
728        }
729        config = {
730            'install_signal_handlers': 0,
731            'use_hash_seed': 1,
732            'hash_seed': 123,
733            'tracemalloc': 2,
734            'import_time': 1,
735            'show_ref_count': 1,
736            'malloc_stats': 1,
737            '_use_peg_parser': 0,
738
739            'stdio_encoding': 'iso8859-1',
740            'stdio_errors': 'replace',
741
742            'pycache_prefix': 'conf_pycache_prefix',
743            'program_name': './conf_program_name',
744            'argv': ['-c', 'arg2'],
745            '_orig_argv': ['python3',
746                           '-W', 'cmdline_warnoption',
747                           '-X', 'cmdline_xoption',
748                           '-c', 'pass',
749                           'arg2'],
750            'parse_argv': 1,
751            'xoptions': [
752                'config_xoption1=3',
753                'config_xoption2=',
754                'config_xoption3',
755                'cmdline_xoption',
756            ],
757            'warnoptions': [
758                'cmdline_warnoption',
759                'default::BytesWarning',
760                'config_warnoption',
761            ],
762            'run_command': 'pass\n',
763
764            'site_import': 0,
765            'bytes_warning': 1,
766            'inspect': 1,
767            'interactive': 1,
768            'optimization_level': 2,
769            'write_bytecode': 0,
770            'verbose': 1,
771            'quiet': 1,
772            'configure_c_stdio': 1,
773            'buffered_stdio': 0,
774            'user_site_directory': 0,
775            'faulthandler': 1,
776            'platlibdir': 'my_platlibdir',
777            'module_search_paths': self.IGNORE_CONFIG,
778
779            'check_hash_pycs_mode': 'always',
780            'pathconfig_warnings': 0,
781
782            '_isolated_interpreter': 1,
783        }
784        self.check_all_configs("test_init_from_config", config, preconfig,
785                               api=API_COMPAT)
786
787    def test_init_compat_env(self):
788        preconfig = {
789            'allocator': PYMEM_ALLOCATOR_MALLOC,
790        }
791        config = {
792            'use_hash_seed': 1,
793            'hash_seed': 42,
794            'tracemalloc': 2,
795            'import_time': 1,
796            'malloc_stats': 1,
797            'inspect': 1,
798            'optimization_level': 2,
799            'pythonpath_env': '/my/path',
800            'pycache_prefix': 'env_pycache_prefix',
801            'write_bytecode': 0,
802            'verbose': 1,
803            'buffered_stdio': 0,
804            'stdio_encoding': 'iso8859-1',
805            'stdio_errors': 'replace',
806            'user_site_directory': 0,
807            'faulthandler': 1,
808            'warnoptions': ['EnvVar'],
809            'platlibdir': 'env_platlibdir',
810            'module_search_paths': self.IGNORE_CONFIG,
811            '_use_peg_parser': 0,
812        }
813        self.check_all_configs("test_init_compat_env", config, preconfig,
814                               api=API_COMPAT)
815
816    def test_init_python_env(self):
817        preconfig = {
818            'allocator': PYMEM_ALLOCATOR_MALLOC,
819            'utf8_mode': 1,
820        }
821        config = {
822            'use_hash_seed': 1,
823            'hash_seed': 42,
824            'tracemalloc': 2,
825            'import_time': 1,
826            'malloc_stats': 1,
827            'inspect': 1,
828            'optimization_level': 2,
829            'pythonpath_env': '/my/path',
830            'pycache_prefix': 'env_pycache_prefix',
831            'write_bytecode': 0,
832            'verbose': 1,
833            'buffered_stdio': 0,
834            'stdio_encoding': 'iso8859-1',
835            'stdio_errors': 'replace',
836            'user_site_directory': 0,
837            'faulthandler': 1,
838            'warnoptions': ['EnvVar'],
839            'platlibdir': 'env_platlibdir',
840            'module_search_paths': self.IGNORE_CONFIG,
841            '_use_peg_parser': 0,
842        }
843        self.check_all_configs("test_init_python_env", config, preconfig,
844                               api=API_PYTHON)
845
846    def test_init_env_dev_mode(self):
847        preconfig = dict(allocator=PYMEM_ALLOCATOR_DEBUG)
848        config = dict(dev_mode=1,
849                      faulthandler=1,
850                      warnoptions=['default'])
851        self.check_all_configs("test_init_env_dev_mode", config, preconfig,
852                               api=API_COMPAT)
853
854    def test_init_env_dev_mode_alloc(self):
855        preconfig = dict(allocator=PYMEM_ALLOCATOR_MALLOC)
856        config = dict(dev_mode=1,
857                      faulthandler=1,
858                      warnoptions=['default'])
859        self.check_all_configs("test_init_env_dev_mode_alloc", config, preconfig,
860                               api=API_COMPAT)
861
862    def test_init_dev_mode(self):
863        preconfig = {
864            'allocator': PYMEM_ALLOCATOR_DEBUG,
865        }
866        config = {
867            'faulthandler': 1,
868            'dev_mode': 1,
869            'warnoptions': ['default'],
870        }
871        self.check_all_configs("test_init_dev_mode", config, preconfig,
872                               api=API_PYTHON)
873
874    def test_preinit_parse_argv(self):
875        # Pre-initialize implicitly using argv: make sure that -X dev
876        # is used to configure the allocation in preinitialization
877        preconfig = {
878            'allocator': PYMEM_ALLOCATOR_DEBUG,
879        }
880        config = {
881            'argv': ['script.py'],
882            '_orig_argv': ['python3', '-X', 'dev', 'script.py'],
883            'run_filename': os.path.abspath('script.py'),
884            'dev_mode': 1,
885            'faulthandler': 1,
886            'warnoptions': ['default'],
887            'xoptions': ['dev'],
888        }
889        self.check_all_configs("test_preinit_parse_argv", config, preconfig,
890                               api=API_PYTHON)
891
892    def test_preinit_dont_parse_argv(self):
893        # -X dev must be ignored by isolated preconfiguration
894        preconfig = {
895            'isolated': 0,
896        }
897        argv = ["python3",
898               "-E", "-I",
899               "-X", "dev",
900               "-X", "utf8",
901               "script.py"]
902        config = {
903            'argv': argv,
904            '_orig_argv': argv,
905            'isolated': 0,
906        }
907        self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig,
908                               api=API_ISOLATED)
909
910    def test_init_isolated_flag(self):
911        config = {
912            'isolated': 1,
913            'use_environment': 0,
914            'user_site_directory': 0,
915        }
916        self.check_all_configs("test_init_isolated_flag", config, api=API_PYTHON)
917
918    def test_preinit_isolated1(self):
919        # _PyPreConfig.isolated=1, _PyCoreConfig.isolated not set
920        config = {
921            'isolated': 1,
922            'use_environment': 0,
923            'user_site_directory': 0,
924        }
925        self.check_all_configs("test_preinit_isolated1", config, api=API_COMPAT)
926
927    def test_preinit_isolated2(self):
928        # _PyPreConfig.isolated=0, _PyCoreConfig.isolated=1
929        config = {
930            'isolated': 1,
931            'use_environment': 0,
932            'user_site_directory': 0,
933        }
934        self.check_all_configs("test_preinit_isolated2", config, api=API_COMPAT)
935
936    def test_preinit_isolated_config(self):
937        self.check_all_configs("test_preinit_isolated_config", api=API_ISOLATED)
938
939    def test_init_isolated_config(self):
940        self.check_all_configs("test_init_isolated_config", api=API_ISOLATED)
941
942    def test_preinit_python_config(self):
943        self.check_all_configs("test_preinit_python_config", api=API_PYTHON)
944
945    def test_init_python_config(self):
946        self.check_all_configs("test_init_python_config", api=API_PYTHON)
947
948    def test_init_dont_configure_locale(self):
949        # _PyPreConfig.configure_locale=0
950        preconfig = {
951            'configure_locale': 0,
952            'coerce_c_locale': 0,
953        }
954        self.check_all_configs("test_init_dont_configure_locale", {}, preconfig,
955                               api=API_PYTHON)
956
957    def test_init_read_set(self):
958        config = {
959            'program_name': './init_read_set',
960            'executable': 'my_executable',
961        }
962        def modify_path(path):
963            path.insert(1, "test_path_insert1")
964            path.append("test_path_append")
965        self.check_all_configs("test_init_read_set", config,
966                               api=API_PYTHON,
967                               modify_path_cb=modify_path)
968
969    def test_init_sys_add(self):
970        config = {
971            'faulthandler': 1,
972            'xoptions': [
973                'config_xoption',
974                'cmdline_xoption',
975                'sysadd_xoption',
976                'faulthandler',
977            ],
978            'warnoptions': [
979                'ignore:::cmdline_warnoption',
980                'ignore:::sysadd_warnoption',
981                'ignore:::config_warnoption',
982            ],
983            '_orig_argv': ['python3',
984                           '-W', 'ignore:::cmdline_warnoption',
985                           '-X', 'cmdline_xoption'],
986        }
987        self.check_all_configs("test_init_sys_add", config, api=API_PYTHON)
988
989    def test_init_run_main(self):
990        code = ('import _testinternalcapi, json; '
991                'print(json.dumps(_testinternalcapi.get_configs()))')
992        config = {
993            'argv': ['-c', 'arg2'],
994            '_orig_argv': ['python3', '-c', code, 'arg2'],
995            'program_name': './python3',
996            'run_command': code + '\n',
997            'parse_argv': 1,
998        }
999        self.check_all_configs("test_init_run_main", config, api=API_PYTHON)
1000
1001    def test_init_main(self):
1002        code = ('import _testinternalcapi, json; '
1003                'print(json.dumps(_testinternalcapi.get_configs()))')
1004        config = {
1005            'argv': ['-c', 'arg2'],
1006            '_orig_argv': ['python3',
1007                           '-c', code,
1008                           'arg2'],
1009            'program_name': './python3',
1010            'run_command': code + '\n',
1011            'parse_argv': 1,
1012            '_init_main': 0,
1013        }
1014        self.check_all_configs("test_init_main", config,
1015                               api=API_PYTHON,
1016                               stderr="Run Python code before _Py_InitializeMain")
1017
1018    def test_init_parse_argv(self):
1019        config = {
1020            'parse_argv': 1,
1021            'argv': ['-c', 'arg1', '-v', 'arg3'],
1022            '_orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
1023            'program_name': './argv0',
1024            'run_command': 'pass\n',
1025            'use_environment': 0,
1026        }
1027        self.check_all_configs("test_init_parse_argv", config, api=API_PYTHON)
1028
1029    def test_init_dont_parse_argv(self):
1030        pre_config = {
1031            'parse_argv': 0,
1032        }
1033        config = {
1034            'parse_argv': 0,
1035            'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
1036            '_orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
1037            'program_name': './argv0',
1038        }
1039        self.check_all_configs("test_init_dont_parse_argv", config, pre_config,
1040                               api=API_PYTHON)
1041
1042    def default_program_name(self, config):
1043        if MS_WINDOWS:
1044            program_name = 'python'
1045            executable = self.test_exe
1046        else:
1047            program_name = 'python3'
1048            if MACOS:
1049                executable = self.test_exe
1050            else:
1051                executable = shutil.which(program_name) or ''
1052        config.update({
1053            'program_name': program_name,
1054            'base_executable': executable,
1055            'executable': executable,
1056        })
1057
1058    def test_init_setpath(self):
1059        # Test Py_SetPath()
1060        config = self._get_expected_config()
1061        paths = config['config']['module_search_paths']
1062
1063        config = {
1064            'module_search_paths': paths,
1065            'prefix': '',
1066            'base_prefix': '',
1067            'exec_prefix': '',
1068            'base_exec_prefix': '',
1069        }
1070        self.default_program_name(config)
1071        env = {'TESTPATH': os.path.pathsep.join(paths)}
1072
1073        self.check_all_configs("test_init_setpath", config,
1074                               api=API_COMPAT, env=env,
1075                               ignore_stderr=True)
1076
1077    def test_init_setpath_config(self):
1078        # Test Py_SetPath() with PyConfig
1079        config = self._get_expected_config()
1080        paths = config['config']['module_search_paths']
1081
1082        config = {
1083            # set by Py_SetPath()
1084            'module_search_paths': paths,
1085            'prefix': '',
1086            'base_prefix': '',
1087            'exec_prefix': '',
1088            'base_exec_prefix': '',
1089            # overriden by PyConfig
1090            'program_name': 'conf_program_name',
1091            'base_executable': 'conf_executable',
1092            'executable': 'conf_executable',
1093        }
1094        env = {'TESTPATH': os.path.pathsep.join(paths)}
1095        self.check_all_configs("test_init_setpath_config", config,
1096                               api=API_PYTHON, env=env, ignore_stderr=True)
1097
1098    def module_search_paths(self, prefix=None, exec_prefix=None):
1099        config = self._get_expected_config()
1100        if prefix is None:
1101            prefix = config['config']['prefix']
1102        if exec_prefix is None:
1103            exec_prefix = config['config']['prefix']
1104        if MS_WINDOWS:
1105            return config['config']['module_search_paths']
1106        else:
1107            ver = sys.version_info
1108            return [
1109                os.path.join(prefix, sys.platlibdir,
1110                             f'python{ver.major}{ver.minor}.zip'),
1111                os.path.join(prefix, sys.platlibdir,
1112                             f'python{ver.major}.{ver.minor}'),
1113                os.path.join(exec_prefix, sys.platlibdir,
1114                             f'python{ver.major}.{ver.minor}', 'lib-dynload'),
1115            ]
1116
1117    @contextlib.contextmanager
1118    def tmpdir_with_python(self):
1119        # Temporary directory with a copy of the Python program
1120        with tempfile.TemporaryDirectory() as tmpdir:
1121            # bpo-38234: On macOS and FreeBSD, the temporary directory
1122            # can be symbolic link. For example, /tmp can be a symbolic link
1123            # to /var/tmp. Call realpath() to resolve all symbolic links.
1124            tmpdir = os.path.realpath(tmpdir)
1125
1126            if MS_WINDOWS:
1127                # Copy pythonXY.dll (or pythonXY_d.dll)
1128                ver = sys.version_info
1129                dll = f'python{ver.major}{ver.minor}'
1130                dll3 = f'python{ver.major}'
1131                if debug_build(sys.executable):
1132                    dll += '_d'
1133                    dll3 += '_d'
1134                dll += '.dll'
1135                dll3 += '.dll'
1136                dll = os.path.join(os.path.dirname(self.test_exe), dll)
1137                dll3 = os.path.join(os.path.dirname(self.test_exe), dll3)
1138                dll_copy = os.path.join(tmpdir, os.path.basename(dll))
1139                dll3_copy = os.path.join(tmpdir, os.path.basename(dll3))
1140                shutil.copyfile(dll, dll_copy)
1141                shutil.copyfile(dll3, dll3_copy)
1142
1143            # Copy Python program
1144            exec_copy = os.path.join(tmpdir, os.path.basename(self.test_exe))
1145            shutil.copyfile(self.test_exe, exec_copy)
1146            shutil.copystat(self.test_exe, exec_copy)
1147            self.test_exe = exec_copy
1148
1149            yield tmpdir
1150
1151    def test_init_setpythonhome(self):
1152        # Test Py_SetPythonHome(home) with PYTHONPATH env var
1153        config = self._get_expected_config()
1154        paths = config['config']['module_search_paths']
1155        paths_str = os.path.pathsep.join(paths)
1156
1157        for path in paths:
1158            if not os.path.isdir(path):
1159                continue
1160            if os.path.exists(os.path.join(path, 'os.py')):
1161                home = os.path.dirname(path)
1162                break
1163        else:
1164            self.fail(f"Unable to find home in {paths!r}")
1165
1166        prefix = exec_prefix = home
1167        ver = sys.version_info
1168        expected_paths = self.module_search_paths(prefix=home, exec_prefix=home)
1169
1170        config = {
1171            'home': home,
1172            'module_search_paths': expected_paths,
1173            'prefix': prefix,
1174            'base_prefix': prefix,
1175            'exec_prefix': exec_prefix,
1176            'base_exec_prefix': exec_prefix,
1177            'pythonpath_env': paths_str,
1178        }
1179        self.default_program_name(config)
1180        env = {'TESTHOME': home, 'PYTHONPATH': paths_str}
1181        self.check_all_configs("test_init_setpythonhome", config,
1182                               api=API_COMPAT, env=env)
1183
1184    def copy_paths_by_env(self, config):
1185        all_configs = self._get_expected_config()
1186        paths = all_configs['config']['module_search_paths']
1187        paths_str = os.path.pathsep.join(paths)
1188        config['pythonpath_env'] = paths_str
1189        env = {'PYTHONPATH': paths_str}
1190        return env
1191
1192    @unittest.skipIf(MS_WINDOWS, 'Windows does not use pybuilddir.txt')
1193    def test_init_pybuilddir(self):
1194        # Test path configuration with pybuilddir.txt configuration file
1195
1196        with self.tmpdir_with_python() as tmpdir:
1197            # pybuilddir.txt is a sub-directory relative to the current
1198            # directory (tmpdir)
1199            subdir = 'libdir'
1200            libdir = os.path.join(tmpdir, subdir)
1201            os.mkdir(libdir)
1202
1203            filename = os.path.join(tmpdir, 'pybuilddir.txt')
1204            with open(filename, "w", encoding="utf8") as fp:
1205                fp.write(subdir)
1206
1207            module_search_paths = self.module_search_paths()
1208            module_search_paths[-1] = libdir
1209
1210            executable = self.test_exe
1211            config = {
1212                'base_executable': executable,
1213                'executable': executable,
1214                'module_search_paths': module_search_paths,
1215            }
1216            env = self.copy_paths_by_env(config)
1217            self.check_all_configs("test_init_compat_config", config,
1218                                   api=API_COMPAT, env=env,
1219                                   ignore_stderr=True, cwd=tmpdir)
1220
1221    def test_init_pyvenv_cfg(self):
1222        # Test path configuration with pyvenv.cfg configuration file
1223
1224        with self.tmpdir_with_python() as tmpdir, \
1225             tempfile.TemporaryDirectory() as pyvenv_home:
1226            ver = sys.version_info
1227
1228            if not MS_WINDOWS:
1229                lib_dynload = os.path.join(pyvenv_home,
1230                                           sys.platlibdir,
1231                                           f'python{ver.major}.{ver.minor}',
1232                                           'lib-dynload')
1233                os.makedirs(lib_dynload)
1234            else:
1235                lib_dynload = os.path.join(pyvenv_home, 'lib')
1236                os.makedirs(lib_dynload)
1237                # getpathp.c uses Lib\os.py as the LANDMARK
1238                shutil.copyfile(os.__file__, os.path.join(lib_dynload, 'os.py'))
1239
1240            filename = os.path.join(tmpdir, 'pyvenv.cfg')
1241            with open(filename, "w", encoding="utf8") as fp:
1242                print("home = %s" % pyvenv_home, file=fp)
1243                print("include-system-site-packages = false", file=fp)
1244
1245            paths = self.module_search_paths()
1246            if not MS_WINDOWS:
1247                paths[-1] = lib_dynload
1248            else:
1249                for index, path in enumerate(paths):
1250                    if index == 0:
1251                        paths[index] = os.path.join(tmpdir, os.path.basename(path))
1252                    else:
1253                        paths[index] = os.path.join(pyvenv_home, os.path.basename(path))
1254                paths[-1] = pyvenv_home
1255
1256            executable = self.test_exe
1257            exec_prefix = pyvenv_home
1258            config = {
1259                'base_exec_prefix': exec_prefix,
1260                'exec_prefix': exec_prefix,
1261                'base_executable': executable,
1262                'executable': executable,
1263                'module_search_paths': paths,
1264            }
1265            if MS_WINDOWS:
1266                config['base_prefix'] = pyvenv_home
1267                config['prefix'] = pyvenv_home
1268            env = self.copy_paths_by_env(config)
1269            actual = self.check_all_configs("test_init_compat_config", config,
1270                                            api=API_COMPAT, env=env,
1271                                            ignore_stderr=True, cwd=tmpdir)
1272            if MS_WINDOWS:
1273                self.assertEqual(
1274                    actual['windows']['python3_dll'],
1275                    os.path.join(
1276                        tmpdir,
1277                        os.path.basename(self.EXPECTED_CONFIG['windows']['python3_dll'])
1278                    )
1279                )
1280
1281
1282    def test_global_pathconfig(self):
1283        # Test C API functions getting the path configuration:
1284        #
1285        # - Py_GetExecPrefix()
1286        # - Py_GetPath()
1287        # - Py_GetPrefix()
1288        # - Py_GetProgramFullPath()
1289        # - Py_GetProgramName()
1290        # - Py_GetPythonHome()
1291        #
1292        # The global path configuration (_Py_path_config) must be a copy
1293        # of the path configuration of PyInterpreter.config (PyConfig).
1294        ctypes = support.import_module('ctypes')
1295        _testinternalcapi = support.import_module('_testinternalcapi')
1296
1297        def get_func(name):
1298            func = getattr(ctypes.pythonapi, name)
1299            func.argtypes = ()
1300            func.restype = ctypes.c_wchar_p
1301            return func
1302
1303        Py_GetPath = get_func('Py_GetPath')
1304        Py_GetPrefix = get_func('Py_GetPrefix')
1305        Py_GetExecPrefix = get_func('Py_GetExecPrefix')
1306        Py_GetProgramName = get_func('Py_GetProgramName')
1307        Py_GetProgramFullPath = get_func('Py_GetProgramFullPath')
1308        Py_GetPythonHome = get_func('Py_GetPythonHome')
1309
1310        config = _testinternalcapi.get_configs()['config']
1311
1312        self.assertEqual(Py_GetPath().split(os.path.pathsep),
1313                         config['module_search_paths'])
1314        self.assertEqual(Py_GetPrefix(), config['prefix'])
1315        self.assertEqual(Py_GetExecPrefix(), config['exec_prefix'])
1316        self.assertEqual(Py_GetProgramName(), config['program_name'])
1317        self.assertEqual(Py_GetProgramFullPath(), config['executable'])
1318        self.assertEqual(Py_GetPythonHome(), config['home'])
1319
1320    def test_init_warnoptions(self):
1321        # lowest to highest priority
1322        warnoptions = [
1323            'ignore:::PyConfig_Insert0',      # PyWideStringList_Insert(0)
1324            'default',                        # PyConfig.dev_mode=1
1325            'ignore:::env1',                  # PYTHONWARNINGS env var
1326            'ignore:::env2',                  # PYTHONWARNINGS env var
1327            'ignore:::cmdline1',              # -W opt command line option
1328            'ignore:::cmdline2',              # -W opt command line option
1329            'default::BytesWarning',          # PyConfig.bytes_warnings=1
1330            'ignore:::PySys_AddWarnOption1',  # PySys_AddWarnOption()
1331            'ignore:::PySys_AddWarnOption2',  # PySys_AddWarnOption()
1332            'ignore:::PyConfig_BeforeRead',   # PyConfig.warnoptions
1333            'ignore:::PyConfig_AfterRead']    # PyWideStringList_Append()
1334        preconfig = dict(allocator=PYMEM_ALLOCATOR_DEBUG)
1335        config = {
1336            'dev_mode': 1,
1337            'faulthandler': 1,
1338            'bytes_warning': 1,
1339            'warnoptions': warnoptions,
1340            '_orig_argv': ['python3',
1341                           '-Wignore:::cmdline1',
1342                           '-Wignore:::cmdline2'],
1343        }
1344        self.check_all_configs("test_init_warnoptions", config, preconfig,
1345                               api=API_PYTHON)
1346
1347    def test_get_argc_argv(self):
1348        self.run_embedded_interpreter("test_get_argc_argv")
1349        # ignore output
1350
1351
1352class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
1353    def test_open_code_hook(self):
1354        self.run_embedded_interpreter("test_open_code_hook")
1355
1356    def test_audit(self):
1357        self.run_embedded_interpreter("test_audit")
1358
1359    def test_audit_subinterpreter(self):
1360        self.run_embedded_interpreter("test_audit_subinterpreter")
1361
1362    def test_audit_run_command(self):
1363        self.run_embedded_interpreter("test_audit_run_command",
1364                                      timeout=support.SHORT_TIMEOUT,
1365                                      returncode=1)
1366
1367    def test_audit_run_file(self):
1368        self.run_embedded_interpreter("test_audit_run_file",
1369                                      timeout=support.SHORT_TIMEOUT,
1370                                      returncode=1)
1371
1372    def test_audit_run_interactivehook(self):
1373        startup = os.path.join(self.oldcwd, support.TESTFN) + ".py"
1374        with open(startup, "w", encoding="utf-8") as f:
1375            print("import sys", file=f)
1376            print("sys.__interactivehook__ = lambda: None", file=f)
1377        try:
1378            env = {**remove_python_envvars(), "PYTHONSTARTUP": startup}
1379            self.run_embedded_interpreter("test_audit_run_interactivehook",
1380                                          timeout=support.SHORT_TIMEOUT,
1381                                          returncode=10, env=env)
1382        finally:
1383            os.unlink(startup)
1384
1385    def test_audit_run_startup(self):
1386        startup = os.path.join(self.oldcwd, support.TESTFN) + ".py"
1387        with open(startup, "w", encoding="utf-8") as f:
1388            print("pass", file=f)
1389        try:
1390            env = {**remove_python_envvars(), "PYTHONSTARTUP": startup}
1391            self.run_embedded_interpreter("test_audit_run_startup",
1392                                          timeout=support.SHORT_TIMEOUT,
1393                                          returncode=10, env=env)
1394        finally:
1395            os.unlink(startup)
1396
1397    def test_audit_run_stdin(self):
1398        self.run_embedded_interpreter("test_audit_run_stdin",
1399                                      timeout=support.SHORT_TIMEOUT,
1400                                      returncode=1)
1401
1402if __name__ == "__main__":
1403    unittest.main()
1404