mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 10:26:02 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			383 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			383 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| Tests PyConfig_Get() and PyConfig_Set() C API (PEP 741).
 | |
| """
 | |
| import sys
 | |
| import sysconfig
 | |
| import types
 | |
| import unittest
 | |
| from test import support
 | |
| from test.support import import_helper
 | |
| 
 | |
| _testcapi = import_helper.import_module('_testcapi')
 | |
| 
 | |
| 
 | |
| # Is the Py_STATS macro defined?
 | |
| Py_STATS = hasattr(sys, '_stats_on')
 | |
| 
 | |
| 
 | |
| class CAPITests(unittest.TestCase):
 | |
|     def test_config_get(self):
 | |
|         # Test PyConfig_Get()
 | |
|         config_get = _testcapi.config_get
 | |
|         config_names = _testcapi.config_names
 | |
| 
 | |
|         TEST_VALUE = {
 | |
|             str: "TEST_MARKER_STR",
 | |
|             str | None: "TEST_MARKER_OPT_STR",
 | |
|             list[str]: ("TEST_MARKER_STR_TUPLE",),
 | |
|             dict[str, str | bool]: {"x": "value", "y": True},
 | |
|         }
 | |
| 
 | |
|         # read config options and check their type
 | |
|         options = [
 | |
|             ("allocator", int, None),
 | |
|             ("argv", list[str], "argv"),
 | |
|             ("base_exec_prefix", str | None, "base_exec_prefix"),
 | |
|             ("base_executable", str | None, "_base_executable"),
 | |
|             ("base_prefix", str | None, "base_prefix"),
 | |
|             ("buffered_stdio", bool, None),
 | |
|             ("bytes_warning", int, None),
 | |
|             ("check_hash_pycs_mode", str, None),
 | |
|             ("code_debug_ranges", bool, None),
 | |
|             ("configure_c_stdio", bool, None),
 | |
|             ("coerce_c_locale", bool, None),
 | |
|             ("coerce_c_locale_warn", bool, None),
 | |
|             ("configure_locale", bool, None),
 | |
|             ("cpu_count", int, None),
 | |
|             ("dev_mode", bool, None),
 | |
|             ("dump_refs", bool, None),
 | |
|             ("dump_refs_file", str | None, None),
 | |
|             ("exec_prefix", str | None, "exec_prefix"),
 | |
|             ("executable", str | None, "executable"),
 | |
|             ("faulthandler", bool, None),
 | |
|             ("filesystem_encoding", str, None),
 | |
|             ("filesystem_errors", str, None),
 | |
|             ("hash_seed", int, None),
 | |
|             ("home", str | None, None),
 | |
|             ("import_time", bool, None),
 | |
|             ("inspect", bool, None),
 | |
|             ("install_signal_handlers", bool, None),
 | |
|             ("int_max_str_digits", int, None),
 | |
|             ("interactive", bool, None),
 | |
|             ("isolated", bool, None),
 | |
|             ("malloc_stats", bool, None),
 | |
|             ("module_search_paths", list[str], "path"),
 | |
|             ("optimization_level", int, None),
 | |
|             ("orig_argv", list[str], "orig_argv"),
 | |
|             ("parser_debug", bool, None),
 | |
|             ("parse_argv", bool, None),
 | |
|             ("pathconfig_warnings", bool, None),
 | |
|             ("perf_profiling", int, None),
 | |
|             ("platlibdir", str, "platlibdir"),
 | |
|             ("prefix", str | None, "prefix"),
 | |
|             ("program_name", str, None),
 | |
|             ("pycache_prefix", str | None, "pycache_prefix"),
 | |
|             ("quiet", bool, None),
 | |
|             ("run_command", str | None, None),
 | |
|             ("run_filename", str | None, None),
 | |
|             ("run_module", str | None, None),
 | |
|             ("safe_path", bool, None),
 | |
|             ("show_ref_count", bool, None),
 | |
|             ("site_import", bool, None),
 | |
|             ("skip_source_first_line", bool, None),
 | |
|             ("stdio_encoding", str, None),
 | |
|             ("stdio_errors", str, None),
 | |
|             ("stdlib_dir", str | None, "_stdlib_dir"),
 | |
|             ("tracemalloc", int, None),
 | |
|             ("use_environment", bool, None),
 | |
|             ("use_frozen_modules", bool, None),
 | |
|             ("use_hash_seed", bool, None),
 | |
|             ("user_site_directory", bool, None),
 | |
|             ("utf8_mode", bool, None),
 | |
|             ("verbose", int, None),
 | |
|             ("warn_default_encoding", bool, None),
 | |
|             ("warnoptions", list[str], "warnoptions"),
 | |
|             ("write_bytecode", bool, None),
 | |
|             ("xoptions", dict[str, str | bool], "_xoptions"),
 | |
|         ]
 | |
|         if support.Py_DEBUG:
 | |
|             options.append(("run_presite", str | None, None))
 | |
|         if sysconfig.get_config_var('Py_GIL_DISABLED'):
 | |
|             options.append(("enable_gil", int, None))
 | |
|             options.append(("tlbc_enabled", int, None))
 | |
|         if support.MS_WINDOWS:
 | |
|             options.extend((
 | |
|                 ("legacy_windows_stdio", bool, None),
 | |
|                 ("legacy_windows_fs_encoding", bool, None),
 | |
|             ))
 | |
|         if Py_STATS:
 | |
|             options.extend((
 | |
|                 ("_pystats", bool, None),
 | |
|             ))
 | |
|         if support.is_apple:
 | |
|             options.extend((
 | |
|                 ("use_system_logger", bool, None),
 | |
|             ))
 | |
| 
 | |
|         for name, option_type, sys_attr in options:
 | |
|             with self.subTest(name=name, option_type=option_type,
 | |
|                               sys_attr=sys_attr):
 | |
|                 value = config_get(name)
 | |
|                 if isinstance(option_type, types.GenericAlias):
 | |
|                     self.assertIsInstance(value, option_type.__origin__)
 | |
|                     if option_type.__origin__ == dict:
 | |
|                         key_type = option_type.__args__[0]
 | |
|                         value_type = option_type.__args__[1]
 | |
|                         for item in value.items():
 | |
|                             self.assertIsInstance(item[0], key_type)
 | |
|                             self.assertIsInstance(item[1], value_type)
 | |
|                     else:
 | |
|                         item_type = option_type.__args__[0]
 | |
|                         for item in value:
 | |
|                             self.assertIsInstance(item, item_type)
 | |
|                 else:
 | |
|                     self.assertIsInstance(value, option_type)
 | |
| 
 | |
|                 if sys_attr is not None:
 | |
|                     expected = getattr(sys, sys_attr)
 | |
|                     self.assertEqual(expected, value)
 | |
| 
 | |
|                     override = TEST_VALUE[option_type]
 | |
|                     with support.swap_attr(sys, sys_attr, override):
 | |
|                         self.assertEqual(config_get(name), override)
 | |
| 
 | |
|         # check that the test checks all options
 | |
|         self.assertEqual(sorted(name for name, option_type, sys_attr in options),
 | |
|                          sorted(config_names()))
 | |
| 
 | |
|     def test_config_get_sys_flags(self):
 | |
|         # Test PyConfig_Get()
 | |
|         config_get = _testcapi.config_get
 | |
| 
 | |
|         # compare config options with sys.flags
 | |
|         for flag, name, negate in (
 | |
|             ("debug", "parser_debug", False),
 | |
|             ("inspect", "inspect", False),
 | |
|             ("interactive", "interactive", False),
 | |
|             ("optimize", "optimization_level", False),
 | |
|             ("dont_write_bytecode", "write_bytecode", True),
 | |
|             ("no_user_site", "user_site_directory", True),
 | |
|             ("no_site", "site_import", True),
 | |
|             ("ignore_environment", "use_environment", True),
 | |
|             ("verbose", "verbose", False),
 | |
|             ("bytes_warning", "bytes_warning", False),
 | |
|             ("quiet", "quiet", False),
 | |
|             # "hash_randomization" is tested below
 | |
|             ("isolated", "isolated", False),
 | |
|             ("dev_mode", "dev_mode", False),
 | |
|             ("utf8_mode", "utf8_mode", False),
 | |
|             ("warn_default_encoding", "warn_default_encoding", False),
 | |
|             ("safe_path", "safe_path", False),
 | |
|             ("int_max_str_digits", "int_max_str_digits", False),
 | |
|             # "gil" is tested below
 | |
|         ):
 | |
|             with self.subTest(flag=flag, name=name, negate=negate):
 | |
|                 value = config_get(name)
 | |
|                 if negate:
 | |
|                     value = not value
 | |
|                 self.assertEqual(getattr(sys.flags, flag), value)
 | |
| 
 | |
|         self.assertEqual(sys.flags.hash_randomization,
 | |
|                          config_get('use_hash_seed') == 0
 | |
|                          or config_get('hash_seed') != 0)
 | |
| 
 | |
|         if sysconfig.get_config_var('Py_GIL_DISABLED'):
 | |
|             value = config_get('enable_gil')
 | |
|             expected = (value if value != -1 else None)
 | |
|             self.assertEqual(sys.flags.gil, expected)
 | |
| 
 | |
|     def test_config_get_non_existent(self):
 | |
|         # Test PyConfig_Get() on non-existent option name
 | |
|         config_get = _testcapi.config_get
 | |
|         nonexistent_key = 'NONEXISTENT_KEY'
 | |
|         err_msg = f'unknown config option name: {nonexistent_key}'
 | |
|         with self.assertRaisesRegex(ValueError, err_msg):
 | |
|             config_get(nonexistent_key)
 | |
| 
 | |
|     def test_config_get_write_bytecode(self):
 | |
|         # PyConfig_Get("write_bytecode") gets sys.dont_write_bytecode
 | |
|         # as an integer
 | |
|         config_get = _testcapi.config_get
 | |
|         with support.swap_attr(sys, "dont_write_bytecode", 0):
 | |
|             self.assertEqual(config_get('write_bytecode'), 1)
 | |
|         with support.swap_attr(sys, "dont_write_bytecode", "yes"):
 | |
|             self.assertEqual(config_get('write_bytecode'), 0)
 | |
|         with support.swap_attr(sys, "dont_write_bytecode", []):
 | |
|             self.assertEqual(config_get('write_bytecode'), 1)
 | |
| 
 | |
|     def test_config_getint(self):
 | |
|         # Test PyConfig_GetInt()
 | |
|         config_getint = _testcapi.config_getint
 | |
| 
 | |
|         # PyConfig_MEMBER_INT type
 | |
|         self.assertEqual(config_getint('verbose'), sys.flags.verbose)
 | |
| 
 | |
|         # PyConfig_MEMBER_UINT type
 | |
|         self.assertEqual(config_getint('isolated'), sys.flags.isolated)
 | |
| 
 | |
|         # PyConfig_MEMBER_ULONG type
 | |
|         self.assertIsInstance(config_getint('hash_seed'), int)
 | |
| 
 | |
|         # PyPreConfig member
 | |
|         self.assertIsInstance(config_getint('allocator'), int)
 | |
| 
 | |
|         # platlibdir type is str
 | |
|         with self.assertRaises(TypeError):
 | |
|             config_getint('platlibdir')
 | |
| 
 | |
|     def test_get_config_names(self):
 | |
|         names = _testcapi.config_names()
 | |
|         self.assertIsInstance(names, frozenset)
 | |
|         for name in names:
 | |
|             self.assertIsInstance(name, str)
 | |
| 
 | |
|     def test_config_set_sys_attr(self):
 | |
|         # Test PyConfig_Set() with sys attributes
 | |
|         config_get = _testcapi.config_get
 | |
|         config_set = _testcapi.config_set
 | |
| 
 | |
|         # mutable configuration option mapped to sys attributes
 | |
|         for name, sys_attr, option_type in (
 | |
|             ('argv', 'argv', list[str]),
 | |
|             ('base_exec_prefix', 'base_exec_prefix', str | None),
 | |
|             ('base_executable', '_base_executable', str | None),
 | |
|             ('base_prefix', 'base_prefix', str | None),
 | |
|             ('exec_prefix', 'exec_prefix', str | None),
 | |
|             ('executable', 'executable', str | None),
 | |
|             ('module_search_paths', 'path', list[str]),
 | |
|             ('platlibdir', 'platlibdir', str),
 | |
|             ('prefix', 'prefix', str | None),
 | |
|             ('pycache_prefix', 'pycache_prefix', str | None),
 | |
|             ('stdlib_dir', '_stdlib_dir', str | None),
 | |
|             ('warnoptions', 'warnoptions', list[str]),
 | |
|             ('xoptions', '_xoptions', dict[str, str | bool]),
 | |
|         ):
 | |
|             with self.subTest(name=name):
 | |
|                 if option_type == str:
 | |
|                     test_values = ('TEST_REPLACE',)
 | |
|                     invalid_types = (1, None)
 | |
|                 elif option_type == str | None:
 | |
|                     test_values = ('TEST_REPLACE', None)
 | |
|                     invalid_types = (123,)
 | |
|                 elif option_type == list[str]:
 | |
|                     test_values = (['TEST_REPLACE'], [])
 | |
|                     invalid_types = ('text', 123, [123])
 | |
|                 else:  # option_type == dict[str, str | bool]:
 | |
|                     test_values = ({"x": "value", "y": True},)
 | |
|                     invalid_types = ('text', 123, ['option'],
 | |
|                                      {123: 'value'},
 | |
|                                      {'key': b'bytes'})
 | |
| 
 | |
|                 old_opt_value = config_get(name)
 | |
|                 old_sys_value = getattr(sys, sys_attr)
 | |
|                 try:
 | |
|                     for value in test_values:
 | |
|                         config_set(name, value)
 | |
|                         self.assertEqual(config_get(name), value)
 | |
|                         self.assertEqual(getattr(sys, sys_attr), value)
 | |
| 
 | |
|                     for value in invalid_types:
 | |
|                         with self.assertRaises(TypeError):
 | |
|                             config_set(name, value)
 | |
|                 finally:
 | |
|                     setattr(sys, sys_attr, old_sys_value)
 | |
|                     config_set(name, old_opt_value)
 | |
| 
 | |
|     def test_config_set_sys_flag(self):
 | |
|         # Test PyConfig_Set() with sys.flags
 | |
|         config_get = _testcapi.config_get
 | |
|         config_set = _testcapi.config_set
 | |
| 
 | |
|         # mutable configuration option mapped to sys.flags
 | |
|         class unsigned_int(int):
 | |
|             pass
 | |
| 
 | |
|         def expect_int(value):
 | |
|             value = int(value)
 | |
|             return (value, value)
 | |
| 
 | |
|         def expect_bool(value):
 | |
|             value = int(bool(value))
 | |
|             return (value, value)
 | |
| 
 | |
|         def expect_bool_not(value):
 | |
|             value = bool(value)
 | |
|             return (int(value), int(not value))
 | |
| 
 | |
|         for name, sys_flag, option_type, expect_func in (
 | |
|             # (some flags cannot be set, see comments below.)
 | |
|             ('parser_debug', 'debug', bool, expect_bool),
 | |
|             ('inspect', 'inspect', bool, expect_bool),
 | |
|             ('interactive', 'interactive', bool, expect_bool),
 | |
|             ('optimization_level', 'optimize', unsigned_int, expect_int),
 | |
|             ('write_bytecode', 'dont_write_bytecode', bool, expect_bool_not),
 | |
|             # user_site_directory
 | |
|             # site_import
 | |
|             ('use_environment', 'ignore_environment', bool, expect_bool_not),
 | |
|             ('verbose', 'verbose', unsigned_int, expect_int),
 | |
|             ('bytes_warning', 'bytes_warning', unsigned_int, expect_int),
 | |
|             ('quiet', 'quiet', bool, expect_bool),
 | |
|             # hash_randomization
 | |
|             # isolated
 | |
|             # dev_mode
 | |
|             # utf8_mode
 | |
|             # warn_default_encoding
 | |
|             # safe_path
 | |
|             ('int_max_str_digits', 'int_max_str_digits', unsigned_int, expect_int),
 | |
|             # gil
 | |
|         ):
 | |
|             if name == "int_max_str_digits":
 | |
|                 new_values = (0, 5_000, 999_999)
 | |
|                 invalid_values = (-1, 40)  # value must 0 or >= 4300
 | |
|                 invalid_types = (1.0, "abc")
 | |
|             elif option_type == int:
 | |
|                 new_values = (False, True, 0, 1, 5, -5)
 | |
|                 invalid_values = ()
 | |
|                 invalid_types = (1.0, "abc")
 | |
|             else:
 | |
|                 new_values = (False, True, 0, 1, 5)
 | |
|                 invalid_values = (-5,)
 | |
|                 invalid_types = (1.0, "abc")
 | |
| 
 | |
|             with self.subTest(name=name):
 | |
|                 old_value = config_get(name)
 | |
|                 try:
 | |
|                     for value in new_values:
 | |
|                         expected, expect_flag = expect_func(value)
 | |
| 
 | |
|                         config_set(name, value)
 | |
|                         self.assertEqual(config_get(name), expected)
 | |
|                         self.assertEqual(getattr(sys.flags, sys_flag), expect_flag)
 | |
|                         if name == "write_bytecode":
 | |
|                             self.assertEqual(getattr(sys, "dont_write_bytecode"),
 | |
|                                              expect_flag)
 | |
|                         if name == "int_max_str_digits":
 | |
|                             self.assertEqual(sys.get_int_max_str_digits(),
 | |
|                                              expect_flag)
 | |
| 
 | |
|                     for value in invalid_values:
 | |
|                         with self.assertRaises(ValueError):
 | |
|                             config_set(name, value)
 | |
| 
 | |
|                     for value in invalid_types:
 | |
|                         with self.assertRaises(TypeError):
 | |
|                             config_set(name, value)
 | |
|                 finally:
 | |
|                     config_set(name, old_value)
 | |
| 
 | |
|     def test_config_set_read_only(self):
 | |
|         # Test PyConfig_Set() on read-only options
 | |
|         config_set = _testcapi.config_set
 | |
|         for name, value in (
 | |
|             ("allocator", 0),  # PyPreConfig member
 | |
|             ("cpu_count", 8),
 | |
|             ("dev_mode", True),
 | |
|             ("filesystem_encoding", "utf-8"),
 | |
|         ):
 | |
|             with self.subTest(name=name, value=value):
 | |
|                 with self.assertRaisesRegex(ValueError, r"read-only"):
 | |
|                     config_set(name, value)
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     unittest.main()
 | 
