python (3.12.0)

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