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