Skip to content

Commit 214b751

Browse files
committed
gh-128911: Add tests on the PyImport C API
* Add Modules/_testlimitedcapi/import.c * Add Lib/test/test_capi/test_import.py * Remove _testcapi.check_pyimport_addmodule(): tests already covered by newly added tests.
1 parent 313b96e commit 214b751

File tree

7 files changed

+270
-72
lines changed

7 files changed

+270
-72
lines changed

Lib/test/test_capi/test_import.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import importlib.util
2+
import sys
3+
import types
4+
import unittest
5+
from test.support import import_helper
6+
from test.support.warnings_helper import check_warnings
7+
8+
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
9+
10+
11+
class ImportTests(unittest.TestCase):
12+
def test_getmagicnumber(self):
13+
# Test PyImport_GetMagicNumber()
14+
magic = _testlimitedcapi.PyImport_GetMagicNumber()
15+
self.assertEqual(magic,
16+
int.from_bytes(importlib.util.MAGIC_NUMBER, 'little'))
17+
18+
def test_getmagictag(self):
19+
# Test PyImport_GetMagicTag()
20+
tag = _testlimitedcapi.PyImport_GetMagicTag()
21+
self.assertEqual(tag, sys.implementation.cache_tag)
22+
23+
def test_getmoduledict(self):
24+
# Test PyImport_GetModuleDict()
25+
modules = _testlimitedcapi.PyImport_GetModuleDict()
26+
self.assertIs(modules, sys.modules)
27+
28+
def check_import_loaded_module(self, import_module):
29+
for name in ('os', 'sys', 'test', 'unittest'):
30+
with self.subTest(name=name):
31+
self.assertIn(name, sys.modules)
32+
module = import_module(name)
33+
self.assertIsInstance(module, types.ModuleType)
34+
self.assertIs(module, sys.modules[name])
35+
36+
def check_import_fresh_module(self, import_module):
37+
old_modules = dict(sys.modules)
38+
try:
39+
for name in ('asyncio', 'colorsys', 'datetime'):
40+
with self.subTest(name=name):
41+
sys.modules.pop(name, None)
42+
module = import_module(name)
43+
self.assertIsInstance(module, types.ModuleType)
44+
self.assertIs(module, sys.modules[name])
45+
self.assertEqual(module.__name__, name)
46+
finally:
47+
sys.modules.clear()
48+
sys.modules.update(old_modules)
49+
50+
def test_getmodule(self):
51+
# Test PyImport_GetModule()
52+
self.check_import_loaded_module(_testlimitedcapi.PyImport_GetModule)
53+
54+
nonexistent = 'nonexistent'
55+
self.assertNotIn(nonexistent, sys.modules)
56+
self.assertIsNone(_testlimitedcapi.PyImport_GetModule(nonexistent))
57+
self.assertIsNone(_testlimitedcapi.PyImport_GetModule(''))
58+
self.assertIsNone(_testlimitedcapi.PyImport_GetModule(object()))
59+
60+
def check_addmodule(self, add_module):
61+
# create a new module
62+
name = 'nonexistent'
63+
self.assertNotIn(name, sys.modules)
64+
try:
65+
module = add_module(name)
66+
self.assertIsInstance(module, types.ModuleType)
67+
self.assertIs(module, sys.modules[name])
68+
finally:
69+
sys.modules.pop(name, None)
70+
71+
# get an existing module
72+
self.check_import_loaded_module(add_module)
73+
74+
def test_addmoduleobject(self):
75+
# Test PyImport_AddModuleObject()
76+
self.check_addmodule(_testlimitedcapi.PyImport_AddModuleObject)
77+
78+
def test_addmodule(self):
79+
# Test PyImport_AddModule()
80+
self.check_addmodule(_testlimitedcapi.PyImport_AddModule)
81+
82+
def test_addmoduleref(self):
83+
# Test PyImport_AddModuleRef()
84+
self.check_addmodule(_testlimitedcapi.PyImport_AddModuleRef)
85+
86+
def check_import_func(self, import_module):
87+
self.check_import_loaded_module(import_module)
88+
self.check_import_fresh_module(import_module)
89+
90+
# Invalid module name types
91+
with self.assertRaises(TypeError):
92+
import_module(123)
93+
with self.assertRaises(TypeError):
94+
import_module(object())
95+
96+
def test_import(self):
97+
# Test PyImport_Import()
98+
self.check_import_func(_testlimitedcapi.PyImport_Import)
99+
100+
def test_importmodule(self):
101+
# Test PyImport_ImportModule()
102+
self.check_import_func(_testlimitedcapi.PyImport_ImportModule)
103+
104+
def test_importmodulenoblock(self):
105+
# Test deprecated PyImport_ImportModuleNoBlock()
106+
with check_warnings(('', DeprecationWarning)):
107+
self.check_import_func(_testlimitedcapi.PyImport_ImportModuleNoBlock)
108+
109+
# TODO: test PyImport_ExecCodeModule()
110+
# TODO: test PyImport_ExecCodeModuleEx()
111+
# TODO: test PyImport_ExecCodeModuleWithPathnames()
112+
# TODO: test PyImport_ExecCodeModuleObject()
113+
# TODO: test PyImport_ImportModuleLevel()
114+
# TODO: test PyImport_ImportModuleLevelObject()
115+
# TODO: test PyImport_ImportModuleEx()
116+
# TODO: test PyImport_GetImporter()
117+
# TODO: test PyImport_ReloadModule()
118+
# TODO: test PyImport_ImportFrozenModuleObject()
119+
# TODO: test PyImport_ImportFrozenModule()
120+
# TODO: test PyImport_AppendInittab()
121+
# TODO: test PyImport_ExtendInittab()
122+
123+
124+
if __name__ == "__main__":
125+
unittest.main()

