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