Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 39 additions & 15 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,21 +241,7 @@ def test_repeated_init_and_inittab(self):

def test_create_module_from_initfunc(self):
out, err = self.run_embedded_interpreter("test_create_module_from_initfunc")
if support.Py_GIL_DISABLED:
# the test imports a singlephase init extension, so it emits a warning
# under the free-threaded build
expected_runtime_warning = (
"RuntimeWarning: The global interpreter lock (GIL)"
" has been enabled to load module 'embedded_ext'"
)
filtered_err_lines = [
line
for line in err.strip().splitlines()
if expected_runtime_warning not in line
]
self.assertEqual(filtered_err_lines, [])
else:
self.assertEqual(err, "")
self.assertEqual(self._nogil_filtered_err(err, "embedded_ext"), "")
self.assertEqual(out,
"<module 'my_test_extension' (static-extension)>\n"
"my_test_extension.executed='yes'\n"
Expand All @@ -264,6 +250,26 @@ def test_create_module_from_initfunc(self):
"embedded_ext.executed='yes'\n"
)

def test_inittab_submodule_multiphase(self):
out, err = self.run_embedded_interpreter("test_inittab_submodule_multiphase")
self.assertEqual(err, "")
self.assertEqual(out,
"<module 'mp_pkg.mp_submod' (built-in)>\n"
"<module 'mp_pkg.mp_submod' (built-in)>\n"
"Hello from sub-module\n"
"mp_pkg.mp_submod.mp_submod_exec_slot_ran='yes'\n"
"mp_pkg.mp_pkg_exec_slot_ran='yes'\n"
)

def test_inittab_submodule_singlephase(self):
out, err = self.run_embedded_interpreter("test_inittab_submodule_singlephase")
self.assertEqual(self._nogil_filtered_err(err, "sp_pkg"), "")
self.assertEqual(out,
"<module 'sp_pkg.sp_submod' (built-in)>\n"
"<module 'sp_pkg.sp_submod' (built-in)>\n"
"Hello from sub-module\n"
)

def test_forced_io_encoding(self):
# Checks forced configuration of embedded interpreter IO streams
env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
Expand Down Expand Up @@ -541,6 +547,24 @@ def test_getargs_reset_static_parser(self):
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
self.assertEqual(out, '1\n2\n3\n' * INIT_LOOPS)

@staticmethod
def _nogil_filtered_err(err: str, mod_name: str) -> str:
if not support.Py_GIL_DISABLED:
return err

# the test imports a singlephase init extension, so it emits a warning
# under the free-threaded build
expected_runtime_warning = (
"RuntimeWarning: The global interpreter lock (GIL)"
f" has been enabled to load module '{mod_name}'"
)
filtered_err_lines = [
line
for line in err.strip().splitlines()
if expected_runtime_warning not in line
]
return "\n".join(filtered_err_lines)


def config_dev_mode(preconfig, config):
preconfig['allocator'] = PYMEM_ALLOCATOR_DEBUG
Expand Down
173 changes: 173 additions & 0 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -2323,6 +2323,177 @@ test_create_module_from_initfunc(void)
return Py_RunMain();
}

/// Multi-phase initialization package & submodule ///

int
mp_pkg_exec(PyObject *mod)
{
// make this a namespace package
// empty list = namespace package
if (PyModule_Add(mod, "__path__", PyList_New(0)) < 0) {
return -1;
}
if (PyModule_AddStringConstant(mod, "mp_pkg_exec_slot_ran", "yes") < 0) {
return -1;
}
return 0;
}

static PyModuleDef_Slot mp_pkg_slots[] = {
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{Py_mod_exec, mp_pkg_exec},
{0, NULL}
};

static struct PyModuleDef mp_pkg_def = {
PyModuleDef_HEAD_INIT,
.m_name = "mp_pkg",
.m_size = 0,
.m_slots = mp_pkg_slots,
};

PyMODINIT_FUNC
PyInit_mp_pkg(void)
{
return PyModuleDef_Init(&mp_pkg_def);
}

static PyObject *
submod_greet(PyObject *self, PyObject *Py_UNUSED(ignored))
{
return PyUnicode_FromString("Hello from sub-module");
}

static PyMethodDef submod_methods[] = {
{"greet", submod_greet, METH_NOARGS, NULL},
{NULL},
};

int
mp_submod_exec(PyObject *mod)
{
return PyModule_AddStringConstant(mod, "mp_submod_exec_slot_ran", "yes");
}

static PyModuleDef_Slot mp_submod_slots[] = {
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{Py_mod_exec, mp_submod_exec},
{0, NULL}
};

