Skip to content

Commit

Permalink
bpo-42260: Add _PyConfig_FromDict() (pythonGH-23167)
Browse files Browse the repository at this point in the history
* Rename config_as_dict() to _PyConfig_AsDict().
* Add 'module_search_paths_set' to _PyConfig_AsDict().
* Add _PyConfig_FromDict().
* Add get_config() and set_config() to _testinternalcapi.
* Add config_check_consistency().
  • Loading branch information
vstinner authored Nov 5, 2020
1 parent 4662fa9 commit f3cb814
Show file tree
Hide file tree
Showing 6 changed files with 653 additions and 60 deletions.
3 changes: 3 additions & 0 deletions Include/internal/pycore_initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ extern PyStatus _PyConfig_SetPyArgv(
PyConfig *config,
const _PyArgv *args);

PyAPI_FUNC(PyObject*) _PyConfig_AsDict(const PyConfig *config);
PyAPI_FUNC(int) _PyConfig_FromDict(PyConfig *config, PyObject *dict);


/* --- Function used for testing ---------------------------------- */

Expand Down
243 changes: 243 additions & 0 deletions Lib/test/_test_embed_set_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# bpo-42260: Test _PyInterpreterState_GetConfigCopy()
# and _PyInterpreterState_SetConfig().
#
# Test run in a subinterpreter since set_config(get_config())
# does reset sys attributes to their state of the Python startup
# (before the site module is run).

import _testinternalcapi
import os
import sys
import unittest


MS_WINDOWS = (os.name == 'nt')
MAX_HASH_SEED = 4294967295

class SetConfigTests(unittest.TestCase):
def setUp(self):
self.old_config = _testinternalcapi.get_config()
self.sys_copy = dict(sys.__dict__)

def tearDown(self):
self.set_config(parse_argv=0)
sys.__dict__.clear()
sys.__dict__.update(self.sys_copy)

def set_config(self, **kwargs):
_testinternalcapi.set_config(self.old_config | kwargs)

def check(self, **kwargs):
self.set_config(**kwargs)
for key, value in kwargs.items():
self.assertEqual(getattr(sys, key), value,
(key, value))

def test_set_invalid(self):
invalid_uint = -1
NULL = None
invalid_wstr = NULL
# PyWideStringList strings must be non-NULL
invalid_wstrlist = ["abc", NULL, "def"]

type_tests = []
value_tests = [
# enum
('_config_init', 0),
('_config_init', 4),
# unsigned long
("hash_seed", -1),
("hash_seed", MAX_HASH_SEED + 1),
]

# int (unsigned)
options = [
'_config_init',
'isolated',
'use_environment',
'dev_mode',
'install_signal_handlers',
'use_hash_seed',
'faulthandler',
'tracemalloc',
'import_time',
'show_ref_count',
'dump_refs',
'malloc_stats',
'parse_argv',
'site_import',
'bytes_warning',
'inspect',
'interactive',
'optimization_level',
'parser_debug',
'write_bytecode',
'verbose',
'quiet',
'user_site_directory',
'configure_c_stdio',
'buffered_stdio',
'pathconfig_warnings',
'module_search_paths_set',
'skip_source_first_line',
'_install_importlib',
'_init_main',
'_isolated_interpreter',
]
if MS_WINDOWS:
options.append('legacy_windows_stdio')
for key in options:
value_tests.append((key, invalid_uint))
type_tests.append((key, "abc"))
type_tests.append((key, 2.0))

# wchar_t*
for key in (
'filesystem_encoding',
'filesystem_errors',
'stdio_encoding',
'stdio_errors',
'check_hash_pycs_mode',
'program_name',
'platlibdir',
'executable',
'base_executable',
'prefix',
'base_prefix',
'exec_prefix',
'base_exec_prefix',
# optional wstr:
# 'pythonpath_env'
# 'home',
# 'pycache_prefix'
# 'run_command'
# 'run_module'
# 'run_filename'
):
value_tests.append((key, invalid_wstr))
type_tests.append((key, b'bytes'))
type_tests.append((key, 123))

# PyWideStringList
for key in (
'orig_argv',
'argv',
'xoptions',
'warnoptions',
'module_search_paths',
):
value_tests.append((key, invalid_wstrlist))
type_tests.append((key, 123))
type_tests.append((key, "abc"))
type_tests.append((key, [123]))
type_tests.append((key, [b"bytes"]))


if MS_WINDOWS:
value_tests.append(('legacy_windows_stdio', invalid_uint))

for exc_type, tests in (
(ValueError, value_tests),
(TypeError, type_tests),
):
for key, value in tests:
config = self.old_config | {key: value}
with self.subTest(key=key, value=value, exc_type=exc_type):
with self.assertRaises(exc_type):
_testinternalcapi.set_config(config)

def test_flags(self):
for sys_attr, key, value in (
("debug", "parser_debug", 1),
("inspect", "inspect", 2),
("interactive", "interactive", 3),
("optimize", "optimization_level", 4),
("verbose", "verbose", 1),
("bytes_warning", "bytes_warning", 10),
("quiet", "quiet", 11),
("isolated", "isolated", 12),
):
with self.subTest(sys=sys_attr, key=key, value=value):
self.set_config(**{key: value, 'parse_argv': 0})
self.assertEqual(getattr(sys.flags, sys_attr), value)

self.set_config(write_bytecode=0)
self.assertEqual(sys.flags.dont_write_bytecode, True)
self.assertEqual(sys.dont_write_bytecode, True)

self.set_config(write_bytecode=1)
self.assertEqual(sys.flags.dont_write_bytecode, False)
self.assertEqual(sys.dont_write_bytecode, False)

