blob: 5a5419dcfcf57efff8bba4403a2dcc58d39e5a5e [file] [log] [blame]
Nick Coghlan39f0bb52017-11-28 08:11:51 +10001# Run the tests in Programs/_testembed.c (tests for the CPython embedding APIs)
2from test import support
3import unittest
4
5from collections import namedtuple
Victor Stinner7ddd56f2018-11-14 00:24:28 +01006import json
Nick Coghlan39f0bb52017-11-28 08:11:51 +10007import os
Michael Feltd2067312018-09-15 11:28:31 +02008import platform
Nick Coghlan39f0bb52017-11-28 08:11:51 +10009import re
10import subprocess
11import sys
Victor Stinnera6537fb2018-11-26 11:54:12 +010012import textwrap
Nick Coghlan39f0bb52017-11-28 08:11:51 +100013
14
Victor Stinner01de89c2018-11-14 17:39:45 +010015MS_WINDOWS = (os.name == 'nt')
Victor Stinnerb16b4e42019-05-17 15:20:52 +020016PYMEM_ALLOCATOR_NOT_SET = 0
17PYMEM_ALLOCATOR_DEBUG = 2
18PYMEM_ALLOCATOR_MALLOC = 3
Victor Stinner01de89c2018-11-14 17:39:45 +010019
20
Victor Stinner56b29b62018-07-26 18:57:56 +020021class EmbeddingTestsMixin:
Nick Coghlan39f0bb52017-11-28 08:11:51 +100022 def setUp(self):
23 here = os.path.abspath(__file__)
24 basepath = os.path.dirname(os.path.dirname(os.path.dirname(here)))
25 exename = "_testembed"
Victor Stinner01de89c2018-11-14 17:39:45 +010026 if MS_WINDOWS:
Nick Coghlan39f0bb52017-11-28 08:11:51 +100027 ext = ("_d" if "_d" in sys.executable else "") + ".exe"
28 exename += ext
29 exepath = os.path.dirname(sys.executable)
30 else:
31 exepath = os.path.join(basepath, "Programs")
32 self.test_exe = exe = os.path.join(exepath, exename)
33 if not os.path.exists(exe):
34 self.skipTest("%r doesn't exist" % exe)
35 # This is needed otherwise we get a fatal error:
36 # "Py_Initialize: Unable to get the locale encoding
37 # LookupError: no codec search functions registered: can't find encoding"
38 self.oldcwd = os.getcwd()
39 os.chdir(basepath)
40
41 def tearDown(self):
42 os.chdir(self.oldcwd)
43
44 def run_embedded_interpreter(self, *args, env=None):
45 """Runs a test in the embedded interpreter"""
46 cmd = [self.test_exe]
47 cmd.extend(args)
Victor Stinner01de89c2018-11-14 17:39:45 +010048 if env is not None and MS_WINDOWS:
Nick Coghlan39f0bb52017-11-28 08:11:51 +100049 # Windows requires at least the SYSTEMROOT environment variable to
50 # start Python.
51 env = env.copy()
52 env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
53
54 p = subprocess.Popen(cmd,
55 stdout=subprocess.PIPE,
56 stderr=subprocess.PIPE,
57 universal_newlines=True,
58 env=env)
Victor Stinner2f549082019-03-29 15:13:46 +010059 try:
60 (out, err) = p.communicate()
61 except:
62 p.terminate()
63 p.wait()
64 raise
Nick Coghlan39f0bb52017-11-28 08:11:51 +100065 if p.returncode != 0 and support.verbose:
66 print(f"--- {cmd} failed ---")
67 print(f"stdout:\n{out}")
Nick Coghlanbc77eff2018-03-25 20:44:30 +100068 print(f"stderr:\n{err}")
Nick Coghlan39f0bb52017-11-28 08:11:51 +100069 print(f"------")
70
71 self.assertEqual(p.returncode, 0,
72 "bad returncode %d, stderr is %r" %
73 (p.returncode, err))
74 return out, err
75
76 def run_repeated_init_and_subinterpreters(self):
77 out, err = self.run_embedded_interpreter("repeated_init_and_subinterpreters")
78 self.assertEqual(err, "")
79
80 # The output from _testembed looks like this:
81 # --- Pass 0 ---
82 # interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
83 # interp 1 <0x1d4f690>, thread state <0x1d35350>: id(modules) = 139650431165784
84 # interp 2 <0x1d5a690>, thread state <0x1d99ed0>: id(modules) = 139650413140368
85 # interp 3 <0x1d4f690>, thread state <0x1dc3340>: id(modules) = 139650412862200
86 # interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
87 # --- Pass 1 ---
88 # ...
89
90 interp_pat = (r"^interp (\d+) <(0x[\dA-F]+)>, "
91 r"thread state <(0x[\dA-F]+)>: "
92 r"id\(modules\) = ([\d]+)$")
93 Interp = namedtuple("Interp", "id interp tstate modules")
94
95 numloops = 0
96 current_run = []
97 for line in out.splitlines():
98 if line == "--- Pass {} ---".format(numloops):
99 self.assertEqual(len(current_run), 0)
Nick Coghlanbc77eff2018-03-25 20:44:30 +1000100 if support.verbose > 1:
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000101 print(line)
102 numloops += 1
103 continue
104
105 self.assertLess(len(current_run), 5)
106 match = re.match(interp_pat, line)
107 if match is None:
108 self.assertRegex(line, interp_pat)
109
110 # Parse the line from the loop. The first line is the main
111 # interpreter and the 3 afterward are subinterpreters.
112 interp = Interp(*match.groups())
Nick Coghlanbc77eff2018-03-25 20:44:30 +1000113 if support.verbose > 1:
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000114 print(interp)
115 self.assertTrue(interp.interp)
116 self.assertTrue(interp.tstate)
117 self.assertTrue(interp.modules)
118 current_run.append(interp)
119
120 # The last line in the loop should be the same as the first.
121 if len(current_run) == 5:
122 main = current_run[0]
123 self.assertEqual(interp, main)
124 yield current_run
125 current_run = []
126
Victor Stinner56b29b62018-07-26 18:57:56 +0200127
128class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000129 def test_subinterps_main(self):
130 for run in self.run_repeated_init_and_subinterpreters():
131 main = run[0]
132
133 self.assertEqual(main.id, '0')
134
135 def test_subinterps_different_ids(self):
136 for run in self.run_repeated_init_and_subinterpreters():
137 main, *subs, _ = run
138
139 mainid = int(main.id)
140 for i, sub in enumerate(subs):
141 self.assertEqual(sub.id, str(mainid + i + 1))
142
143 def test_subinterps_distinct_state(self):
144 for run in self.run_repeated_init_and_subinterpreters():
145 main, *subs, _ = run
146
147 if '0x0' in main:
148 # XXX Fix on Windows (and other platforms): something
149 # is going on with the pointers in Programs/_testembed.c.
150 # interp.interp is 0x0 and interp.modules is the same
151 # between interpreters.
152 raise unittest.SkipTest('platform prints pointers as 0x0')
153
154 for sub in subs:
155 # A new subinterpreter may have the same
156 # PyInterpreterState pointer as a previous one if
157 # the earlier one has already been destroyed. So
158 # we compare with the main interpreter. The same
159 # applies to tstate.
160 self.assertNotEqual(sub.interp, main.interp)
161 self.assertNotEqual(sub.tstate, main.tstate)
162 self.assertNotEqual(sub.modules, main.modules)
163
164 def test_forced_io_encoding(self):
165 # Checks forced configuration of embedded interpreter IO streams
166 env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
167 out, err = self.run_embedded_interpreter("forced_io_encoding", env=env)
168 if support.verbose > 1:
169 print()
170 print(out)
171 print(err)
172 expected_stream_encoding = "utf-8"
173 expected_errors = "surrogateescape"
174 expected_output = '\n'.join([
175 "--- Use defaults ---",
176 "Expected encoding: default",
177 "Expected errors: default",
178 "stdin: {in_encoding}:{errors}",
179 "stdout: {out_encoding}:{errors}",
180 "stderr: {out_encoding}:backslashreplace",
181 "--- Set errors only ---",
182 "Expected encoding: default",
183 "Expected errors: ignore",
184 "stdin: {in_encoding}:ignore",
185 "stdout: {out_encoding}:ignore",
186 "stderr: {out_encoding}:backslashreplace",
187 "--- Set encoding only ---",
Victor Stinner9e4994d2018-08-28 23:26:33 +0200188 "Expected encoding: iso8859-1",
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000189 "Expected errors: default",
Victor Stinner9e4994d2018-08-28 23:26:33 +0200190 "stdin: iso8859-1:{errors}",
191 "stdout: iso8859-1:{errors}",
192 "stderr: iso8859-1:backslashreplace",
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000193 "--- Set encoding and errors ---",
Victor Stinner9e4994d2018-08-28 23:26:33 +0200194 "Expected encoding: iso8859-1",
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000195 "Expected errors: replace",
Victor Stinner9e4994d2018-08-28 23:26:33 +0200196 "stdin: iso8859-1:replace",
197 "stdout: iso8859-1:replace",
198 "stderr: iso8859-1:backslashreplace"])
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000199 expected_output = expected_output.format(
200 in_encoding=expected_stream_encoding,
201 out_encoding=expected_stream_encoding,
202 errors=expected_errors)
203 # This is useful if we ever trip over odd platform behaviour
204 self.maxDiff = None
205 self.assertEqual(out.strip(), expected_output)
206
207 def test_pre_initialization_api(self):
208 """
Nick Coghlanbc77eff2018-03-25 20:44:30 +1000209 Checks some key parts of the C-API that need to work before the runtine
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000210 is initialized (via Py_Initialize()).
211 """
212 env = dict(os.environ, PYTHONPATH=os.pathsep.join(sys.path))
213 out, err = self.run_embedded_interpreter("pre_initialization_api", env=env)
Victor Stinner01de89c2018-11-14 17:39:45 +0100214 if MS_WINDOWS:
Nick Coghlanbc77eff2018-03-25 20:44:30 +1000215 expected_path = self.test_exe
216 else:
217 expected_path = os.path.join(os.getcwd(), "spam")
218 expected_output = f"sys.executable: {expected_path}\n"
219 self.assertIn(expected_output, out)
220 self.assertEqual(err, '')
221
222 def test_pre_initialization_sys_options(self):
223 """
224 Checks that sys.warnoptions and sys._xoptions can be set before the
225 runtime is initialized (otherwise they won't be effective).
226 """
Pablo Galindo41148462018-04-27 13:23:13 +0100227 env = dict(os.environ, PYTHONPATH=os.pathsep.join(sys.path))
Nick Coghlanbc77eff2018-03-25 20:44:30 +1000228 out, err = self.run_embedded_interpreter(
229 "pre_initialization_sys_options", env=env)
230 expected_output = (
231 "sys.warnoptions: ['once', 'module', 'default']\n"
232 "sys._xoptions: {'not_an_option': '1', 'also_not_an_option': '2'}\n"
233 "warnings.filters[:3]: ['default', 'module', 'once']\n"
234 )
235 self.assertIn(expected_output, out)
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000236 self.assertEqual(err, '')
237
Victor Stinnerb4d1e1f2017-11-30 22:05:00 +0100238 def test_bpo20891(self):
239 """
240 bpo-20891: Calling PyGILState_Ensure in a non-Python thread before
241 calling PyEval_InitThreads() must not crash. PyGILState_Ensure() must
242 call PyEval_InitThreads() for us in this case.
243 """
244 out, err = self.run_embedded_interpreter("bpo20891")
245 self.assertEqual(out, '')
246 self.assertEqual(err, '')
247
Victor Stinner209abf72018-06-22 19:14:51 +0200248 def test_initialize_twice(self):
249 """
250 bpo-33932: Calling Py_Initialize() twice should do nothing (and not
251 crash!).
252 """
253 out, err = self.run_embedded_interpreter("initialize_twice")
254 self.assertEqual(out, '')
255 self.assertEqual(err, '')
256
Victor Stinnerfb47bca2018-07-20 17:34:23 +0200257 def test_initialize_pymain(self):
258 """
259 bpo-34008: Calling Py_Main() after Py_Initialize() must not fail.
260 """
261 out, err = self.run_embedded_interpreter("initialize_pymain")
262 self.assertEqual(out.rstrip(), "Py_Main() after Py_Initialize: sys.argv=['-c', 'arg2']")
263 self.assertEqual(err, '')
264
Victor Stinner2f549082019-03-29 15:13:46 +0100265 def test_run_main(self):
266 out, err = self.run_embedded_interpreter("run_main")
267 self.assertEqual(out.rstrip(), "_Py_RunMain(): sys.argv=['-c', 'arg2']")
268 self.assertEqual(err, '')
269
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000270
Victor Stinner56b29b62018-07-26 18:57:56 +0200271class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
272 maxDiff = 4096
Victor Stinner01de89c2018-11-14 17:39:45 +0100273 UTF8_MODE_ERRORS = ('surrogatepass' if MS_WINDOWS else 'surrogateescape')
274
Victor Stinnerbcfbbd72019-05-17 22:44:16 +0200275 # Marker to read the default configuration: get_default_config()
Victor Stinnera6537fb2018-11-26 11:54:12 +0100276 GET_DEFAULT_CONFIG = object()
Victor Stinnerbcfbbd72019-05-17 22:44:16 +0200277
278 # Marker to ignore a configuration parameter
279 IGNORE_CONFIG = object()
280
Victor Stinner1075d162019-03-25 23:19:57 +0100281 DEFAULT_PRE_CONFIG = {
Victor Stinnerb16b4e42019-05-17 15:20:52 +0200282 'allocator': PYMEM_ALLOCATOR_NOT_SET,
Victor Stinnerbcfbbd72019-05-17 22:44:16 +0200283 'configure_locale': 1,
Victor Stinner1075d162019-03-25 23:19:57 +0100284 'coerce_c_locale': 0,
285 'coerce_c_locale_warn': 0,
Victor Stinner1075d162019-03-25 23:19:57 +0100286 'utf8_mode': 0,
287 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200288 ISOLATED_PRE_CONFIG = dict(DEFAULT_PRE_CONFIG,
289 configure_locale=0,
290 isolated=1,
291 use_environment=0,
292 utf8_mode=0,
293 dev_mode=0,
294 )
295 if MS_WINDOWS:
296 ISOLATED_PRE_CONFIG['legacy_windows_fs_encoding'] = 0
297
Victor Stinner20004952019-03-26 02:31:11 +0100298 COPY_PRE_CONFIG = [
299 'dev_mode',
300 'isolated',
301 'use_environment',
302 ]
303
Victor Stinner00b137c2018-11-13 19:59:26 +0100304 DEFAULT_CORE_CONFIG = {
Victor Stinner20004952019-03-26 02:31:11 +0100305 'isolated': 0,
306 'use_environment': 1,
307 'dev_mode': 0,
308
Victor Stinner56b29b62018-07-26 18:57:56 +0200309 'install_signal_handlers': 1,
Victor Stinner56b29b62018-07-26 18:57:56 +0200310 'use_hash_seed': 0,
311 'hash_seed': 0,
Victor Stinner56b29b62018-07-26 18:57:56 +0200312 'faulthandler': 0,
313 'tracemalloc': 0,
314 'import_time': 0,
315 'show_ref_count': 0,
316 'show_alloc_count': 0,
317 'dump_refs': 0,
318 'malloc_stats': 0,
Victor Stinner56b29b62018-07-26 18:57:56 +0200319
Victor Stinnera6537fb2018-11-26 11:54:12 +0100320 'filesystem_encoding': GET_DEFAULT_CONFIG,
321 'filesystem_errors': GET_DEFAULT_CONFIG,
Victor Stinnerc5989cd2018-08-29 19:32:47 +0200322
Victor Stinner7ddd56f2018-11-14 00:24:28 +0100323 'pycache_prefix': None,
Victor Stinner91c99872019-05-14 22:01:51 +0200324 'program_name': GET_DEFAULT_CONFIG,
Victor Stinnercab5d072019-05-17 19:01:14 +0200325 'parse_argv': 0,
Victor Stinner62599762019-03-15 16:03:23 +0100326 'argv': [""],
Victor Stinner7ddd56f2018-11-14 00:24:28 +0100327
328 'xoptions': [],
329 'warnoptions': [],
Victor Stinner56b29b62018-07-26 18:57:56 +0200330
Victor Stinner01de89c2018-11-14 17:39:45 +0100331 'module_search_path_env': None,
332 'home': None,
Victor Stinner91c99872019-05-14 22:01:51 +0200333 'executable': GET_DEFAULT_CONFIG,
Victor Stinnera6537fb2018-11-26 11:54:12 +0100334
335 'prefix': GET_DEFAULT_CONFIG,
336 'base_prefix': GET_DEFAULT_CONFIG,
337 'exec_prefix': GET_DEFAULT_CONFIG,
338 'base_exec_prefix': GET_DEFAULT_CONFIG,
Victor Stinner5eb8b072019-05-15 02:12:48 +0200339 'module_search_paths': GET_DEFAULT_CONFIG,
Victor Stinner01de89c2018-11-14 17:39:45 +0100340
Victor Stinner56b29b62018-07-26 18:57:56 +0200341 'site_import': 1,
342 'bytes_warning': 0,
343 'inspect': 0,
344 'interactive': 0,
345 'optimization_level': 0,
Victor Stinner98512272018-08-01 03:07:00 +0200346 'parser_debug': 0,
Victor Stinner56b29b62018-07-26 18:57:56 +0200347 'write_bytecode': 1,
348 'verbose': 0,
349 'quiet': 0,
350 'user_site_directory': 1,
Victor Stinnercab5d072019-05-17 19:01:14 +0200351 'configure_c_stdio': 0,
Victor Stinner98512272018-08-01 03:07:00 +0200352 'buffered_stdio': 1,
Victor Stinnerc5989cd2018-08-29 19:32:47 +0200353
Victor Stinnera6537fb2018-11-26 11:54:12 +0100354 'stdio_encoding': GET_DEFAULT_CONFIG,
355 'stdio_errors': GET_DEFAULT_CONFIG,
Victor Stinner56b29b62018-07-26 18:57:56 +0200356
Victor Stinner62be7632019-03-01 13:10:14 +0100357 'skip_source_first_line': 0,
358 'run_command': None,
359 'run_module': None,
360 'run_filename': None,
361
Victor Stinner56b29b62018-07-26 18:57:56 +0200362 '_install_importlib': 1,
Victor Stinnercb9fbd32019-05-01 23:51:56 -0400363 'check_hash_pycs_mode': 'default',
Victor Stinner9ef5dca2019-05-16 17:38:16 +0200364 'pathconfig_warnings': 1,
365 '_init_main': 1,
Victor Stinner56b29b62018-07-26 18:57:56 +0200366 }
Victor Stinner01de89c2018-11-14 17:39:45 +0100367 if MS_WINDOWS:
Victor Stinner1075d162019-03-25 23:19:57 +0100368 DEFAULT_PRE_CONFIG.update({
Victor Stinner01de89c2018-11-14 17:39:45 +0100369 'legacy_windows_fs_encoding': 0,
Victor Stinner1075d162019-03-25 23:19:57 +0100370 })
371 DEFAULT_CORE_CONFIG.update({
Victor Stinner01de89c2018-11-14 17:39:45 +0100372 'legacy_windows_stdio': 0,
373 })
374
Victor Stinnerbab0db62019-05-18 03:21:27 +0200375 PYTHON_CORE_CONFIG = dict(DEFAULT_CORE_CONFIG,
376 configure_c_stdio=1,
377 parse_argv=1,
378 )
379 ISOLATED_CORE_CONFIG = dict(DEFAULT_CORE_CONFIG,
380 isolated=1,
381 use_environment=0,
382 user_site_directory=0,
383 dev_mode=0,
384 install_signal_handlers=0,
385 use_hash_seed=0,
386 faulthandler=0,
387 tracemalloc=0,
388 pathconfig_warnings=0,
389 )
390 if MS_WINDOWS:
391 ISOLATED_CORE_CONFIG['legacy_windows_stdio'] = 0
392
Victor Stinner01de89c2018-11-14 17:39:45 +0100393 # global config
394 DEFAULT_GLOBAL_CONFIG = {
395 'Py_HasFileSystemDefaultEncoding': 0,
396 'Py_HashRandomizationFlag': 1,
397 '_Py_HasFileSystemDefaultEncodeErrors': 0,
398 }
Victor Stinner1075d162019-03-25 23:19:57 +0100399 COPY_GLOBAL_PRE_CONFIG = [
Victor Stinner1075d162019-03-25 23:19:57 +0100400 ('Py_UTF8Mode', 'utf8_mode'),
401 ]
Victor Stinner01de89c2018-11-14 17:39:45 +0100402 COPY_GLOBAL_CONFIG = [
403 # Copy core config to global config for expected values
404 # True means that the core config value is inverted (0 => 1 and 1 => 0)
405 ('Py_BytesWarningFlag', 'bytes_warning'),
406 ('Py_DebugFlag', 'parser_debug'),
407 ('Py_DontWriteBytecodeFlag', 'write_bytecode', True),
408 ('Py_FileSystemDefaultEncodeErrors', 'filesystem_errors'),
409 ('Py_FileSystemDefaultEncoding', 'filesystem_encoding'),
Victor Stinner9ef5dca2019-05-16 17:38:16 +0200410 ('Py_FrozenFlag', 'pathconfig_warnings', True),
Victor Stinner20004952019-03-26 02:31:11 +0100411 ('Py_IgnoreEnvironmentFlag', 'use_environment', True),
Victor Stinner01de89c2018-11-14 17:39:45 +0100412 ('Py_InspectFlag', 'inspect'),
413 ('Py_InteractiveFlag', 'interactive'),
Victor Stinner20004952019-03-26 02:31:11 +0100414 ('Py_IsolatedFlag', 'isolated'),
Victor Stinner01de89c2018-11-14 17:39:45 +0100415 ('Py_NoSiteFlag', 'site_import', True),
416 ('Py_NoUserSiteDirectory', 'user_site_directory', True),
417 ('Py_OptimizeFlag', 'optimization_level'),
418 ('Py_QuietFlag', 'quiet'),
Victor Stinner01de89c2018-11-14 17:39:45 +0100419 ('Py_UnbufferedStdioFlag', 'buffered_stdio', True),
420 ('Py_VerboseFlag', 'verbose'),
421 ]
422 if MS_WINDOWS:
Victor Stinner1075d162019-03-25 23:19:57 +0100423 COPY_GLOBAL_PRE_CONFIG.extend((
Victor Stinner01de89c2018-11-14 17:39:45 +0100424 ('Py_LegacyWindowsFSEncodingFlag', 'legacy_windows_fs_encoding'),
Victor Stinner1075d162019-03-25 23:19:57 +0100425 ))
426 COPY_GLOBAL_CONFIG.extend((
Victor Stinner01de89c2018-11-14 17:39:45 +0100427 ('Py_LegacyWindowsStdioFlag', 'legacy_windows_stdio'),
428 ))
Victor Stinner56b29b62018-07-26 18:57:56 +0200429
Victor Stinner01de89c2018-11-14 17:39:45 +0100430 def main_xoptions(self, xoptions_list):
431 xoptions = {}
432 for opt in xoptions_list:
433 if '=' in opt:
434 key, value = opt.split('=', 1)
435 xoptions[key] = value
436 else:
437 xoptions[opt] = True
438 return xoptions
Victor Stinnerdfe0dc72018-08-29 11:47:29 +0200439
Victor Stinnerbab0db62019-05-18 03:21:27 +0200440 def get_expected_config(self, expected_preconfig, expected, env, api,
441 add_path=None):
442 if api == "python":
443 default_config = self.PYTHON_CORE_CONFIG
444 elif api == "isolated":
445 default_config = self.ISOLATED_CORE_CONFIG
446 else:
447 default_config = self.DEFAULT_CORE_CONFIG
448 expected = dict(default_config, **expected)
Victor Stinnera6537fb2018-11-26 11:54:12 +0100449
450 code = textwrap.dedent('''
451 import json
Victor Stinnera6537fb2018-11-26 11:54:12 +0100452 import sys
Victor Stinner5eb8b072019-05-15 02:12:48 +0200453 import _testinternalcapi
Victor Stinnera6537fb2018-11-26 11:54:12 +0100454
Victor Stinner5eb8b072019-05-15 02:12:48 +0200455 configs = _testinternalcapi.get_configs()
456 core_config = configs['core_config']
Victor Stinnera6537fb2018-11-26 11:54:12 +0100457 data = {
458 'stdio_encoding': sys.stdout.encoding,
459 'stdio_errors': sys.stdout.errors,
460 'prefix': sys.prefix,
461 'base_prefix': sys.base_prefix,
462 'exec_prefix': sys.exec_prefix,
463 'base_exec_prefix': sys.base_exec_prefix,
464 'filesystem_encoding': sys.getfilesystemencoding(),
465 'filesystem_errors': sys.getfilesystemencodeerrors(),
Victor Stinner5eb8b072019-05-15 02:12:48 +0200466 'module_search_paths': core_config['module_search_paths'],
Victor Stinnera6537fb2018-11-26 11:54:12 +0100467 }
468
469 data = json.dumps(data)
470 data = data.encode('utf-8')
471 sys.stdout.buffer.write(data)
472 sys.stdout.buffer.flush()
473 ''')
474
475 # Use -S to not import the site module: get the proper configuration
476 # when test_embed is run from a venv (bpo-35313)
Victor Stinner5eb8b072019-05-15 02:12:48 +0200477 args = [sys.executable, '-S', '-c', code]
Victor Stinnera6537fb2018-11-26 11:54:12 +0100478 env = dict(env)
Victor Stinner20004952019-03-26 02:31:11 +0100479 if not expected['isolated']:
Victor Stinnera6537fb2018-11-26 11:54:12 +0100480 env['PYTHONCOERCECLOCALE'] = '0'
481 env['PYTHONUTF8'] = '0'
482 proc = subprocess.run(args, env=env,
483 stdout=subprocess.PIPE,
484 stderr=subprocess.STDOUT)
485 if proc.returncode:
486 raise Exception(f"failed to get the default config: "
487 f"stdout={proc.stdout!r} stderr={proc.stderr!r}")
488 stdout = proc.stdout.decode('utf-8')
Victor Stinner4631da12019-05-02 15:30:21 -0400489 try:
490 config = json.loads(stdout)
491 except json.JSONDecodeError:
492 self.fail(f"fail to decode stdout: {stdout!r}")
Victor Stinnera6537fb2018-11-26 11:54:12 +0100493
Victor Stinner91c99872019-05-14 22:01:51 +0200494 if expected['executable'] is self.GET_DEFAULT_CONFIG:
495 if sys.platform == 'win32':
496 expected['executable'] = self.test_exe
497 else:
498 if expected['program_name'] is not self.GET_DEFAULT_CONFIG:
499 expected['executable'] = os.path.abspath(expected['program_name'])
500 else:
501 expected['executable'] = os.path.join(os.getcwd(), '_testembed')
502 if expected['program_name'] is self.GET_DEFAULT_CONFIG:
503 expected['program_name'] = './_testembed'
504
Victor Stinnera6537fb2018-11-26 11:54:12 +0100505 for key, value in expected.items():
506 if value is self.GET_DEFAULT_CONFIG:
507 expected[key] = config[key]
Victor Stinner5eb8b072019-05-15 02:12:48 +0200508
509 if add_path is not None:
510 expected['module_search_paths'].append(add_path)
Victor Stinnerbcfbbd72019-05-17 22:44:16 +0200511
512 if not expected_preconfig['configure_locale']:
513 # there is no easy way to get the locale encoding before
514 # setlocale(LC_CTYPE, "") is called: don't test encodings
515 for key in ('filesystem_encoding', 'filesystem_errors',
516 'stdio_encoding', 'stdio_errors'):
517 expected[key] = self.IGNORE_CONFIG
518
Victor Stinnera6537fb2018-11-26 11:54:12 +0100519 return expected
Victor Stinner01de89c2018-11-14 17:39:45 +0100520
Victor Stinner1075d162019-03-25 23:19:57 +0100521 def check_pre_config(self, config, expected):
522 pre_config = dict(config['pre_config'])
523 core_config = dict(config['core_config'])
524 self.assertEqual(pre_config, expected)
525
Victor Stinner5eb8b072019-05-15 02:12:48 +0200526 def check_core_config(self, config, expected):
Victor Stinner01de89c2018-11-14 17:39:45 +0100527 core_config = dict(config['core_config'])
Victor Stinnerbcfbbd72019-05-17 22:44:16 +0200528 for key, value in list(expected.items()):
529 if value is self.IGNORE_CONFIG:
530 del core_config[key]
531 del expected[key]
Victor Stinner01de89c2018-11-14 17:39:45 +0100532 self.assertEqual(core_config, expected)
Victor Stinnerdfe0dc72018-08-29 11:47:29 +0200533
Victor Stinner01de89c2018-11-14 17:39:45 +0100534 def check_global_config(self, config):
Victor Stinner1075d162019-03-25 23:19:57 +0100535 pre_config = config['pre_config']
Victor Stinner7ddd56f2018-11-14 00:24:28 +0100536 core_config = config['core_config']
Victor Stinner00b137c2018-11-13 19:59:26 +0100537
Victor Stinnera6537fb2018-11-26 11:54:12 +0100538 expected = dict(self.DEFAULT_GLOBAL_CONFIG)
Victor Stinner01de89c2018-11-14 17:39:45 +0100539 for item in self.COPY_GLOBAL_CONFIG:
Victor Stinner7ddd56f2018-11-14 00:24:28 +0100540 if len(item) == 3:
541 global_key, core_key, opposite = item
Victor Stinnera6537fb2018-11-26 11:54:12 +0100542 expected[global_key] = 0 if core_config[core_key] else 1
Victor Stinner7ddd56f2018-11-14 00:24:28 +0100543 else:
544 global_key, core_key = item
Victor Stinnera6537fb2018-11-26 11:54:12 +0100545 expected[global_key] = core_config[core_key]
Victor Stinner1075d162019-03-25 23:19:57 +0100546 for item in self.COPY_GLOBAL_PRE_CONFIG:
547 if len(item) == 3:
548 global_key, core_key, opposite = item
549 expected[global_key] = 0 if pre_config[core_key] else 1
550 else:
551 global_key, core_key = item
552 expected[global_key] = pre_config[core_key]
Victor Stinner7ddd56f2018-11-14 00:24:28 +0100553
Victor Stinnera6537fb2018-11-26 11:54:12 +0100554 self.assertEqual(config['global_config'], expected)
Victor Stinner7ddd56f2018-11-14 00:24:28 +0100555
Victor Stinnerbab0db62019-05-18 03:21:27 +0200556 def check_config(self, testname, expected_config=None, expected_preconfig=None,
557 add_path=None, stderr=None, api="default"):
Victor Stinner01de89c2018-11-14 17:39:45 +0100558 env = dict(os.environ)
559 # Remove PYTHON* environment variables to get deterministic environment
560 for key in list(env):
561 if key.startswith('PYTHON'):
562 del env[key]
563 # Disable C locale coercion and UTF-8 mode to not depend
564 # on the current locale
565 env['PYTHONCOERCECLOCALE'] = '0'
566 env['PYTHONUTF8'] = '0'
567
Victor Stinnerbab0db62019-05-18 03:21:27 +0200568 if api == "isolated":
569 default_preconfig = self.ISOLATED_PRE_CONFIG
570 else:
571 default_preconfig = self.DEFAULT_PRE_CONFIG
572 if expected_preconfig is None:
573 expected_preconfig = {}
574 expected_preconfig = dict(default_preconfig, **expected_preconfig)
575 if expected_config is None:
576 expected_config = {}
577 expected_config = self.get_expected_config(expected_preconfig,
578 expected_config, env,
579 api, add_path)
Victor Stinner20004952019-03-26 02:31:11 +0100580 for key in self.COPY_PRE_CONFIG:
581 if key not in expected_preconfig:
582 expected_preconfig[key] = expected_config[key]
Victor Stinner1075d162019-03-25 23:19:57 +0100583
Victor Stinner9ef5dca2019-05-16 17:38:16 +0200584 out, err = self.run_embedded_interpreter(testname, env=env)
585 if stderr is None and not expected_config['verbose']:
586 stderr = ""
587 if stderr is not None:
588 self.assertEqual(err.rstrip(), stderr)
589 try:
590 config = json.loads(out)
591 except json.JSONDecodeError:
592 self.fail(f"fail to decode stdout: {out!r}")
593
Victor Stinner1075d162019-03-25 23:19:57 +0100594 self.check_pre_config(config, expected_preconfig)
Victor Stinner5eb8b072019-05-15 02:12:48 +0200595 self.check_core_config(config, expected_config)
Victor Stinner01de89c2018-11-14 17:39:45 +0100596 self.check_global_config(config)
Victor Stinner7ddd56f2018-11-14 00:24:28 +0100597
Victor Stinner56b29b62018-07-26 18:57:56 +0200598 def test_init_default_config(self):
Victor Stinner1075d162019-03-25 23:19:57 +0100599 self.check_config("init_default_config", {}, {})
Victor Stinner56b29b62018-07-26 18:57:56 +0200600
601 def test_init_global_config(self):
Victor Stinner1075d162019-03-25 23:19:57 +0100602 preconfig = {
603 'utf8_mode': 1,
604 }
Victor Stinner56b29b62018-07-26 18:57:56 +0200605 config = {
606 'program_name': './globalvar',
607 'site_import': 0,
608 'bytes_warning': 1,
Victor Stinnerf8ba6f52019-03-26 16:58:50 +0100609 'warnoptions': ['default::BytesWarning'],
Victor Stinner56b29b62018-07-26 18:57:56 +0200610 'inspect': 1,
611 'interactive': 1,
612 'optimization_level': 2,
613 'write_bytecode': 0,
614 'verbose': 1,
615 'quiet': 1,
Victor Stinner98512272018-08-01 03:07:00 +0200616 'buffered_stdio': 0,
Victor Stinnerdfe0dc72018-08-29 11:47:29 +0200617
Victor Stinnerdfe0dc72018-08-29 11:47:29 +0200618 'stdio_encoding': 'utf-8',
619 'stdio_errors': 'surrogateescape',
Victor Stinnerb2457ef2018-08-29 13:25:36 +0200620 'filesystem_encoding': 'utf-8',
621 'filesystem_errors': self.UTF8_MODE_ERRORS,
Victor Stinner56b29b62018-07-26 18:57:56 +0200622 'user_site_directory': 0,
Victor Stinner54b43bb2019-05-16 18:30:15 +0200623 'pathconfig_warnings': 0,
Victor Stinner56b29b62018-07-26 18:57:56 +0200624 }
Victor Stinner1075d162019-03-25 23:19:57 +0100625 self.check_config("init_global_config", config, preconfig)
Victor Stinner56b29b62018-07-26 18:57:56 +0200626
627 def test_init_from_config(self):
Victor Stinner1075d162019-03-25 23:19:57 +0100628 preconfig = {
Victor Stinnerb16b4e42019-05-17 15:20:52 +0200629 'allocator': PYMEM_ALLOCATOR_MALLOC,
Victor Stinner1075d162019-03-25 23:19:57 +0100630 'utf8_mode': 1,
631 }
Victor Stinner56b29b62018-07-26 18:57:56 +0200632 config = {
633 'install_signal_handlers': 0,
634 'use_hash_seed': 1,
635 'hash_seed': 123,
Victor Stinner56b29b62018-07-26 18:57:56 +0200636 'tracemalloc': 2,
637 'import_time': 1,
638 'show_ref_count': 1,
639 'show_alloc_count': 1,
640 'malloc_stats': 1,
641
Victor Stinnerdfe0dc72018-08-29 11:47:29 +0200642 'stdio_encoding': 'iso8859-1',
643 'stdio_errors': 'replace',
Victor Stinnerb2457ef2018-08-29 13:25:36 +0200644 'filesystem_encoding': 'utf-8',
645 'filesystem_errors': self.UTF8_MODE_ERRORS,
Victor Stinner56b29b62018-07-26 18:57:56 +0200646
647 'pycache_prefix': 'conf_pycache_prefix',
648 'program_name': './conf_program_name',
Victor Stinner2f549082019-03-29 15:13:46 +0100649 'argv': ['-c', 'arg2'],
Victor Stinnercab5d072019-05-17 19:01:14 +0200650 'parse_argv': 1,
Victor Stinner01de89c2018-11-14 17:39:45 +0100651 'xoptions': ['core_xoption1=3', 'core_xoption2=', 'core_xoption3'],
Victor Stinnerf8ba6f52019-03-26 16:58:50 +0100652 'warnoptions': ['error::ResourceWarning', 'default::BytesWarning'],
Victor Stinner2f549082019-03-29 15:13:46 +0100653 'run_command': 'pass\n',
Victor Stinner56b29b62018-07-26 18:57:56 +0200654
655 'site_import': 0,
656 'bytes_warning': 1,
657 'inspect': 1,
658 'interactive': 1,
659 'optimization_level': 2,
660 'write_bytecode': 0,
661 'verbose': 1,
662 'quiet': 1,
Victor Stinnercab5d072019-05-17 19:01:14 +0200663 'configure_c_stdio': 1,
Victor Stinner98512272018-08-01 03:07:00 +0200664 'buffered_stdio': 0,
Victor Stinner56b29b62018-07-26 18:57:56 +0200665 'user_site_directory': 0,
666 'faulthandler': 1,
Victor Stinnerb75d7e22018-08-01 02:13:04 +0200667
Victor Stinnercb9fbd32019-05-01 23:51:56 -0400668 'check_hash_pycs_mode': 'always',
Victor Stinner54b43bb2019-05-16 18:30:15 +0200669 'pathconfig_warnings': 0,
Victor Stinner56b29b62018-07-26 18:57:56 +0200670 }
Victor Stinner1075d162019-03-25 23:19:57 +0100671 self.check_config("init_from_config", config, preconfig)
Victor Stinner56b29b62018-07-26 18:57:56 +0200672
Victor Stinner1075d162019-03-25 23:19:57 +0100673 INIT_ENV_PRECONFIG = {
Victor Stinnerb16b4e42019-05-17 15:20:52 +0200674 'allocator': PYMEM_ALLOCATOR_MALLOC,
Victor Stinner1075d162019-03-25 23:19:57 +0100675 }
Victor Stinnerb35be4b2019-03-05 17:37:44 +0100676 INIT_ENV_CONFIG = {
677 'use_hash_seed': 1,
678 'hash_seed': 42,
Victor Stinnerb35be4b2019-03-05 17:37:44 +0100679 'tracemalloc': 2,
680 'import_time': 1,
681 'malloc_stats': 1,
Victor Stinnerb35be4b2019-03-05 17:37:44 +0100682 'inspect': 1,
683 'optimization_level': 2,
684 'pycache_prefix': 'env_pycache_prefix',
685 'write_bytecode': 0,
686 'verbose': 1,
687 'buffered_stdio': 0,
688 'stdio_encoding': 'iso8859-1',
689 'stdio_errors': 'replace',
690 'user_site_directory': 0,
691 'faulthandler': 1,
692 }
693
Victor Stinner56b29b62018-07-26 18:57:56 +0200694 def test_init_env(self):
Victor Stinner1075d162019-03-25 23:19:57 +0100695 self.check_config("init_env", self.INIT_ENV_CONFIG, self.INIT_ENV_PRECONFIG)
Victor Stinnerb35be4b2019-03-05 17:37:44 +0100696
697 def test_init_env_dev_mode(self):
Victor Stinner1075d162019-03-25 23:19:57 +0100698 preconfig = dict(self.INIT_ENV_PRECONFIG,
Victor Stinnerb16b4e42019-05-17 15:20:52 +0200699 allocator=PYMEM_ALLOCATOR_DEBUG)
Victor Stinner1075d162019-03-25 23:19:57 +0100700 config = dict(self.INIT_ENV_CONFIG,
Victor Stinnerf8ba6f52019-03-26 16:58:50 +0100701 dev_mode=1,
702 warnoptions=['default'])
Victor Stinner1075d162019-03-25 23:19:57 +0100703 self.check_config("init_env_dev_mode", config, preconfig)
Victor Stinner56b29b62018-07-26 18:57:56 +0200704
Victor Stinner20004952019-03-26 02:31:11 +0100705 def test_init_env_dev_mode_alloc(self):
Victor Stinner1075d162019-03-25 23:19:57 +0100706 preconfig = dict(self.INIT_ENV_PRECONFIG,
Victor Stinnerb16b4e42019-05-17 15:20:52 +0200707 allocator=PYMEM_ALLOCATOR_MALLOC)
Victor Stinner20004952019-03-26 02:31:11 +0100708 config = dict(self.INIT_ENV_CONFIG,
Victor Stinnerf8ba6f52019-03-26 16:58:50 +0100709 dev_mode=1,
710 warnoptions=['default'])
Victor Stinner1075d162019-03-25 23:19:57 +0100711 self.check_config("init_env_dev_mode_alloc", config, preconfig)
Victor Stinner25d13f32019-03-06 12:51:53 +0100712
Victor Stinner56b29b62018-07-26 18:57:56 +0200713 def test_init_dev_mode(self):
Victor Stinner1075d162019-03-25 23:19:57 +0100714 preconfig = {
Victor Stinnerb16b4e42019-05-17 15:20:52 +0200715 'allocator': PYMEM_ALLOCATOR_DEBUG,
Victor Stinner56b29b62018-07-26 18:57:56 +0200716 }
Victor Stinner1075d162019-03-25 23:19:57 +0100717 config = {
718 'faulthandler': 1,
Victor Stinner20004952019-03-26 02:31:11 +0100719 'dev_mode': 1,
Victor Stinnerf8ba6f52019-03-26 16:58:50 +0100720 'warnoptions': ['default'],
Victor Stinner1075d162019-03-25 23:19:57 +0100721 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200722 self.check_config("init_dev_mode", config, preconfig, api="python")
Victor Stinner56b29b62018-07-26 18:57:56 +0200723
Victor Stinnercab5d072019-05-17 19:01:14 +0200724 def test_init_isolated_flag(self):
Victor Stinner1075d162019-03-25 23:19:57 +0100725 config = {
Victor Stinner20004952019-03-26 02:31:11 +0100726 'isolated': 1,
727 'use_environment': 0,
Victor Stinner56b29b62018-07-26 18:57:56 +0200728 'user_site_directory': 0,
729 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200730 self.check_config("init_isolated_flag", config, api="python")
Victor Stinner56b29b62018-07-26 18:57:56 +0200731
Victor Stinner6da20a42019-03-27 00:26:18 +0100732 def test_preinit_isolated1(self):
733 # _PyPreConfig.isolated=1, _PyCoreConfig.isolated not set
Victor Stinner6da20a42019-03-27 00:26:18 +0100734 config = {
735 'isolated': 1,
736 'use_environment': 0,
737 'user_site_directory': 0,
738 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200739 self.check_config("preinit_isolated1", config)
Victor Stinner6da20a42019-03-27 00:26:18 +0100740
741 def test_preinit_isolated2(self):
742 # _PyPreConfig.isolated=0, _PyCoreConfig.isolated=1
Victor Stinner6da20a42019-03-27 00:26:18 +0100743 config = {
744 'isolated': 1,
745 'use_environment': 0,
746 'user_site_directory': 0,
747 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200748 self.check_config("preinit_isolated2", config)
Victor Stinner6da20a42019-03-27 00:26:18 +0100749
Victor Stinnercab5d072019-05-17 19:01:14 +0200750 def test_init_isolated_config(self):
Victor Stinnerbab0db62019-05-18 03:21:27 +0200751 self.check_config("init_isolated_config", api="isolated")
Victor Stinnercab5d072019-05-17 19:01:14 +0200752
753 def test_init_python_config(self):
Victor Stinnerbab0db62019-05-18 03:21:27 +0200754 self.check_config("init_python_config", api="python")
Victor Stinnercab5d072019-05-17 19:01:14 +0200755
Victor Stinnerbcfbbd72019-05-17 22:44:16 +0200756 def test_init_dont_configure_locale(self):
757 # _PyPreConfig.configure_locale=0
758 preconfig = {
759 'configure_locale': 0,
760 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200761 self.check_config("init_dont_configure_locale", {}, preconfig, api="python")
Victor Stinnerbcfbbd72019-05-17 22:44:16 +0200762
Victor Stinner91c99872019-05-14 22:01:51 +0200763 def test_init_read_set(self):
Victor Stinner91c99872019-05-14 22:01:51 +0200764 core_config = {
765 'program_name': './init_read_set',
766 'executable': 'my_executable',
767 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200768 self.check_config("init_read_set", core_config, api="python",
Victor Stinner91c99872019-05-14 22:01:51 +0200769 add_path="init_read_set_path")
770
Victor Stinner9ef5dca2019-05-16 17:38:16 +0200771 def test_init_run_main(self):
Victor Stinner5eb8b072019-05-15 02:12:48 +0200772 code = ('import _testinternalcapi, json; '
773 'print(json.dumps(_testinternalcapi.get_configs()))')
774 core_config = {
775 'argv': ['-c', 'arg2'],
Victor Stinner5eb8b072019-05-15 02:12:48 +0200776 'program_name': './python3',
777 'run_command': code + '\n',
Victor Stinnercab5d072019-05-17 19:01:14 +0200778 'parse_argv': 1,
Victor Stinner5eb8b072019-05-15 02:12:48 +0200779 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200780 self.check_config("init_run_main", core_config, api="python")
Victor Stinner9ef5dca2019-05-16 17:38:16 +0200781
782 def test_init_main(self):
Victor Stinner9ef5dca2019-05-16 17:38:16 +0200783 code = ('import _testinternalcapi, json; '
784 'print(json.dumps(_testinternalcapi.get_configs()))')
785 core_config = {
786 'argv': ['-c', 'arg2'],
Victor Stinner9ef5dca2019-05-16 17:38:16 +0200787 'program_name': './python3',
788 'run_command': code + '\n',
Victor Stinnercab5d072019-05-17 19:01:14 +0200789 'parse_argv': 1,
Victor Stinner9ef5dca2019-05-16 17:38:16 +0200790 '_init_main': 0,
791 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200792 self.check_config("init_main", core_config, api="python",
Victor Stinner9ef5dca2019-05-16 17:38:16 +0200793 stderr="Run Python code before _Py_InitializeMain")
Victor Stinner5eb8b072019-05-15 02:12:48 +0200794
Victor Stinnercab5d072019-05-17 19:01:14 +0200795 def test_init_parse_argv(self):
796 core_config = {
Victor Stinnerbab0db62019-05-18 03:21:27 +0200797 'parse_argv': 1,
Victor Stinnercab5d072019-05-17 19:01:14 +0200798 'argv': ['-c', 'arg1', '-v', 'arg3'],
799 'program_name': './argv0',
Victor Stinnercab5d072019-05-17 19:01:14 +0200800 'run_command': 'pass\n',
801 'use_environment': 0,
802 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200803 self.check_config("init_parse_argv", core_config, api="python")
Victor Stinnercab5d072019-05-17 19:01:14 +0200804
Victor Stinnerae239f62019-05-16 17:02:56 +0200805 def test_init_dont_parse_argv(self):
806 core_config = {
Victor Stinnerbab0db62019-05-18 03:21:27 +0200807 'parse_argv': 0,
Victor Stinnercab5d072019-05-17 19:01:14 +0200808 'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
809 'program_name': './argv0',
Victor Stinnerae239f62019-05-16 17:02:56 +0200810 }
Victor Stinnerbab0db62019-05-18 03:21:27 +0200811 self.check_config("init_dont_parse_argv", core_config, api="python")
Victor Stinnerae239f62019-05-16 17:02:56 +0200812
Victor Stinner56b29b62018-07-26 18:57:56 +0200813
Nick Coghlan39f0bb52017-11-28 08:11:51 +1000814if __name__ == "__main__":
815 unittest.main()