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 before 271 calling PyEval_InitThreads() must not crash. PyGILState_Ensure() must 272 call PyEval_InitThreads() for us in this case. 273 """ 274 out, err = self.run_embedded_interpreter("test_bpo20891") 275 self.assertEqual(out, '') 276 self.assertEqual(err, '') 277 278 def test_initialize_twice(self): 279 """ 280 bpo-33932: Calling Py_Initialize() twice should do nothing (and not 281 crash!). 282 """ 283 out, err = self.run_embedded_interpreter("test_initialize_twice") 284 self.assertEqual(out, '') 285 self.assertEqual(err, '') 286 287 def test_initialize_pymain(self): 288 """ 289 bpo-34008: Calling Py_Main() after Py_Initialize() must not fail. 290 """ 291 out, err = self.run_embedded_interpreter("test_initialize_pymain") 292 self.assertEqual(out.rstrip(), "Py_Main() after Py_Initialize: sys.argv=['-c', 'arg2']") 293 self.assertEqual(err, '') 294 295 def test_run_main(self): 296 out, err = self.run_embedded_interpreter("test_run_main") 297 self.assertEqual(out.rstrip(), "Py_RunMain(): sys.argv=['-c', 'arg2']") 298 self.assertEqual(err, '') 299 300 301class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 302 maxDiff = 4096 303 UTF8_MODE_ERRORS = ('surrogatepass' if MS_WINDOWS else 'surrogateescape') 304 305 # Marker to read the default configuration: get_default_config() 306 GET_DEFAULT_CONFIG = object() 307 308 # Marker to ignore a configuration parameter 309 IGNORE_CONFIG = object() 310 311 PRE_CONFIG_COMPAT = { 312 '_config_init': API_COMPAT, 313 'allocator': PYMEM_ALLOCATOR_NOT_SET, 314 'parse_argv': 0, 315 'configure_locale': 1, 316 'coerce_c_locale': 0, 317 'coerce_c_locale_warn': 0, 318 'utf8_mode': 0, 319 } 320 if MS_WINDOWS: 321 PRE_CONFIG_COMPAT.update({ 322 'legacy_windows_fs_encoding': 0, 323 }) 324 PRE_CONFIG_PYTHON = dict(PRE_CONFIG_COMPAT, 325 _config_init=API_PYTHON, 326 parse_argv=1, 327 coerce_c_locale=GET_DEFAULT_CONFIG, 328 utf8_mode=GET_DEFAULT_CONFIG, 329 ) 330 PRE_CONFIG_ISOLATED = dict(PRE_CONFIG_COMPAT, 331 _config_init=API_ISOLATED, 332 configure_locale=0, 333 isolated=1, 334 use_environment=0, 335 utf8_mode=0, 336 dev_mode=0, 337 coerce_c_locale=0, 338 ) 339 340 COPY_PRE_CONFIG = [ 341 'dev_mode', 342 'isolated', 343 'use_environment', 344 ] 345 346 CONFIG_COMPAT = { 347 '_config_init': API_COMPAT, 348 'isolated': 0, 349 'use_environment': 1, 350 'dev_mode': 0, 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 'show_alloc_count': 0, 360 'dump_refs': 0, 361 'malloc_stats': 0, 362 363 'filesystem_encoding': GET_DEFAULT_CONFIG, 364 'filesystem_errors': GET_DEFAULT_CONFIG, 365 366 'pycache_prefix': None, 367 'program_name': GET_DEFAULT_CONFIG, 368 'parse_argv': 0, 369 '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 385 'site_import': 1, 386 'bytes_warning': 0, 387 'inspect': 0, 388 'interactive': 0, 389 'optimization_level': 0, 390 'parser_debug': 0, 391 'write_bytecode': 1, 392 'verbose': 0, 393 'quiet': 0, 394 'user_site_directory': 1, 395 'configure_c_stdio': 0, 396 'buffered_stdio': 1, 397 398 'stdio_encoding': GET_DEFAULT_CONFIG, 399 'stdio_errors': GET_DEFAULT_CONFIG, 400 401 'skip_source_first_line': 0, 402 'run_command': None, 403 'run_module': None, 404 'run_filename': None, 405 406 '_install_importlib': 1, 407 'check_hash_pycs_mode': 'default', 408 'pathconfig_warnings': 1, 409 '_init_main': 1, 410 } 411 if MS_WINDOWS: 412 CONFIG_COMPAT.update({ 413 'legacy_windows_stdio': 0, 414 }) 415 416 CONFIG_PYTHON = dict(CONFIG_COMPAT, 417 _config_init=API_PYTHON, 418 configure_c_stdio=1, 419 parse_argv=1, 420 ) 421 CONFIG_ISOLATED = dict(CONFIG_COMPAT, 422 _config_init=API_ISOLATED, 423 isolated=1, 424 use_environment=0, 425 user_site_directory=0, 426 dev_mode=0, 427 install_signal_handlers=0, 428 use_hash_seed=0, 429 faulthandler=0, 430 tracemalloc=0, 431 pathconfig_warnings=0, 432 ) 433 if MS_WINDOWS: 434 CONFIG_ISOLATED['legacy_windows_stdio'] = 0 435 436 # global config 437 DEFAULT_GLOBAL_CONFIG = { 438 'Py_HasFileSystemDefaultEncoding': 0, 439 'Py_HashRandomizationFlag': 1, 440 '_Py_HasFileSystemDefaultEncodeErrors': 0, 441 } 442 COPY_GLOBAL_PRE_CONFIG = [ 443 ('Py_UTF8Mode', 'utf8_mode'), 444 ] 445 COPY_GLOBAL_CONFIG = [ 446 # Copy core config to global config for expected values 447 # True means that the core config value is inverted (0 => 1 and 1 => 0) 448 ('Py_BytesWarningFlag', 'bytes_warning'), 449 ('Py_DebugFlag', 'parser_debug'), 450 ('Py_DontWriteBytecodeFlag', 'write_bytecode', True), 451 ('Py_FileSystemDefaultEncodeErrors', 'filesystem_errors'), 452 ('Py_FileSystemDefaultEncoding', 'filesystem_encoding'), 453 ('Py_FrozenFlag', 'pathconfig_warnings', True), 454 ('Py_IgnoreEnvironmentFlag', 'use_environment', True), 455 ('Py_InspectFlag', 'inspect'), 456 ('Py_InteractiveFlag', 'interactive'), 457 ('Py_IsolatedFlag', 'isolated'), 458 ('Py_NoSiteFlag', 'site_import', True), 459 ('Py_NoUserSiteDirectory', 'user_site_directory', True), 460 ('Py_OptimizeFlag', 'optimization_level'), 461 ('Py_QuietFlag', 'quiet'), 462 ('Py_UnbufferedStdioFlag', 'buffered_stdio', True), 463 ('Py_VerboseFlag', 'verbose'), 464 ] 465 if MS_WINDOWS: 466 COPY_GLOBAL_PRE_CONFIG.extend(( 467 ('Py_LegacyWindowsFSEncodingFlag', 'legacy_windows_fs_encoding'), 468 )) 469 COPY_GLOBAL_CONFIG.extend(( 470 ('Py_LegacyWindowsStdioFlag', 'legacy_windows_stdio'), 471 )) 472 473 EXPECTED_CONFIG = None 474 475 @classmethod 476 def tearDownClass(cls): 477 # clear cache 478 cls.EXPECTED_CONFIG = None 479 480 def main_xoptions(self, xoptions_list): 481 xoptions = {} 482 for opt in xoptions_list: 483 if '=' in opt: 484 key, value = opt.split('=', 1) 485 xoptions[key] = value 486 else: 487 xoptions[opt] = True 488 return xoptions 489 490 def _get_expected_config_impl(self): 491 env = remove_python_envvars() 492 code = textwrap.dedent(''' 493 import json 494 import sys 495 import _testinternalcapi 496 497 configs = _testinternalcapi.get_configs() 498 499 data = json.dumps(configs) 500 data = data.encode('utf-8') 501 sys.stdout.buffer.write(data) 502 sys.stdout.buffer.flush() 503 ''') 504 505 # Use -S to not import the site module: get the proper configuration 506 # when test_embed is run from a venv (bpo-35313) 507 args = [sys.executable, '-S', '-c', code] 508 proc = subprocess.run(args, env=env, 509 stdout=subprocess.PIPE, 510 stderr=subprocess.PIPE) 511 if proc.returncode: 512 raise Exception(f"failed to get the default config: " 513 f"stdout={proc.stdout!r} stderr={proc.stderr!r}") 514 stdout = proc.stdout.decode('utf-8') 515 # ignore stderr 516 try: 517 return json.loads(stdout) 518 except json.JSONDecodeError: 519 self.fail(f"fail to decode stdout: {stdout!r}") 520 521 def _get_expected_config(self): 522 cls = InitConfigTests 523 if cls.EXPECTED_CONFIG is None: 524 cls.EXPECTED_CONFIG = self._get_expected_config_impl() 525 526 # get a copy 527 configs = {} 528 for config_key, config_value in cls.EXPECTED_CONFIG.items(): 529 config = {} 530 for key, value in config_value.items(): 531 if isinstance(value, list): 532 value = value.copy() 533 config[key] = value 534 configs[config_key] = config 535 return configs 536 537 def get_expected_config(self, expected_preconfig, expected, env, api, 538 modify_path_cb=None): 539 cls = self.__class__ 540 configs = self._get_expected_config() 541 542 pre_config = configs['pre_config'] 543 for key, value in expected_preconfig.items(): 544 if value is self.GET_DEFAULT_CONFIG: 545 expected_preconfig[key] = pre_config[key] 546 547 if not expected_preconfig['configure_locale'] or api == API_COMPAT: 548 # there is no easy way to get the locale encoding before 549 # setlocale(LC_CTYPE, "") is called: don't test encodings 550 for key in ('filesystem_encoding', 'filesystem_errors', 551 'stdio_encoding', 'stdio_errors'): 552 expected[key] = self.IGNORE_CONFIG 553 554 if not expected_preconfig['configure_locale']: 555 # UTF-8 Mode depends on the locale. There is no easy way 556 # to guess if UTF-8 Mode will be enabled or not if the locale 557 # is not configured. 558 expected_preconfig['utf8_mode'] = self.IGNORE_CONFIG 559 560 if expected_preconfig['utf8_mode'] == 1: 561 if expected['filesystem_encoding'] is self.GET_DEFAULT_CONFIG: 562 expected['filesystem_encoding'] = 'utf-8' 563 if expected['filesystem_errors'] is self.GET_DEFAULT_CONFIG: 564 expected['filesystem_errors'] = self.UTF8_MODE_ERRORS 565 if expected['stdio_encoding'] is self.GET_DEFAULT_CONFIG: 566 expected['stdio_encoding'] = 'utf-8' 567 if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG: 568 expected['stdio_errors'] = 'surrogateescape' 569 570 if MS_WINDOWS: 571 default_executable = self.test_exe 572 elif expected['program_name'] is not self.GET_DEFAULT_CONFIG: 573 default_executable = os.path.abspath(expected['program_name']) 574 else: 575 default_executable = os.path.join(os.getcwd(), '_testembed') 576 if expected['executable'] is self.GET_DEFAULT_CONFIG: 577 expected['executable'] = default_executable 578 if expected['base_executable'] is self.GET_DEFAULT_CONFIG: 579 expected['base_executable'] = default_executable 580 if expected['program_name'] is self.GET_DEFAULT_CONFIG: 581 expected['program_name'] = './_testembed' 582 583 config = configs['config'] 584 for key, value in expected.items(): 585 if value is self.GET_DEFAULT_CONFIG: 586 expected[key] = config[key] 587 588 pythonpath_env = expected['pythonpath_env'] 589 if pythonpath_env is not None: 590 paths = pythonpath_env.split(os.path.pathsep) 591 expected['module_search_paths'] = [*paths, *expected['module_search_paths']] 592 if modify_path_cb is not None: 593 expected['module_search_paths'] = expected['module_search_paths'].copy() 594 modify_path_cb(expected['module_search_paths']) 595 596 for key in self.COPY_PRE_CONFIG: 597 if key not in expected_preconfig: 598 expected_preconfig[key] = expected[key] 599 600 def check_pre_config(self, configs, expected): 601 pre_config = dict(configs['pre_config']) 602 for key, value in list(expected.items()): 603 if value is self.IGNORE_CONFIG: 604 pre_config.pop(key, None) 605 del expected[key] 606 self.assertEqual(pre_config, expected) 607 608 def check_config(self, configs, expected): 609 config = dict(configs['config']) 610 for key, value in list(expected.items()): 611 if value is self.IGNORE_CONFIG: 612 config.pop(key, None) 613 del expected[key] 614 self.assertEqual(config, expected) 615 616 def check_global_config(self, configs): 617 pre_config = configs['pre_config'] 618 config = configs['config'] 619 620 expected = dict(self.DEFAULT_GLOBAL_CONFIG) 621 for item in self.COPY_GLOBAL_CONFIG: 622 if len(item) == 3: 623 global_key, core_key, opposite = item 624 expected[global_key] = 0 if config[core_key] else 1 625 else: 626 global_key, core_key = item 627 expected[global_key] = config[core_key] 628 for item in self.COPY_GLOBAL_PRE_CONFIG: 629 if len(item) == 3: 630 global_key, core_key, opposite = item 631 expected[global_key] = 0 if pre_config[core_key] else 1 632 else: 633 global_key, core_key = item 634 expected[global_key] = pre_config[core_key] 635 636 self.assertEqual(configs['global_config'], expected) 637 638 def check_all_configs(self, testname, expected_config=None, 639 expected_preconfig=None, modify_path_cb=None, 640 stderr=None, *, api, preconfig_api=None, 641 env=None, ignore_stderr=False, cwd=None): 642 new_env = remove_python_envvars() 643 if env is not None: 644 new_env.update(env) 645 env = new_env 646 647 if preconfig_api is None: 648 preconfig_api = api 649 if preconfig_api == API_ISOLATED: 650 default_preconfig = self.PRE_CONFIG_ISOLATED 651 elif preconfig_api == API_PYTHON: 652 default_preconfig = self.PRE_CONFIG_PYTHON 653 else: 654 default_preconfig = self.PRE_CONFIG_COMPAT 655 if expected_preconfig is None: 656 expected_preconfig = {} 657 expected_preconfig = dict(default_preconfig, **expected_preconfig) 658 if expected_config is None: 659 expected_config = {} 660 661 if api == API_PYTHON: 662 default_config = self.CONFIG_PYTHON 663 elif api == API_ISOLATED: 664 default_config = self.CONFIG_ISOLATED 665 else: 666 default_config = self.CONFIG_COMPAT 667 expected_config = dict(default_config, **expected_config) 668 669 self.get_expected_config(expected_preconfig, 670 expected_config, env, 671 api, modify_path_cb) 672 673 out, err = self.run_embedded_interpreter(testname, 674 env=env, cwd=cwd) 675 if stderr is None and not expected_config['verbose']: 676 stderr = "" 677 if stderr is not None and not ignore_stderr: 678 self.assertEqual(err.rstrip(), stderr) 679 try: 680 configs = json.loads(out) 681 except json.JSONDecodeError: 682 self.fail(f"fail to decode stdout: {out!r}") 683 684 self.check_pre_config(configs, expected_preconfig) 685 self.check_config(configs, expected_config) 686 self.check_global_config(configs) 687 return configs 688 689 def test_init_default_config(self): 690 self.check_all_configs("test_init_initialize_config", api=API_COMPAT) 691 692 def test_preinit_compat_config(self): 693 self.check_all_configs("test_preinit_compat_config", api=API_COMPAT) 694 695 def test_init_compat_config(self): 696 self.check_all_configs("test_init_compat_config", api=API_COMPAT) 697 698 def test_init_global_config(self): 699 preconfig = { 700 'utf8_mode': 1, 701 } 702 config = { 703 'program_name': './globalvar', 704 'site_import': 0, 705 'bytes_warning': 1, 706 'warnoptions': ['default::BytesWarning'], 707 'inspect': 1, 708 'interactive': 1, 709 'optimization_level': 2, 710 'write_bytecode': 0, 711 'verbose': 1, 712 'quiet': 1, 713 'buffered_stdio': 0, 714 715 'user_site_directory': 0, 716 'pathconfig_warnings': 0, 717 } 718 self.check_all_configs("test_init_global_config", config, preconfig, 719 api=API_COMPAT) 720 721 def test_init_from_config(self): 722 preconfig = { 723 'allocator': PYMEM_ALLOCATOR_MALLOC, 724 'utf8_mode': 1, 725 } 726 config = { 727 'install_signal_handlers': 0, 728 'use_hash_seed': 1, 729 'hash_seed': 123, 730 'tracemalloc': 2, 731 'import_time': 1, 732 'show_ref_count': 1, 733 'show_alloc_count': 1, 734 'malloc_stats': 1, 735 736 'stdio_encoding': 'iso8859-1', 737 'stdio_errors': 'replace', 738 739 'pycache_prefix': 'conf_pycache_prefix', 740 'program_name': './conf_program_name', 741 'argv': ['-c', 'arg2', ], 742 'parse_argv': 1, 743 'xoptions': [ 744 'config_xoption1=3', 745 'config_xoption2=', 746 'config_xoption3', 747 'cmdline_xoption', 748 ], 749 'warnoptions': [ 750 'cmdline_warnoption', 751 'default::BytesWarning', 752 'config_warnoption', 753 ], 754 'run_command': 'pass\n', 755 756 'site_import': 0, 757 'bytes_warning': 1, 758 'inspect': 1, 759 'interactive': 1, 760 'optimization_level': 2, 761 'write_bytecode': 0, 762 'verbose': 1, 763 'quiet': 1, 764 'configure_c_stdio': 1, 765 'buffered_stdio': 0, 766 'user_site_directory': 0, 767 'faulthandler': 1, 768 769 'check_hash_pycs_mode': 'always', 770 'pathconfig_warnings': 0, 771 } 772 self.check_all_configs("test_init_from_config", config, preconfig, 773 api=API_COMPAT) 774 775 def test_init_compat_env(self): 776 preconfig = { 777 'allocator': PYMEM_ALLOCATOR_MALLOC, 778 } 779 config = { 780 'use_hash_seed': 1, 781 'hash_seed': 42, 782 'tracemalloc': 2, 783 'import_time': 1, 784 'malloc_stats': 1, 785 'inspect': 1, 786 'optimization_level': 2, 787 'pythonpath_env': '/my/path', 788 'pycache_prefix': 'env_pycache_prefix', 789 'write_bytecode': 0, 790 'verbose': 1, 791 'buffered_stdio': 0, 792 'stdio_encoding': 'iso8859-1', 793 'stdio_errors': 'replace', 794 'user_site_directory': 0, 795 'faulthandler': 1, 796 'warnoptions': ['EnvVar'], 797 } 798 self.check_all_configs("test_init_compat_env", config, preconfig, 799 api=API_COMPAT) 800 801 def test_init_python_env(self): 802 preconfig = { 803 'allocator': PYMEM_ALLOCATOR_MALLOC, 804 'utf8_mode': 1, 805 } 806 config = { 807 'use_hash_seed': 1, 808 'hash_seed': 42, 809 'tracemalloc': 2, 810 'import_time': 1, 811 'malloc_stats': 1, 812 'inspect': 1, 813 'optimization_level': 2, 814 'pythonpath_env': '/my/path', 815 'pycache_prefix': 'env_pycache_prefix', 816 'write_bytecode': 0, 817 'verbose': 1, 818 'buffered_stdio': 0, 819 'stdio_encoding': 'iso8859-1', 820 'stdio_errors': 'replace', 821 'user_site_directory': 0, 822 'faulthandler': 1, 823 'warnoptions': ['EnvVar'], 824 } 825 self.check_all_configs("test_init_python_env", config, preconfig, 826 api=API_PYTHON) 827 828 def test_init_env_dev_mode(self): 829 preconfig = dict(allocator=PYMEM_ALLOCATOR_DEBUG) 830 config = dict(dev_mode=1, 831 faulthandler=1, 832 warnoptions=['default']) 833 self.check_all_configs("test_init_env_dev_mode", config, preconfig, 834 api=API_COMPAT) 835 836 def test_init_env_dev_mode_alloc(self): 837 preconfig = dict(allocator=PYMEM_ALLOCATOR_MALLOC) 838 config = dict(dev_mode=1, 839 faulthandler=1, 840 warnoptions=['default']) 841 self.check_all_configs("test_init_env_dev_mode_alloc", config, preconfig, 842 api=API_COMPAT) 843 844 def test_init_dev_mode(self): 845 preconfig = { 846 'allocator': PYMEM_ALLOCATOR_DEBUG, 847 } 848 config = { 849 'faulthandler': 1, 850 'dev_mode': 1, 851 'warnoptions': ['default'], 852 } 853 self.check_all_configs("test_init_dev_mode", config, preconfig, 854 api=API_PYTHON) 855 856 def test_preinit_parse_argv(self): 857 # Pre-initialize implicitly using argv: make sure that -X dev 858 # is used to configure the allocation in preinitialization 859 preconfig = { 860 'allocator': PYMEM_ALLOCATOR_DEBUG, 861 } 862 config = { 863 'argv': ['script.py'], 864 'run_filename': 'script.py', 865 'dev_mode': 1, 866 'faulthandler': 1, 867 'warnoptions': ['default'], 868 'xoptions': ['dev'], 869 } 870 self.check_all_configs("test_preinit_parse_argv", config, preconfig, 871 api=API_PYTHON) 872 873 def test_preinit_dont_parse_argv(self): 874 # -X dev must be ignored by isolated preconfiguration 875 preconfig = { 876 'isolated': 0, 877 } 878 config = { 879 'argv': ["python3", "-E", "-I", 880 "-X", "dev", "-X", "utf8", "script.py"], 881 'isolated': 0, 882 } 883 self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig, 884 api=API_ISOLATED) 885 886 def test_init_isolated_flag(self): 887 config = { 888 'isolated': 1, 889 'use_environment': 0, 890 'user_site_directory': 0, 891 } 892 self.check_all_configs("test_init_isolated_flag", config, api=API_PYTHON) 893 894 def test_preinit_isolated1(self): 895 # _PyPreConfig.isolated=1, _PyCoreConfig.isolated not set 896 config = { 897 'isolated': 1, 898 'use_environment': 0, 899 'user_site_directory': 0, 900 } 901 self.check_all_configs("test_preinit_isolated1", config, api=API_COMPAT) 902 903 def test_preinit_isolated2(self): 904 # _PyPreConfig.isolated=0, _PyCoreConfig.isolated=1 905 config = { 906 'isolated': 1, 907 'use_environment': 0, 908 'user_site_directory': 0, 909 } 910 self.check_all_configs("test_preinit_isolated2", config, api=API_COMPAT) 911 912 def test_preinit_isolated_config(self): 913 self.check_all_configs("test_preinit_isolated_config", api=API_ISOLATED) 914 915 def test_init_isolated_config(self): 916 self.check_all_configs("test_init_isolated_config", api=API_ISOLATED) 917 918 def test_preinit_python_config(self): 919 self.check_all_configs("test_preinit_python_config", api=API_PYTHON) 920 921 def test_init_python_config(self): 922 self.check_all_configs("test_init_python_config", api=API_PYTHON) 923 924 def test_init_dont_configure_locale(self): 925 # _PyPreConfig.configure_locale=0 926 preconfig = { 927 'configure_locale': 0, 928 'coerce_c_locale': 0, 929 } 930 self.check_all_configs("test_init_dont_configure_locale", {}, preconfig, 931 api=API_PYTHON) 932 933 def test_init_read_set(self): 934 config = { 935 'program_name': './init_read_set', 936 'executable': 'my_executable', 937 } 938 def modify_path(path): 939 path.insert(1, "test_path_insert1") 940 path.append("test_path_append") 941 self.check_all_configs("test_init_read_set", config, 942 api=API_PYTHON, 943 modify_path_cb=modify_path) 944 945 def test_init_sys_add(self): 946 config = { 947 'faulthandler': 1, 948 'xoptions': [ 949 'config_xoption', 950 'cmdline_xoption', 951 'sysadd_xoption', 952 'faulthandler', 953 ], 954 'warnoptions': [ 955 'ignore:::cmdline_warnoption', 956 'ignore:::sysadd_warnoption', 957 'ignore:::config_warnoption', 958 ], 959 } 960 self.check_all_configs("test_init_sys_add", config, api=API_PYTHON) 961 962 def test_init_run_main(self): 963 code = ('import _testinternalcapi, json; ' 964 'print(json.dumps(_testinternalcapi.get_configs()))') 965 config = { 966 'argv': ['-c', 'arg2'], 967 'program_name': './python3', 968 'run_command': code + '\n', 969 'parse_argv': 1, 970 } 971 self.check_all_configs("test_init_run_main", config, api=API_PYTHON) 972 973 def test_init_main(self): 974 code = ('import _testinternalcapi, json; ' 975 'print(json.dumps(_testinternalcapi.get_configs()))') 976 config = { 977 'argv': ['-c', 'arg2'], 978 'program_name': './python3', 979 'run_command': code + '\n', 980 'parse_argv': 1, 981 '_init_main': 0, 982 } 983 self.check_all_configs("test_init_main", config, 984 api=API_PYTHON, 985 stderr="Run Python code before _Py_InitializeMain") 986 987 def test_init_parse_argv(self): 988 config = { 989 'parse_argv': 1, 990 'argv': ['-c', 'arg1', '-v', 'arg3'], 991 'program_name': './argv0', 992 'run_command': 'pass\n', 993 'use_environment': 0, 994 } 995 self.check_all_configs("test_init_parse_argv", config, api=API_PYTHON) 996 997 def test_init_dont_parse_argv(self): 998 pre_config = { 999 'parse_argv': 0, 1000 } 1001 config = { 1002 'parse_argv': 0, 1003 'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], 1004 'program_name': './argv0', 1005 } 1006 self.check_all_configs("test_init_dont_parse_argv", config, pre_config, 1007 api=API_PYTHON) 1008 1009 def default_program_name(self, config): 1010 if MS_WINDOWS: 1011 program_name = 'python' 1012 executable = self.test_exe 1013 else: 1014 program_name = 'python3' 1015 if MACOS: 1016 executable = self.test_exe 1017 else: 1018 executable = shutil.which(program_name) or '' 1019 config.update({ 1020 'program_name': program_name, 1021 'base_executable': executable, 1022 'executable': executable, 1023 }) 1024 1025 def test_init_setpath(self): 1026 # Test Py_SetPath() 1027 config = self._get_expected_config() 1028 paths = config['config']['module_search_paths'] 1029 1030 config = { 1031 'module_search_paths': paths, 1032 'prefix': '', 1033 'base_prefix': '', 1034 'exec_prefix': '', 1035 'base_exec_prefix': '', 1036 } 1037 self.default_program_name(config) 1038 env = {'TESTPATH': os.path.pathsep.join(paths)} 1039 1040 self.check_all_configs("test_init_setpath", config, 1041 api=API_COMPAT, env=env, 1042 ignore_stderr=True) 1043 1044 def test_init_setpath_config(self): 1045 # Test Py_SetPath() with PyConfig 1046 config = self._get_expected_config() 1047 paths = config['config']['module_search_paths'] 1048 1049 config = { 1050 # set by Py_SetPath() 1051 'module_search_paths': paths, 1052 'prefix': '', 1053 'base_prefix': '', 1054 'exec_prefix': '', 1055 'base_exec_prefix': '', 1056 # overriden by PyConfig 1057 'program_name': 'conf_program_name', 1058 'base_executable': 'conf_executable', 1059 'executable': 'conf_executable', 1060 } 1061 env = {'TESTPATH': os.path.pathsep.join(paths)} 1062 self.check_all_configs("test_init_setpath_config", config, 1063 api=API_PYTHON, env=env, ignore_stderr=True) 1064 1065 def module_search_paths(self, prefix=None, exec_prefix=None): 1066 config = self._get_expected_config() 1067 if prefix is None: 1068 prefix = config['config']['prefix'] 1069 if exec_prefix is None: 1070 exec_prefix = config['config']['prefix'] 1071 if MS_WINDOWS: 1072 return config['config']['module_search_paths'] 1073 else: 1074 ver = sys.version_info 1075 return [ 1076 os.path.join(prefix, 'lib', 1077 f'python{ver.major}{ver.minor}.zip'), 1078 os.path.join(prefix, 'lib', 1079 f'python{ver.major}.{ver.minor}'), 1080 os.path.join(exec_prefix, 'lib', 1081 f'python{ver.major}.{ver.minor}', 'lib-dynload'), 1082 ] 1083 1084 @contextlib.contextmanager 1085 def tmpdir_with_python(self): 1086 # Temporary directory with a copy of the Python program 1087 with tempfile.TemporaryDirectory() as tmpdir: 1088 # bpo-38234: On macOS and FreeBSD, the temporary directory 1089 # can be symbolic link. For example, /tmp can be a symbolic link 1090 # to /var/tmp. Call realpath() to resolve all symbolic links. 1091 tmpdir = os.path.realpath(tmpdir) 1092 1093 if MS_WINDOWS: 1094 # Copy pythonXY.dll (or pythonXY_d.dll) 1095 ver = sys.version_info 1096 dll = f'python{ver.major}{ver.minor}' 1097 dll3 = f'python{ver.major}' 1098 if debug_build(sys.executable): 1099 dll += '_d' 1100 dll3 += '_d' 1101 dll += '.dll' 1102 dll3 += '.dll' 1103 dll = os.path.join(os.path.dirname(self.test_exe), dll) 1104 dll3 = os.path.join(os.path.dirname(self.test_exe), dll3) 1105 dll_copy = os.path.join(tmpdir, os.path.basename(dll)) 1106 dll3_copy = os.path.join(tmpdir, os.path.basename(dll3)) 1107 shutil.copyfile(dll, dll_copy) 1108 shutil.copyfile(dll3, dll3_copy) 1109 1110 # Copy Python program 1111 exec_copy = os.path.join(tmpdir, os.path.basename(self.test_exe)) 1112 shutil.copyfile(self.test_exe, exec_copy) 1113 shutil.copystat(self.test_exe, exec_copy) 1114 self.test_exe = exec_copy 1115 1116 yield tmpdir 1117 1118 def test_init_setpythonhome(self): 1119 # Test Py_SetPythonHome(home) with PYTHONPATH env var 1120 config = self._get_expected_config() 1121 paths = config['config']['module_search_paths'] 1122 paths_str = os.path.pathsep.join(paths) 1123 1124 for path in paths: 1125 if not os.path.isdir(path): 1126 continue 1127 if os.path.exists(os.path.join(path, 'os.py')): 1128 home = os.path.dirname(path) 1129 break 1130 else: 1131 self.fail(f"Unable to find home in {paths!r}") 1132 1133 prefix = exec_prefix = home 1134 ver = sys.version_info 1135 expected_paths = self.module_search_paths(prefix=home, exec_prefix=home) 1136 1137 config = { 1138 'home': home, 1139 'module_search_paths': expected_paths, 1140 'prefix': prefix, 1141 'base_prefix': prefix, 1142 'exec_prefix': exec_prefix, 1143 'base_exec_prefix': exec_prefix, 1144 'pythonpath_env': paths_str, 1145 } 1146 self.default_program_name(config) 1147 env = {'TESTHOME': home, 'PYTHONPATH': paths_str} 1148 self.check_all_configs("test_init_setpythonhome", config, 1149 api=API_COMPAT, env=env) 1150 1151 def copy_paths_by_env(self, config): 1152 all_configs = self._get_expected_config() 1153 paths = all_configs['config']['module_search_paths'] 1154 paths_str = os.path.pathsep.join(paths) 1155 config['pythonpath_env'] = paths_str 1156 env = {'PYTHONPATH': paths_str} 1157 return env 1158 1159 @unittest.skipIf(MS_WINDOWS, 'Windows does not use pybuilddir.txt') 1160 def test_init_pybuilddir(self): 1161 # Test path configuration with pybuilddir.txt configuration file 1162 1163 with self.tmpdir_with_python() as tmpdir: 1164 # pybuilddir.txt is a sub-directory relative to the current 1165 # directory (tmpdir) 1166 subdir = 'libdir' 1167 libdir = os.path.join(tmpdir, subdir) 1168 os.mkdir(libdir) 1169 1170 filename = os.path.join(tmpdir, 'pybuilddir.txt') 1171 with open(filename, "w", encoding="utf8") as fp: 1172 fp.write(subdir) 1173 1174 module_search_paths = self.module_search_paths() 1175 module_search_paths[-1] = libdir 1176 1177 executable = self.test_exe 1178 config = { 1179 'base_executable': executable, 1180 'executable': executable, 1181 'module_search_paths': module_search_paths, 1182 } 1183 env = self.copy_paths_by_env(config) 1184 self.check_all_configs("test_init_compat_config", config, 1185 api=API_COMPAT, env=env, 1186 ignore_stderr=True, cwd=tmpdir) 1187 1188 def test_init_pyvenv_cfg(self): 1189 # Test path configuration with pyvenv.cfg configuration file 1190 1191 with self.tmpdir_with_python() as tmpdir, \ 1192 tempfile.TemporaryDirectory() as pyvenv_home: 1193 ver = sys.version_info 1194 1195 if not MS_WINDOWS: 1196 lib_dynload = os.path.join(pyvenv_home, 1197 'lib', 1198 f'python{ver.major}.{ver.minor}', 1199 'lib-dynload') 1200 os.makedirs(lib_dynload) 1201 else: 1202 lib_dynload = os.path.join(pyvenv_home, 'lib') 1203 os.makedirs(lib_dynload) 1204 # getpathp.c uses Lib\os.py as the LANDMARK 1205 shutil.copyfile(os.__file__, os.path.join(lib_dynload, 'os.py')) 1206 1207 filename = os.path.join(tmpdir, 'pyvenv.cfg') 1208 with open(filename, "w", encoding="utf8") as fp: 1209 print("home = %s" % pyvenv_home, file=fp) 1210 print("include-system-site-packages = false", file=fp) 1211 1212 paths = self.module_search_paths() 1213 if not MS_WINDOWS: 1214 paths[-1] = lib_dynload 1215 else: 1216 for index, path in enumerate(paths): 1217 if index == 0: 1218 paths[index] = os.path.join(tmpdir, os.path.basename(path)) 1219 else: 1220 paths[index] = os.path.join(pyvenv_home, os.path.basename(path)) 1221 paths[-1] = pyvenv_home 1222 1223 executable = self.test_exe 1224 exec_prefix = pyvenv_home 1225 config = { 1226 'base_exec_prefix': exec_prefix, 1227 'exec_prefix': exec_prefix, 1228 'base_executable': executable, 1229 'executable': executable, 1230 'module_search_paths': paths, 1231 } 1232 if MS_WINDOWS: 1233 config['base_prefix'] = pyvenv_home 1234 config['prefix'] = pyvenv_home 1235 env = self.copy_paths_by_env(config) 1236 actual = self.check_all_configs("test_init_compat_config", config, 1237 api=API_COMPAT, env=env, 1238 ignore_stderr=True, cwd=tmpdir) 1239 if MS_WINDOWS: 1240 self.assertEqual( 1241 actual['windows']['python3_dll'], 1242 os.path.join( 1243 tmpdir, 1244 os.path.basename(self.EXPECTED_CONFIG['windows']['python3_dll']) 1245 ) 1246 ) 1247 1248 1249 def test_global_pathconfig(self): 1250 # Test C API functions getting the path configuration: 1251 # 1252 # - Py_GetExecPrefix() 1253 # - Py_GetPath() 1254 # - Py_GetPrefix() 1255 # - Py_GetProgramFullPath() 1256 # - Py_GetProgramName() 1257 # - Py_GetPythonHome() 1258 # 1259 # The global path configuration (_Py_path_config) must be a copy 1260 # of the path configuration of PyInterpreter.config (PyConfig). 1261 ctypes = support.import_module('ctypes') 1262 _testinternalcapi = support.import_module('_testinternalcapi') 1263 1264 def get_func(name): 1265 func = getattr(ctypes.pythonapi, name) 1266 func.argtypes = () 1267 func.restype = ctypes.c_wchar_p 1268 return func 1269 1270 Py_GetPath = get_func('Py_GetPath') 1271 Py_GetPrefix = get_func('Py_GetPrefix') 1272 Py_GetExecPrefix = get_func('Py_GetExecPrefix') 1273 Py_GetProgramName = get_func('Py_GetProgramName') 1274 Py_GetProgramFullPath = get_func('Py_GetProgramFullPath') 1275 Py_GetPythonHome = get_func('Py_GetPythonHome') 1276 1277 config = _testinternalcapi.get_configs()['config'] 1278 1279 self.assertEqual(Py_GetPath().split(os.path.pathsep), 1280 config['module_search_paths']) 1281 self.assertEqual(Py_GetPrefix(), config['prefix']) 1282 self.assertEqual(Py_GetExecPrefix(), config['exec_prefix']) 1283 self.assertEqual(Py_GetProgramName(), config['program_name']) 1284 self.assertEqual(Py_GetProgramFullPath(), config['executable']) 1285 self.assertEqual(Py_GetPythonHome(), config['home']) 1286 1287 def test_init_warnoptions(self): 1288 # lowest to highest priority 1289 warnoptions = [ 1290 'ignore:::PyConfig_Insert0', # PyWideStringList_Insert(0) 1291 'default', # PyConfig.dev_mode=1 1292 'ignore:::env1', # PYTHONWARNINGS env var 1293 'ignore:::env2', # PYTHONWARNINGS env var 1294 'ignore:::cmdline1', # -W opt command line option 1295 'ignore:::cmdline2', # -W opt command line option 1296 'default::BytesWarning', # PyConfig.bytes_warnings=1 1297 'ignore:::PySys_AddWarnOption1', # PySys_AddWarnOption() 1298 'ignore:::PySys_AddWarnOption2', # PySys_AddWarnOption() 1299 'ignore:::PyConfig_BeforeRead', # PyConfig.warnoptions 1300 'ignore:::PyConfig_AfterRead'] # PyWideStringList_Append() 1301 preconfig = dict(allocator=PYMEM_ALLOCATOR_DEBUG) 1302 config = { 1303 'dev_mode': 1, 1304 'faulthandler': 1, 1305 'bytes_warning': 1, 1306 'warnoptions': warnoptions, 1307 } 1308 self.check_all_configs("test_init_warnoptions", config, preconfig, 1309 api=API_PYTHON) 1310 1311 1312class AuditingTests(EmbeddingTestsMixin, unittest.TestCase): 1313 def test_open_code_hook(self): 1314 self.run_embedded_interpreter("test_open_code_hook") 1315 1316 def test_audit(self): 1317 self.run_embedded_interpreter("test_audit") 1318 1319 def test_audit_subinterpreter(self): 1320 self.run_embedded_interpreter("test_audit_subinterpreter") 1321 1322 def test_audit_run_command(self): 1323 self.run_embedded_interpreter("test_audit_run_command", timeout=3, returncode=1) 1324 1325 def test_audit_run_file(self): 1326 self.run_embedded_interpreter("test_audit_run_file", timeout=3, returncode=1) 1327 1328 def test_audit_run_interactivehook(self): 1329 startup = os.path.join(self.oldcwd, support.TESTFN) + (support.FS_NONASCII or '') + ".py" 1330 with open(startup, "w", encoding="utf-8") as f: 1331 print("import sys", file=f) 1332 print("sys.__interactivehook__ = lambda: None", file=f) 1333 try: 1334 env = {**remove_python_envvars(), "PYTHONSTARTUP": startup} 1335 self.run_embedded_interpreter("test_audit_run_interactivehook", timeout=5, 1336 returncode=10, env=env) 1337 finally: 1338 os.unlink(startup) 1339 1340 def test_audit_run_startup(self): 1341 startup = os.path.join(self.oldcwd, support.TESTFN) + (support.FS_NONASCII or '') + ".py" 1342 with open(startup, "w", encoding="utf-8") as f: 1343 print("pass", file=f) 1344 try: 1345 env = {**remove_python_envvars(), "PYTHONSTARTUP": startup} 1346 self.run_embedded_interpreter("test_audit_run_startup", timeout=5, 1347 returncode=10, env=env) 1348 finally: 1349 os.unlink(startup) 1350 1351 def test_audit_run_stdin(self): 1352 self.run_embedded_interpreter("test_audit_run_stdin", timeout=3, returncode=1) 1353 1354if __name__ == "__main__": 1355 unittest.main() 1356