Lib/test/test_import/__init__.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3311,30 +3311,6 @@ def test_basic_multiple_interpreters_reset_each(self):
33113311
# * module's global state was initialized, not reset
33123312

33133313

3314-
@cpython_only
3315-
class CAPITests(unittest.TestCase):
3316-
def test_pyimport_addmodule(self):
3317-
# gh-105922: Test PyImport_AddModuleRef(), PyImport_AddModule()
3318-
# and PyImport_AddModuleObject()
3319-
_testcapi = import_module("_testcapi")
3320-
for name in (
3321-
'sys', # frozen module
3322-
'test', # package
3323-
__name__, # package.module
3324-
):
3325-
_testcapi.check_pyimport_addmodule(name)
3326-
3327-
def test_pyimport_addmodule_create(self):
3328-
# gh-105922: Test PyImport_AddModuleRef(), create a new module
3329-
_testcapi = import_module("_testcapi")
3330-
name = 'dontexist'
3331-
self.assertNotIn(name, sys.modules)
3332-
self.addCleanup(unload, name)
3333-
3334-
mod = _testcapi.check_pyimport_addmodule(name)
3335-
self.assertIs(mod, sys.modules[name])
3336-
3337-
33383314
@cpython_only
33393315
class TestMagicNumber(unittest.TestCase):
33403316
def test_magic_number_endianness(self):

Modules/Setup.stdlib.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
164164
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
165165
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c
166-
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c
166+
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c
167167
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
168168
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
169169

Modules/_testcapimodule.c

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3059,52 +3059,6 @@ function_set_closure(PyObject *self, PyObject *args)
30593059
Py_RETURN_NONE;
30603060
}
30613061

