mirror of https://github.com/python/cpython
380 lines
15 KiB
Python
380 lines
15 KiB
Python
|
"""
|
||
|
Tests PyConfig_Get() and PyConfig_Set() C API (PEP 741).
|
||
|
"""
|
||
|
import os
|
||
|
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", bool, 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))
|
||
|
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),
|
||
|
))
|
||
|
|
||
|
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()
|