self.set_config(user_site_directory=0, isolated=0)
self.assertEqual(sys.flags.no_user_site, 1)
self.set_config(user_site_directory=1, isolated=0)
self.assertEqual(sys.flags.no_user_site, 0)

self.set_config(site_import=0)
self.assertEqual(sys.flags.no_site, 1)
self.set_config(site_import=1)
self.assertEqual(sys.flags.no_site, 0)

self.set_config(dev_mode=0)
self.assertEqual(sys.flags.dev_mode, False)
self.set_config(dev_mode=1)
self.assertEqual(sys.flags.dev_mode, True)

self.set_config(use_environment=0, isolated=0)
self.assertEqual(sys.flags.ignore_environment, 1)
self.set_config(use_environment=1, isolated=0)
self.assertEqual(sys.flags.ignore_environment, 0)

self.set_config(use_hash_seed=1, hash_seed=0)
self.assertEqual(sys.flags.hash_randomization, 0)
self.set_config(use_hash_seed=0, hash_seed=0)
self.assertEqual(sys.flags.hash_randomization, 1)
self.set_config(use_hash_seed=1, hash_seed=123)
self.assertEqual(sys.flags.hash_randomization, 1)

def test_options(self):
self.check(warnoptions=[])
self.check(warnoptions=["default", "ignore"])

self.set_config(xoptions=[])
self.assertEqual(sys._xoptions, {})
self.set_config(xoptions=["dev", "tracemalloc=5"])
self.assertEqual(sys._xoptions, {"dev": True, "tracemalloc": "5"})

def test_pathconfig(self):
self.check(
executable='executable',
prefix="prefix",
base_prefix="base_prefix",
exec_prefix="exec_prefix",
base_exec_prefix="base_exec_prefix",
platlibdir="platlibdir")

self.set_config(base_executable="base_executable")
self.assertEqual(sys._base_executable, "base_executable")

def test_path(self):
self.set_config(module_search_paths_set=1,
module_search_paths=['a', 'b', 'c'])
self.assertEqual(sys.path, ['a', 'b', 'c'])

# Leave sys.path unchanged if module_search_paths_set=0
self.set_config(module_search_paths_set=0,
module_search_paths=['new_path'])
self.assertEqual(sys.path, ['a', 'b', 'c'])

def test_argv(self):
self.set_config(parse_argv=0,
argv=['python_program', 'args'],
orig_argv=['orig', 'orig_args'])
self.assertEqual(sys.argv, ['python_program', 'args'])
self.assertEqual(sys.orig_argv, ['orig', 'orig_args'])

def test_pycache_prefix(self):
self.check(pycache_prefix=None)
self.check(pycache_prefix="pycache_prefix")


if __name__ == "__main__":
unittest.main()
14 changes: 14 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
# _PyCoreConfig_InitIsolatedConfig()
API_ISOLATED = 3

MAX_HASH_SEED = 4294967295


def debug_build(program):
program = os.path.basename(program)
Expand Down Expand Up @@ -382,6 +384,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'exec_prefix': GET_DEFAULT_CONFIG,
'base_exec_prefix': GET_DEFAULT_CONFIG,
'module_search_paths': GET_DEFAULT_CONFIG,
'module_search_paths_set': 1,
'platlibdir': sys.platlibdir,

'site_import': 1,
Expand Down Expand Up @@ -1408,6 +1411,17 @@ def test_get_argc_argv(self):
# ignore output


class SetConfigTests(unittest.TestCase):
def test_set_config(self):
# bpo-42260: Test _PyInterpreterState_SetConfig()
cmd = [sys.executable, '-I', '-m', 'test._test_embed_set_config']
proc = subprocess.run(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
self.assertEqual(proc.returncode, 0,
(proc.returncode, proc.stdout, proc.stderr))


class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
def test_open_code_hook(self):
self.run_embedded_interpreter("test_open_code_hook")
Expand Down
39 changes: 37 additions & 2 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@

#include "Python.h"
#include "pycore_bitutils.h" // _Py_bswap32()
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
#include "pycore_hashtable.h" // _Py_hashtable_new()
#include "pycore_gc.h" // PyGC_Head
#include "pycore_hashtable.h" // _Py_hashtable_new()
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
#include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy()


static PyObject *
Expand Down Expand Up @@ -231,13 +232,47 @@ test_hashtable(PyObject *self, PyObject *Py_UNUSED(args))
}


static PyObject *
test_get_config(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
PyConfig config;
PyConfig_InitIsolatedConfig(&config);
if (_PyInterpreterState_GetConfigCopy(&config) < 0) {
PyConfig_Clear(&config);
return NULL;
}
PyObject *dict = _PyConfig_AsDict(&config);
PyConfig_Clear(&config);
return dict;
}


static PyObject *
test_set_config(PyObject *Py_UNUSED(self), PyObject *dict)
{
PyConfig config;
PyConfig_InitIsolatedConfig(&config);
if (_PyConfig_FromDict(&config, dict) < 0) {
PyConfig_Clear(&config);
return NULL;
}
if (_PyInterpreterState_SetConfig(&config) < 0) {
return NULL;
}
PyConfig_Clear(&config);
Py_RETURN_NONE;
}


static PyMethodDef TestMethods[] = {
{"get_configs", get_configs, METH_NOARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
{"test_bswap", test_bswap, METH_NOARGS},
{"test_popcount", test_popcount, METH_NOARGS},
{"test_bit_length", test_bit_length, METH_NOARGS},
{"test_hashtable", test_hashtable, METH_NOARGS},
{"get_config", test_get_config, METH_NOARGS},
{"set_config", test_set_config, METH_O},
{NULL, NULL} /* sentinel */
};

Expand Down
Loading

0 comments on commit f3cb814

Please sign in to comment.