3062-
static PyObject *
3063-
check_pyimport_addmodule(PyObject *self, PyObject *args)
3064-
{
3065-
const char *name;
3066-
if (!PyArg_ParseTuple(args, "s", &name)) {
3067-
return NULL;
3068-
}
3069-
3070-
// test PyImport_AddModuleRef()
3071-
PyObject *module = PyImport_AddModuleRef(name);
3072-
if (module == NULL) {
3073-
return NULL;
3074-
}
3075-
assert(PyModule_Check(module));
3076-
// module is a strong reference
3077-
3078-
// test PyImport_AddModule()
3079-
PyObject *module2 = PyImport_AddModule(name);
3080-
if (module2 == NULL) {
3081-
goto error;
3082-
}
3083-
assert(PyModule_Check(module2));
3084-
assert(module2 == module);
3085-
// module2 is a borrowed ref
3086-
3087-
// test PyImport_AddModuleObject()
3088-
PyObject *name_obj = PyUnicode_FromString(name);
3089-
if (name_obj == NULL) {
3090-
goto error;
3091-
}
3092-
PyObject *module3 = PyImport_AddModuleObject(name_obj);
3093-
Py_DECREF(name_obj);
3094-
if (module3 == NULL) {
3095-
goto error;
3096-
}
3097-
assert(PyModule_Check(module3));
3098-
assert(module3 == module);
3099-
// module3 is a borrowed ref
3100-
3101-
return module;
3102-
3103-
error:
3104-
Py_DECREF(module);
3105-
return NULL;
3106-
}
3107-
31083062