static struct PyModuleDef mp_submod_def = {
PyModuleDef_HEAD_INIT,
.m_name = "mp_pkg.mp_submod",
.m_size = 0,
.m_methods = submod_methods,
.m_slots = mp_submod_slots,
};

PyMODINIT_FUNC
PyInit_mp_submod(void)
{
return PyModuleDef_Init(&mp_submod_def);
}

static int
test_inittab_submodule_multiphase(void)
{
wchar_t* argv[] = {
PROGRAM_NAME,
L"-c",
L"import sys;"
L"import mp_pkg.mp_submod;"
L"print(mp_pkg.mp_submod);"
L"print(sys.modules['mp_pkg.mp_submod']);"
L"print(mp_pkg.mp_submod.greet());"
L"print(f'{mp_pkg.mp_submod.mp_submod_exec_slot_ran=}');"
L"print(f'{mp_pkg.mp_pkg_exec_slot_ran=}');"
};
PyConfig config;
if (PyImport_AppendInittab("mp_pkg",
&PyInit_mp_pkg) != 0) {
fprintf(stderr, "PyImport_AppendInittab() failed\n");
return 1;
}
if (PyImport_AppendInittab("mp_pkg.mp_submod",
&PyInit_mp_submod) != 0) {
fprintf(stderr, "PyImport_AppendInittab() failed\n");
return 1;
}
PyConfig_InitPythonConfig(&config);
config.isolated = 1;
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
init_from_config_clear(&config);
return Py_RunMain();
}

/// Single-phase initialization package & submodule ///

static struct PyModuleDef sp_pkg_def = {
PyModuleDef_HEAD_INIT,
.m_name = "sp_pkg",
.m_size = 0,
};

PyMODINIT_FUNC
PyInit_sp_pkg(void)
{
PyObject *mod = PyModule_Create(&sp_pkg_def);
if (mod == NULL) {
return NULL;
}
// make this a namespace package
// empty list = namespace package
if (PyModule_Add(mod, "__path__", PyList_New(0)) < 0) {
Py_DECREF(mod);
return NULL;
}
return mod;
}

static struct PyModuleDef sp_submod_def = {
PyModuleDef_HEAD_INIT,
.m_name = "sp_pkg.sp_submod",
.m_size = 0,
.m_methods = submod_methods,
};

PyMODINIT_FUNC
PyInit_sp_submod(void)
{
return PyModule_Create(&sp_submod_def);
}

static int
test_inittab_submodule_singlephase(void)
{
wchar_t* argv[] = {
PROGRAM_NAME,
L"-c",
L"import sys;"
L"import sp_pkg.sp_submod;"
L"print(sp_pkg.sp_submod);"
L"print(sys.modules['sp_pkg.sp_submod']);"
L"print(sp_pkg.sp_submod.greet());"
};
PyConfig config;
if (PyImport_AppendInittab("sp_pkg",
&PyInit_sp_pkg) != 0) {
fprintf(stderr, "PyImport_AppendInittab() failed\n");
return 1;
}
if (PyImport_AppendInittab("sp_pkg.sp_submod",
&PyInit_sp_submod) != 0) {
fprintf(stderr, "PyImport_AppendInittab() failed\n");
return 1;
}
PyConfig_InitPythonConfig(&config);
config.isolated = 1;
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
init_from_config_clear(&config);
return Py_RunMain();
}

static void wrap_allocator(PyMemAllocatorEx *allocator);
static void unwrap_allocator(PyMemAllocatorEx *allocator);

Expand Down Expand Up @@ -2507,6 +2678,8 @@ static struct TestCase TestCases[] = {
{"test_get_incomplete_frame", test_get_incomplete_frame},
{"test_gilstate_after_finalization", test_gilstate_after_finalization},
{"test_create_module_from_initfunc", test_create_module_from_initfunc},
{"test_inittab_submodule_multiphase", test_inittab_submodule_multiphase},
{"test_inittab_submodule_singlephase", test_inittab_submodule_singlephase},
{NULL, NULL}
};

Expand Down
1 change: 0 additions & 1 deletion Python/importdl.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ _Py_ext_module_loader_info_init_for_builtin(
PyObject *name)
{
assert(PyUnicode_Check(name));
assert(PyUnicode_FindChar(name, '.', 0, PyUnicode_GetLength(name), -1) == -1);
assert(PyUnicode_GetLength(name) > 0);

PyObject *name_encoded = PyUnicode_AsEncodedString(name, "ascii", NULL);
Expand Down
Loading