Skip to content

Commit b510374

Browse files
Add the _testsinglephase module.
1 parent 4702552 commit b510374

File tree

7 files changed

+183
-19
lines changed

7 files changed

+183
-19
lines changed

Lib/test/test_importlib/extension/test_case_sensitivity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
machinery = util.import_importlib('importlib.machinery')
99

1010

11-
@unittest.skipIf(util.EXTENSIONS.filename is None, '_testcapi not available')
11+
@unittest.skipIf(util.EXTENSIONS.filename is None, f'{util.EXTENSIONS.name} not available')
1212
@util.case_insensitive_tests
1313
class ExtensionModuleCaseSensitivityTest(util.CASEOKTestBase):
1414

Lib/test/test_importlib/extension/test_loader.py

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
from test.support.script_helper import assert_python_failure
1414

1515

16-
class LoaderTests(abc.LoaderTests):
16+
class LoaderTests:
1717

18-
"""Test load_module() for extension modules."""
18+
"""Test ExtensionFileLoader."""
1919

2020
def setUp(self):
2121
if not self.machinery.EXTENSION_SUFFIXES:
@@ -32,15 +32,6 @@ def load_module(self, fullname):
3232
warnings.simplefilter("ignore", DeprecationWarning)
3333
return self.loader.load_module(fullname)
3434

35-
def test_load_module_API(self):
36-
# Test the default argument for load_module().
37-
with warnings.catch_warnings():
38-
warnings.simplefilter("ignore", DeprecationWarning)
39-
self.loader.load_module()
40-
self.loader.load_module(None)
41-
with self.assertRaises(ImportError):
42-
self.load_module('XXX')
43-
4435
def test_equality(self):
4536
other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
4637
util.EXTENSIONS.file_path)
@@ -51,6 +42,15 @@ def test_inequality(self):
5142
util.EXTENSIONS.file_path)
5243
self.assertNotEqual(self.loader, other)
5344

45+
def test_load_module_API(self):
46+
# Test the default argument for load_module().
47+
with warnings.catch_warnings():
48+
warnings.simplefilter("ignore", DeprecationWarning)
49+
self.loader.load_module()
50+
self.loader.load_module(None)
51+
with self.assertRaises(ImportError):
52+
self.load_module('XXX')
53+
5454
def test_module(self):
5555
with util.uncache(util.EXTENSIONS.name):
5656
module = self.load_module(util.EXTENSIONS.name)
@@ -68,12 +68,6 @@ def test_module(self):
6868
# No extension module in a package available for testing.
6969
test_lacking_parent = None
7070

71-
def test_module_reuse(self):
72-
with util.uncache(util.EXTENSIONS.name):
73-
module1 = self.load_module(util.EXTENSIONS.name)
74-
module2 = self.load_module(util.EXTENSIONS.name)
75-
self.assertIs(module1, module2)
76-
7771
# No easy way to trigger a failure after a successful import.
7872
test_state_after_failure = None
7973

@@ -83,17 +77,106 @@ def test_unloadable(self):
8377
self.load_module(name)
8478
self.assertEqual(cm.exception.name, name)
8579

80+
def test_module_reuse(self):
81+
with util.uncache(util.EXTENSIONS.name):
82+
module1 = self.load_module(util.EXTENSIONS.name)
83+
module2 = self.load_module(util.EXTENSIONS.name)
84+
self.assertIs(module1, module2)
85+
8686
def test_is_package(self):
8787
self.assertFalse(self.loader.is_package(util.EXTENSIONS.name))
8888
for suffix in self.machinery.EXTENSION_SUFFIXES:
8989
path = os.path.join('some', 'path', 'pkg', '__init__' + suffix)
9090
loader = self.machinery.ExtensionFileLoader('pkg', path)
9191
self.assertTrue(loader.is_package('pkg'))
9292

93+
9394
(Frozen_LoaderTests,
9495
Source_LoaderTests
9596
) = util.test_both(LoaderTests, machinery=machinery)
9697

98+
99+
class SinglePhaseExtensionModuleTests(abc.LoaderTests):
100+
# Test loading extension modules without multi-phase initialization.
101+
102+
def setUp(self):
103+
if not self.machinery.EXTENSION_SUFFIXES:
104+
raise unittest.SkipTest("Requires dynamic loading support.")
105+
self.name = '_testsinglephase'
106+
if self.name in sys.builtin_module_names:
107+
raise unittest.SkipTest(
108+
f"{self.name} is a builtin module"
109+
)
110+
finder = self.machinery.FileFinder(None)
111+
self.spec = importlib.util.find_spec(self.name)
112+
assert self.spec
113+
self.loader = self.machinery.ExtensionFileLoader(
114+
self.name, self.spec.origin)
115+
116+
def load_module(self):
117+
with warnings.catch_warnings():
118+
warnings.simplefilter("ignore", DeprecationWarning)
119+
return self.loader.load_module(self.name)
120+
121+
def load_module_by_name(self, fullname):
122+
# Load a module from the test extension by name.
123+
origin = self.spec.origin
124+
loader = self.machinery.ExtensionFileLoader(fullname, origin)
125+
spec = importlib.util.spec_from_loader(fullname, loader)
126+
module = importlib.util.module_from_spec(spec)
127+
loader.exec_module(module)
128+
return module
129+
130+
def test_module(self):
131+
# Test loading an extension module.
132+
with util.uncache(self.name):
133+
module = self.load_module()
134+
for attr, value in [('__name__', self.name),
135+
('__file__', self.spec.origin),
136+
('__package__', '')]:
137+
self.assertEqual(getattr(module, attr), value)
138+
with self.assertRaises(AttributeError):
139+
module.__path__
140+
self.assertIs(module, sys.modules[self.name])
141+
self.assertIsInstance(module.__loader__,
142+
self.machinery.ExtensionFileLoader)
143+
144+
# No extension module as __init__ available for testing.
145+
test_package = None
146+
147+
# No extension module in a package available for testing.
148+
test_lacking_parent = None
149+
150+
# No easy way to trigger a failure after a successful import.
151+
test_state_after_failure = None
152+
153+
def test_unloadable(self):
154+
name = 'asdfjkl;'
155+
with self.assertRaises(ImportError) as cm:
156+
self.load_module_by_name(name)
157+
self.assertEqual(cm.exception.name, name)
158+
159+
def test_unloadable_nonascii(self):
160+
# Test behavior with nonexistent module with non-ASCII name.
161+
name = 'fo\xf3'
162+
with self.assertRaises(ImportError) as cm:
163+
self.load_module_by_name(name)
164+
self.assertEqual(cm.exception.name, name)
165+
166+
# It may make sense to add the equivalent to
167+
# the following MultiPhaseExtensionModuleTests tests:
168+
#
169+
# * test_nonmodule
170+
# * test_nonmodule_with_methods
171+
# * test_bad_modules
172+
# * test_nonascii
173+
174+
175+
(Frozen_SinglePhaseExtensionModuleTests,
176+
Source_SinglePhaseExtensionModuleTests
177+
) = util.test_both(SinglePhaseExtensionModuleTests, machinery=machinery)
178+
179+
97180
class MultiPhaseExtensionModuleTests(abc.LoaderTests):
98181
# Test loading extension modules with multi-phase initialization (PEP 489).
99182