31093063
static PyObject *
31103064
test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
@@ -3570,7 +3524,6 @@ static PyMethodDef TestMethods[] = {
35703524
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
35713525
{"function_get_closure", function_get_closure, METH_O, NULL},
35723526
{"function_set_closure", function_set_closure, METH_VARARGS, NULL},
3573-
{"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
35743527
{"test_weakref_capi", test_weakref_capi, METH_NOARGS},
35753528
{"function_set_warning", function_set_warning, METH_NOARGS},
35763529
{"test_critical_sections", test_critical_sections, METH_NOARGS},

Modules/_testlimitedcapi.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ PyInit__testlimitedcapi(void)
5656
if (_PyTestLimitedCAPI_Init_HeaptypeRelative(mod) < 0) {
5757
return NULL;
5858
}
59+
if (_PyTestLimitedCAPI_Init_Import(mod) < 0) {
60+
return NULL;
61+
}
5962
if (_PyTestLimitedCAPI_Init_List(mod) < 0) {
6063
return NULL;
6164
}

Modules/_testlimitedcapi/import.c

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Need limited C API version 3.7 for PyImport_GetModule()
2+
#include "pyconfig.h" // Py_GIL_DISABLED
3+
#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API)
4+
# define Py_LIMITED_API 0x030d0000
5+
#endif
6+
7+
#include "parts.h"
8+
#include "util.h"
9+
10+
11+
/* Test PyImport_GetMagicNumber() */
12+
static PyObject *
13+
pyimport_getmagicnumber(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
14+
{
15+
long magic = PyImport_GetMagicNumber();
16+
return PyLong_FromLong(magic);
17+
}
18+
19+
20+
/* Test PyImport_GetMagicTag() */
21+
static PyObject *
22+
pyimport_getmagictag(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
23+
{
24+
const char *tag = PyImport_GetMagicTag();
25+
return PyUnicode_FromString(tag);
26+
}
27+
28+
29+
/* Test PyImport_GetModuleDict() */
30+
static PyObject *
31+
pyimport_getmoduledict(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
32+
{
33+
return Py_XNewRef(PyImport_GetModuleDict());
34+
}
35+
36+
37+
/* Test PyImport_GetModule() */
38+
static PyObject *
39+
pyimport_getmodule(PyObject *Py_UNUSED(module), PyObject *name)
40+
{
41+
assert(!PyErr_Occurred());
42+
PyObject *module = PyImport_GetModule(name);
43+
if (module == NULL && !PyErr_Occurred()) {
44+
Py_RETURN_NONE;
45+
}
46+
return module;
47+
}
48+
49+
50+
/* Test PyImport_AddModuleObject() */
51+
static PyObject *
52+
pyimport_addmoduleobject(PyObject *Py_UNUSED(module), PyObject *name)
53+
{
54+
return Py_XNewRef(PyImport_AddModuleObject(name));
55+
}
56+
57+
58+
/* Test PyImport_AddModule() */
59+
static PyObject *
60+
pyimport_addmodule(PyObject *Py_UNUSED(module), PyObject *args)
61+
{
62+
const char *name;
63+
if (!PyArg_ParseTuple(args, "s", &name)) {
64+
return NULL;
65+
}
66+
67+
return Py_XNewRef(PyImport_AddModule(name));
68+
}
69+
70+
71+
/* Test PyImport_AddModuleRef() */
72+
static PyObject *
73+
pyimport_addmoduleref(PyObject *Py_UNUSED(module), PyObject *args)
74+
{
75+
const char *name;
76+
if (!PyArg_ParseTuple(args, "s", &name)) {
77+
return NULL;
78+
}
79+
80+
return PyImport_AddModuleRef(name);
81+
}
82+
83+
84+
/* Test PyImport_Import() */
85+
static PyObject *
86+
pyimport_import(PyObject *Py_UNUSED(module), PyObject *name)
87+
{
88+
return PyImport_Import(name);
89+
}
90+
91+
92+
/* Test PyImport_ImportModule() */
93+
static PyObject *
94+
pyimport_importmodule(PyObject *Py_UNUSED(module), PyObject *args)
95+
{
96+
const char *name;
97+
if (!PyArg_ParseTuple(args, "s", &name)) {
98+
return NULL;
99+
}
100+
101+
return PyImport_ImportModule(name);
102+
}
103+
104+
105+
/* Test PyImport_ImportModuleNoBlock() */
106+
static PyObject *
107+
pyimport_importmodulenoblock(PyObject *Py_UNUSED(module), PyObject *args)
108+
{
109+
const char *name;
110+
if (!PyArg_ParseTuple(args, "s", &name)) {
111+
return NULL;
112+
}
113+
114+
_Py_COMP_DIAG_PUSH
115+
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
116+
return PyImport_ImportModuleNoBlock(name);
117+
_Py_COMP_DIAG_POP
118+
}
119+
120+
121+
static PyMethodDef test_methods[] = {
122+
{"PyImport_GetMagicNumber", pyimport_getmagicnumber, METH_NOARGS},
123+
{"PyImport_GetMagicTag", pyimport_getmagictag, METH_NOARGS},
124+
{"PyImport_GetModuleDict", pyimport_getmoduledict, METH_NOARGS},
125+
{"PyImport_GetModule", pyimport_getmodule, METH_O},
126+
{"PyImport_AddModuleObject", pyimport_addmoduleobject, METH_O},
127+
{"PyImport_AddModule", pyimport_addmodule, METH_VARARGS},
128+
{"PyImport_AddModuleRef", pyimport_addmoduleref, METH_VARARGS},
129+
{"PyImport_Import", pyimport_import, METH_O},
130+
{"PyImport_ImportModule", pyimport_importmodule, METH_VARARGS},
131+
{"PyImport_ImportModuleNoBlock", pyimport_importmodulenoblock, METH_VARARGS},
132+
{NULL},
133+
};
134+
135+
136+
int
137+
_PyTestLimitedCAPI_Init_Import(PyObject *module)
138+
{
139+
return PyModule_AddFunctions(module, test_methods);
140+
}

Modules/_testlimitedcapi/parts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ int _PyTestLimitedCAPI_Init_Dict(PyObject *module);
3131
int _PyTestLimitedCAPI_Init_Eval(PyObject *module);
3232
int _PyTestLimitedCAPI_Init_Float(PyObject *module);
3333
int _PyTestLimitedCAPI_Init_HeaptypeRelative(PyObject *module);
34+
int _PyTestLimitedCAPI_Init_Import(PyObject *module);
3435
int _PyTestLimitedCAPI_Init_Object(PyObject *module);
3536
int _PyTestLimitedCAPI_Init_List(PyObject *module);
3637
int _PyTestLimitedCAPI_Init_Long(PyObject *module);

0 commit comments

Comments
 (0)