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