Lib/test/test_importlib/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
EXTENSIONS.ext = None
2828
EXTENSIONS.filename = None
2929
EXTENSIONS.file_path = None
30-
EXTENSIONS.name = '_testcapi'
30+
EXTENSIONS.name = '_testsinglephase'
3131

3232
def _extension_details():
3333
global EXTENSIONS

Modules/Setup

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ PYTHONPATH=$(COREPYTHONPATH)
291291
#_testcapi _testcapimodule.c
292292
#_testimportmultiple _testimportmultiple.c
293293
#_testmultiphase _testmultiphase.c
294+
#_testsinglephase _testsinglephase.c
294295

295296
# ---
296297
# Uncommenting the following line tells makesetup that all following modules

Modules/Setup.stdlib.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
*shared*
176176
@MODULE__TESTIMPORTMULTIPLE_TRUE@_testimportmultiple _testimportmultiple.c
177177
@MODULE__TESTMULTIPHASE_TRUE@_testmultiphase _testmultiphase.c
178+
@MODULE__TESTMULTIPHASE_TRUE@_testsinglephase _testsinglephase.c
178179
@MODULE__CTYPES_TEST_TRUE@_ctypes_test _ctypes/_ctypes_test.c
179180

180181
# Limited API template modules; must be built as shared modules.

Modules/_testsinglephase.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
2+
/* Testing module for single-phase initialization of extension modules
3+
*/
4+
#ifndef Py_BUILD_CORE_BUILTIN
5+
# define Py_BUILD_CORE_MODULE 1
6+
#endif
7+
8+
#include "Python.h"
9+
#include "pycore_namespace.h" // _PyNamespace_New()
10+
11+
12+
/* Function of two integers returning integer */
13+
14+
PyDoc_STRVAR(testexport_foo_doc,
15+
"foo(i,j)\n\
16+
\n\
17+
Return the sum of i and j.");
18+
19+
static PyObject *
20+
testexport_foo(PyObject *self, PyObject *args)
21+
{
22+
long i, j;
23+
long res;
24+
if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
25+
return NULL;
26+
res = i + j;
27+
return PyLong_FromLong(res);
28+
}
29+
30+
31+
static PyMethodDef TestMethods[] = {
32+
{"foo", testexport_foo, METH_VARARGS,
33+
testexport_foo_doc},
34+
{NULL, NULL} /* sentinel */
35+
};
36+
37+
38+
static struct PyModuleDef _testsinglephase = {
39+
PyModuleDef_HEAD_INIT,
40+
.m_name = "_testsinglephase",
41+
.m_doc = PyDoc_STR("Test module _testsinglephase (main)"),
42+
.m_size = -1, // no module state
43+
.m_methods = TestMethods,
44+
};
45+
46+
47+
PyMODINIT_FUNC
48+
PyInit__testsinglephase(void)
49+
{
50+
PyObject *module = PyModule_Create(&_testsinglephase);
51+
if (module == NULL) {
52+
return NULL;
53+
}
54+
55+
/* Add an exception type */
56+
PyObject *temp = PyErr_NewException("_testsinglephase.error", NULL, NULL);
57+
if (temp == NULL) {
58+
goto error;
59+
}
60+
if (PyModule_AddObject(module, "error", temp) != 0) {
61+
Py_DECREF(temp);
62+
goto error;
63+
}
64+
65+
if (PyModule_AddIntConstant(module, "int_const", 1969) != 0) {
66+
goto error;
67+
}
68+
69+
if (PyModule_AddStringConstant(module, "str_const", "something different") != 0) {
70+
goto error;
71+
}
72+
73+
return module;
74+
75+
error:
76+
Py_DECREF(module);
77+
return NULL;
78+
}

Tools/build/generate_stdlib_module_names.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
'_testimportmultiple',
3434
'_testinternalcapi',
3535
'_testmultiphase',
36+
'_testsinglephase',
3637
'_xxsubinterpreters',
3738
'_xxtestfuzz',
3839
'distutils.tests',

0 commit comments

Comments
 (0)