1# bpo-42260: Test _PyInterpreterState_GetConfigCopy()
2# and _PyInterpreterState_SetConfig().
3#
4# Test run in a subprocess since set_config(get_config())
5# does reset sys attributes to their state of the Python startup
6# (before the site module is run).
7
8import _testinternalcapi
9import os
10import sys
11import unittest
12
13
14MS_WINDOWS = (os.name == 'nt')
15MAX_HASH_SEED = 4294967295
16
17class SetConfigTests(unittest.TestCase):
18    def setUp(self):
19        self.old_config = _testinternalcapi.get_config()
20        self.sys_copy = dict(sys.__dict__)
21
22    def tearDown(self):
23        _testinternalcapi.reset_path_config()
24        _testinternalcapi.set_config(self.old_config)
25        sys.__dict__.clear()
26        sys.__dict__.update(self.sys_copy)
27
28    def set_config(self, **kwargs):
29        _testinternalcapi.set_config(self.old_config | kwargs)
30
31    def check(self, **kwargs):
32        self.set_config(**kwargs)
33        for key, value in kwargs.items():
34            self.assertEqual(getattr(sys, key), value,
35                             (key, value))
36
37    def test_set_invalid(self):
38        invalid_uint = -1
39        NULL = None
40        invalid_wstr = NULL
41        # PyWideStringList strings must be non-NULL
42        invalid_wstrlist = ["abc", NULL, "def"]
43
44        type_tests = []
45        value_tests = [
46            # enum
47            ('_config_init', 0),
48            ('_config_init', 4),
49            # unsigned long
50            ("hash_seed", -1),
51            ("hash_seed", MAX_HASH_SEED + 1),
52        ]
53
54        # int (unsigned)
55        options = [
56            '_config_init',
57            'isolated',
58            'use_environment',
59            'dev_mode',
60            'install_signal_handlers',
61            'use_hash_seed',
62            'faulthandler',
63            'tracemalloc',
64            'import_time',
65            'code_debug_ranges',
66            'show_ref_count',
67            'dump_refs',
68            'malloc_stats',
69            'parse_argv',
70            'site_import',
71            'bytes_warning',
72            'inspect',
73            'interactive',
74            'optimization_level',
75            'parser_debug',
76            'write_bytecode',
77            'verbose',
78            'quiet',
79            'user_site_directory',
80            'configure_c_stdio',
81            'buffered_stdio',
82            'pathconfig_warnings',
83            'module_search_paths_set',
84            'skip_source_first_line',
85            '_install_importlib',
86            '_init_main',
87            '_isolated_interpreter',
88        ]
89        if MS_WINDOWS:
90            options.append('legacy_windows_stdio')
91        for key in options:
92            value_tests.append((key, invalid_uint))
93            type_tests.append((key, "abc"))
94            type_tests.append((key, 2.0))
95
96        # wchar_t*
97        for key in (
98            'filesystem_encoding',
99            'filesystem_errors',
100            'stdio_encoding',
101            'stdio_errors',
102            'check_hash_pycs_mode',
103            'program_name',
104            'platlibdir',
105            # optional wstr:
106            # 'pythonpath_env'
107            # 'home'
108            # 'pycache_prefix'
109            # 'run_command'
110            # 'run_module'
111            # 'run_filename'
112            # 'executable'
113            # 'prefix'
114            # 'exec_prefix'
115            # 'base_executable'
116            # 'base_prefix'
117            # 'base_exec_prefix'
118        ):
119            value_tests.append((key, invalid_wstr))
120            type_tests.append((key, b'bytes'))
121            type_tests.append((key, 123))
122
123        # PyWideStringList
124        for key in (
125            'orig_argv',
126            'argv',
127            'xoptions',
128            'warnoptions',
129            'module_search_paths',
130        ):
131            value_tests.append((key, invalid_wstrlist))
132            type_tests.append((key, 123))
133            type_tests.append((key, "abc"))
134            type_tests.append((key, [123]))
135            type_tests.append((key, [b"bytes"]))
136
137
138        if MS_WINDOWS:
139            value_tests.append(('legacy_windows_stdio', invalid_uint))
140
141        for exc_type, tests in (
142            (ValueError, value_tests),
143            (TypeError, type_tests),
144        ):
145            for key, value in tests:
146                config = self.old_config | {key: value}
147                with self.subTest(key=key, value=value, exc_type=exc_type):
148                    with self.assertRaises(exc_type):
149                        _testinternalcapi.set_config(config)
150
151    def test_flags(self):
152        for sys_attr, key, value in (
153            ("debug", "parser_debug", 1),
154            ("inspect", "inspect", 2),
155            ("interactive", "interactive", 3),
156            ("optimize", "optimization_level", 4),
157            ("verbose", "verbose", 1),
158            ("bytes_warning", "bytes_warning", 10),
159            ("quiet", "quiet", 11),
160            ("isolated", "isolated", 12),
161        ):
162            with self.subTest(sys=sys_attr, key=key, value=value):
163                self.set_config(**{key: value, 'parse_argv': 0})
164                self.assertEqual(getattr(sys.flags, sys_attr), value)
165
166        self.set_config(write_bytecode=0)
167        self.assertEqual(sys.flags.dont_write_bytecode, True)
168        self.assertEqual(sys.dont_write_bytecode, True)
169
170        self.set_config(write_bytecode=1)
171        self.assertEqual(sys.flags.dont_write_bytecode, False)
172        self.assertEqual(sys.dont_write_bytecode, False)
173
174        self.set_config(user_site_directory=0, isolated=0)
175        self.assertEqual(sys.flags.no_user_site, 1)
176        self.set_config(user_site_directory=1, isolated=0)
177        self.assertEqual(sys.flags.no_user_site, 0)
178
179        self.set_config(site_import=0)
180        self.assertEqual(sys.flags.no_site, 1)
181        self.set_config(site_import=1)
182        self.assertEqual(sys.flags.no_site, 0)
183
184        self.set_config(dev_mode=0)
185        self.assertEqual(sys.flags.dev_mode, False)
186        self.set_config(dev_mode=1)
187        self.assertEqual(sys.flags.dev_mode, True)
188
189        self.set_config(use_environment=0, isolated=0)
190        self.assertEqual(sys.flags.ignore_environment, 1)
191        self.set_config(use_environment=1, isolated=0)
192        self.assertEqual(sys.flags.ignore_environment, 0)
193
194        self.set_config(use_hash_seed=1, hash_seed=0)
195        self.assertEqual(sys.flags.hash_randomization, 0)
196        self.set_config(use_hash_seed=0, hash_seed=0)
197        self.assertEqual(sys.flags.hash_randomization, 1)
198        self.set_config(use_hash_seed=1, hash_seed=123)
199        self.assertEqual(sys.flags.hash_randomization, 1)
200
201    def test_options(self):
202        self.check(warnoptions=[])
203        self.check(warnoptions=["default", "ignore"])
204
205        self.set_config(xoptions=[])
206        self.assertEqual(sys._xoptions, {})
207        self.set_config(xoptions=["dev", "tracemalloc=5"])
208        self.assertEqual(sys._xoptions, {"dev": True, "tracemalloc": "5"})
209
210    def test_pathconfig(self):
211        self.check(
212            executable='executable',
213            prefix="prefix",
214            base_prefix="base_prefix",
215            exec_prefix="exec_prefix",
216            base_exec_prefix="base_exec_prefix",
217            platlibdir="platlibdir")
218
219        self.set_config(base_executable="base_executable")
220        self.assertEqual(sys._base_executable, "base_executable")
221
222        # When base_xxx is NULL, value is copied from xxxx
223        self.set_config(
224            executable='executable',
225            prefix="prefix",
226            exec_prefix="exec_prefix",
227            base_executable=None,
228            base_prefix=None,
229            base_exec_prefix=None)
230        self.assertEqual(sys._base_executable, "executable")
231        self.assertEqual(sys.base_prefix, "prefix")
232        self.assertEqual(sys.base_exec_prefix, "exec_prefix")
233
234    def test_path(self):
235        self.set_config(module_search_paths_set=1,
236                        module_search_paths=['a', 'b', 'c'])
237        self.assertEqual(sys.path, ['a', 'b', 'c'])
238
239        # sys.path is reset if module_search_paths_set=0
240        self.set_config(module_search_paths_set=0,
241                        module_search_paths=['new_path'])
242        self.assertNotEqual(sys.path, ['a', 'b', 'c'])
243        self.assertNotEqual(sys.path, ['new_path'])
244
245    def test_argv(self):
246        self.set_config(parse_argv=0,
247                        argv=['python_program', 'args'],
248                        orig_argv=['orig', 'orig_args'])
249        self.assertEqual(sys.argv, ['python_program', 'args'])
250        self.assertEqual(sys.orig_argv, ['orig', 'orig_args'])
251
252        self.set_config(parse_argv=0,
253                        argv=[],
254                        orig_argv=[])
255        self.assertEqual(sys.argv, [''])
256        self.assertEqual(sys.orig_argv, [])
257
258    def test_pycache_prefix(self):
259        self.check(pycache_prefix=None)
260        self.check(pycache_prefix="pycache_prefix")
261
262
263if __name__ == "__main__":
264    unittest.main()
265