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