Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 34ded1a

Browse filesBrowse files
[3.13] gh-128911: Add tests on the PyImport C API (#128915) (#128960)
gh-128911: Add tests on the PyImport C API (#128915) * 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. Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> (cherry picked from commit d95ba9f)
1 parent dc77f19 commit 34ded1a
Copy full SHA for 34ded1a

File tree

Expand file treeCollapse file tree

9 files changed

+635
-72
lines changed
Filter options
Expand file treeCollapse file tree

9 files changed

+635
-72
lines changed

‎Lib/test/test_capi/test_import.py

Copy file name to clipboard
+322Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
import importlib.util
2+
import os.path
3+
import sys
4+
import types
5+
import unittest
6+
from test.support import os_helper
7+
from test.support import import_helper
8+
from test.support.warnings_helper import check_warnings
9+
10+
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
11+
NULL = None
12+
13+
14+
class ImportTests(unittest.TestCase):
15+
def test_getmagicnumber(self):
16+
# Test PyImport_GetMagicNumber()
17+
magic = _testlimitedcapi.PyImport_GetMagicNumber()
18+
self.assertEqual(magic,
19+
int.from_bytes(importlib.util.MAGIC_NUMBER, 'little'))
20+
21+
def test_getmagictag(self):
22+
# Test PyImport_GetMagicTag()
23+
tag = _testlimitedcapi.PyImport_GetMagicTag()
24+
self.assertEqual(tag, sys.implementation.cache_tag)
25+
26+
def test_getmoduledict(self):
27+
# Test PyImport_GetModuleDict()
28+
modules = _testlimitedcapi.PyImport_GetModuleDict()
29+
self.assertIs(modules, sys.modules)
30+
31+
def check_import_loaded_module(self, import_module):
32+
for name in ('os', 'sys', 'test', 'unittest'):
33+
with self.subTest(name=name):
34+
self.assertIn(name, sys.modules)
35+
old_module = sys.modules[name]
36+
module = import_module(name)
37+
self.assertIsInstance(module, types.ModuleType)
38+
self.assertIs(module, old_module)
39+
40+
def check_import_fresh_module(self, import_module):
41+
old_modules = dict(sys.modules)
42+
try:
43+
for name in ('colorsys', 'math'):
44+
with self.subTest(name=name):
45+
sys.modules.pop(name, None)
46+
module = import_module(name)
47+
self.assertIsInstance(module, types.ModuleType)
48+
self.assertIs(module, sys.modules[name])
49+
self.assertEqual(module.__name__, name)
50+
finally:
51+
sys.modules.clear()
52+
sys.modules.update(old_modules)
53+
54+
def test_getmodule(self):
55+
# Test PyImport_GetModule()
56+
getmodule = _testlimitedcapi.PyImport_GetModule
57+
self.check_import_loaded_module(getmodule)
58+
59+
nonexistent = 'nonexistent'
60+
self.assertNotIn(nonexistent, sys.modules)
61+
self.assertIs(getmodule(nonexistent), KeyError)
62+
self.assertIs(getmodule(''), KeyError)
63+
self.assertIs(getmodule(object()), KeyError)
64+
65+
self.assertRaises(TypeError, getmodule, []) # unhashable
66+
# CRASHES getmodule(NULL)
67+
68+
def check_addmodule(self, add_module, accept_nonstr=False):
69+
# create a new module
70+
names = ['nonexistent']
71+
if accept_nonstr:
72+
names.append(b'\xff') # non-UTF-8
73+
for name in names:
74+
with self.subTest(name=name):
75+
self.assertNotIn(name, sys.modules)
76+
try:
77+
module = add_module(name)
78+
self.assertIsInstance(module, types.ModuleType)
79+
self.assertEqual(module.__name__, name)
80+
self.assertIs(module, sys.modules[name])
81+
finally:
82+
sys.modules.pop(name, None)
83+
84+
# get an existing module
85+
self.check_import_loaded_module(add_module)
86+
87+
def test_addmoduleobject(self):
88+
# Test PyImport_AddModuleObject()
89+
addmoduleobject = _testlimitedcapi.PyImport_AddModuleObject
90+
self.check_addmodule(addmoduleobject, accept_nonstr=True)
91+
92+
self.assertRaises(TypeError, addmoduleobject, []) # unhashable
93+
# CRASHES addmoduleobject(NULL)
94+
95+
def test_addmodule(self):
96+
# Test PyImport_AddModule()
97+
addmodule = _testlimitedcapi.PyImport_AddModule
98+
self.check_addmodule(addmodule)
99+
100+
self.assertRaises(UnicodeDecodeError, addmodule, b'\xff')
101+
# CRASHES addmodule(NULL)
102+
103+
def test_addmoduleref(self):
104+
# Test PyImport_AddModuleRef()
105+
addmoduleref = _testlimitedcapi.PyImport_AddModuleRef
106+
self.check_addmodule(addmoduleref)
107+
108+
self.assertRaises(UnicodeDecodeError, addmoduleref, b'\xff')
109+
# CRASHES addmoduleref(NULL)
110+
111+
def check_import_func(self, import_module):
112+
self.check_import_loaded_module(import_module)
113+
self.check_import_fresh_module(import_module)
114+
self.assertRaises(ModuleNotFoundError, import_module, 'nonexistent')
115+
self.assertRaises(ValueError, import_module, '')
116+
117+
def test_import(self):
118+
# Test PyImport_Import()
119+
import_ = _testlimitedcapi.PyImport_Import
120+
self.check_import_func(import_)
121+
122+
self.assertRaises(TypeError, import_, b'os')
123+
self.assertRaises(SystemError, import_, NULL)
124+
125+
def test_importmodule(self):
126+
# Test PyImport_ImportModule()
127+
importmodule = _testlimitedcapi.PyImport_ImportModule
128+
self.check_import_func(importmodule)
129+
130+
self.assertRaises(UnicodeDecodeError, importmodule, b'\xff')
131+
# CRASHES importmodule(NULL)
132+
133+
def test_importmodulenoblock(self):
134+
# Test deprecated PyImport_ImportModuleNoBlock()
135+
importmodulenoblock = _testlimitedcapi.PyImport_ImportModuleNoBlock
136+
with check_warnings(('', DeprecationWarning)):
137+
self.check_import_func(importmodulenoblock)
138+
self.assertRaises(UnicodeDecodeError, importmodulenoblock, b'\xff')
139+
140+
# CRASHES importmodulenoblock(NULL)
141+
142+
def check_frozen_import(self, import_frozen_module):
143+
# Importing a frozen module executes its code, so start by unloading
144+
# the module to execute the code in a new (temporary) module.
145+
old_zipimport = sys.modules.pop('zipimport')
146+
try:
147+
self.assertEqual(import_frozen_module('zipimport'), 1)
148+
149+
# import zipimport again
150+
self.assertEqual(import_frozen_module('zipimport'), 1)
151+
finally:
152+
sys.modules['zipimport'] = old_zipimport
153+
154+
# not a frozen module
155+
self.assertEqual(import_frozen_module('sys'), 0)
156+
self.assertEqual(import_frozen_module('nonexistent'), 0)
157+
self.assertEqual(import_frozen_module(''), 0)
158+
159+
def test_importfrozenmodule(self):
160+
# Test PyImport_ImportFrozenModule()
161+
importfrozenmodule = _testlimitedcapi.PyImport_ImportFrozenModule
162+
self.check_frozen_import(importfrozenmodule)
163+
164+
self.assertRaises(UnicodeDecodeError, importfrozenmodule, b'\xff')
165+
# CRASHES importfrozenmodule(NULL)
166+
167+
def test_importfrozenmoduleobject(self):
168+
# Test PyImport_ImportFrozenModuleObject()
169+
importfrozenmoduleobject = _testlimitedcapi.PyImport_ImportFrozenModuleObject
170+
self.check_frozen_import(importfrozenmoduleobject)
171+
self.assertEqual(importfrozenmoduleobject(b'zipimport'), 0)
172+
self.assertEqual(importfrozenmoduleobject(NULL), 0)
173+
174+
def test_importmoduleex(self):
175+
# Test PyImport_ImportModuleEx()
176+
importmoduleex = _testlimitedcapi.PyImport_ImportModuleEx
177+
self.check_import_func(lambda name: importmoduleex(name, NULL, NULL, NULL))
178+
179+
self.assertRaises(ModuleNotFoundError, importmoduleex, 'nonexistent', NULL, NULL, NULL)
180+
self.assertRaises(ValueError, importmoduleex, '', NULL, NULL, NULL)
181+
self.assertRaises(UnicodeDecodeError, importmoduleex, b'\xff', NULL, NULL, NULL)
182+
# CRASHES importmoduleex(NULL, NULL, NULL, NULL)
183+
184+
def check_importmodulelevel(self, importmodulelevel):
185+
self.check_import_func(lambda name: importmodulelevel(name, NULL, NULL, NULL, 0))
186+
187+
self.assertRaises(ModuleNotFoundError, importmodulelevel, 'nonexistent', NULL, NULL, NULL, 0)
188+
self.assertRaises(ValueError, importmodulelevel, '', NULL, NULL, NULL, 0)
189+
190+
if __package__:
191+
self.assertIs(importmodulelevel('test_import', globals(), NULL, NULL, 1),
192+
sys.modules['test.test_capi.test_import'])
193+
self.assertIs(importmodulelevel('test_capi', globals(), NULL, NULL, 2),
194+
sys.modules['test.test_capi'])
195+
self.assertRaises(ValueError, importmodulelevel, 'os', NULL, NULL, NULL, -1)
196+
with self.assertWarns(ImportWarning):
197+
self.assertRaises(KeyError, importmodulelevel, 'test_import', {}, NULL, NULL, 1)
198+
self.assertRaises(TypeError, importmodulelevel, 'test_import', [], NULL, NULL, 1)
199+
200+
def test_importmodulelevel(self):
201+
# Test PyImport_ImportModuleLevel()
202+
importmodulelevel = _testlimitedcapi.PyImport_ImportModuleLevel
203+
self.check_importmodulelevel(importmodulelevel)
204+
205+
self.assertRaises(UnicodeDecodeError, importmodulelevel, b'\xff', NULL, NULL, NULL, 0)
206+
# CRASHES importmodulelevel(NULL, NULL, NULL, NULL, 0)
207+
208+
def test_importmodulelevelobject(self):
209+
# Test PyImport_ImportModuleLevelObject()
210+
importmodulelevel = _testlimitedcapi.PyImport_ImportModuleLevelObject
211+
self.check_importmodulelevel(importmodulelevel)
212+
213+
self.assertRaises(TypeError, importmodulelevel, b'os', NULL, NULL, NULL, 0)
214+
self.assertRaises(ValueError, importmodulelevel, NULL, NULL, NULL, NULL, 0)
215+
216+
def check_executecodemodule(self, execute_code, *args):
217+
name = 'test_import_executecode'
218+
try:
219+
# Create a temporary module where the code will be executed
220+
self.assertNotIn(name, sys.modules)
221+
module = _testlimitedcapi.PyImport_AddModuleRef(name)
222+
self.assertFalse(hasattr(module, 'attr'))
223+
224+
# Execute the code
225+
code = compile('attr = 1', '<test>', 'exec')
226+
module2 = execute_code(name, code, *args)
227+
self.assertIs(module2, module)
228+
229+
# Check the function side effects
230+
self.assertEqual(module.attr, 1)
231+
finally:
232+
sys.modules.pop(name, None)
233+
return module.__spec__.origin
234+
235+
def test_executecodemodule(self):
236+
# Test PyImport_ExecCodeModule()
237+
execcodemodule = _testlimitedcapi.PyImport_ExecCodeModule
238+
self.check_executecodemodule(execcodemodule)
239+
240+
code = compile('attr = 1', '<test>', 'exec')
241+
self.assertRaises(UnicodeDecodeError, execcodemodule, b'\xff', code)
242+
# CRASHES execcodemodule(NULL, code)
243+
# CRASHES execcodemodule(name, NULL)
244+
245+
def test_executecodemoduleex(self):
246+
# Test PyImport_ExecCodeModuleEx()
247+
execcodemoduleex = _testlimitedcapi.PyImport_ExecCodeModuleEx
248+
249+
# Test NULL path (it should not crash)
250+
self.check_executecodemodule(execcodemoduleex, NULL)
251+
252+
# Test non-NULL path
253+
pathname = b'pathname'
254+
origin = self.check_executecodemodule(execcodemoduleex, pathname)
255+
self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
256+
257+
pathname = os_helper.TESTFN_UNDECODABLE
258+
if pathname:
259+
origin = self.check_executecodemodule(execcodemoduleex, pathname)
260+
self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
261+
262+
code = compile('attr = 1', '<test>', 'exec')
263+
self.assertRaises(UnicodeDecodeError, execcodemoduleex, b'\xff', code, NULL)
264+
# CRASHES execcodemoduleex(NULL, code, NULL)
265+
# CRASHES execcodemoduleex(name, NULL, NULL)
266+
267+
def check_executecode_pathnames(self, execute_code_func, object=False):
268+
# Test non-NULL pathname and NULL cpathname
269+
270+
# Test NULL paths (it should not crash)
271+
self.check_executecodemodule(execute_code_func, NULL, NULL)
272+
273+
pathname = 'pathname'
274+
origin = self.check_executecodemodule(execute_code_func, pathname, NULL)
275+
self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
276+
origin = self.check_executecodemodule(execute_code_func, NULL, pathname)
277+
if not object:
278+
self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
279+
280+
pathname = os_helper.TESTFN_UNDECODABLE
281+
if pathname:
282+
if object:
283+
pathname = os.fsdecode(pathname)
284+
origin = self.check_executecodemodule(execute_code_func, pathname, NULL)
285+
self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname)))
286+
self.check_executecodemodule(execute_code_func, NULL, pathname)
287+
288+
# Test NULL pathname and non-NULL cpathname
289+
pyc_filename = importlib.util.cache_from_source(__file__)
290+
py_filename = importlib.util.source_from_cache(pyc_filename)
291+
origin = self.check_executecodemodule(execute_code_func, NULL, pyc_filename)
292+
if not object:
293+
self.assertEqual(origin, py_filename)
294+
295+
def test_executecodemodulewithpathnames(self):
296+
# Test PyImport_ExecCodeModuleWithPathnames()
297+
execute_code_func = _testlimitedcapi.PyImport_ExecCodeModuleWithPathnames
298+
self.check_executecode_pathnames(execute_code_func)
299+
300+
code = compile('attr = 1', '<test>', 'exec')
301+
self.assertRaises(UnicodeDecodeError, execute_code_func, b'\xff', code, NULL, NULL)
302+
# CRASHES execute_code_func(NULL, code, NULL, NULL)
303+
# CRASHES execute_code_func(name, NULL, NULL, NULL)
304+
305+
def test_executecodemoduleobject(self):
306+
# Test PyImport_ExecCodeModuleObject()
307+
execute_code_func = _testlimitedcapi.PyImport_ExecCodeModuleObject
308+
self.check_executecode_pathnames(execute_code_func, object=True)
309+
310+
code = compile('attr = 1', '<test>', 'exec')
311+
self.assertRaises(TypeError, execute_code_func, [], code, NULL, NULL)
312+
# CRASHES execute_code_func(NULL, code, NULL, NULL)
313+
# CRASHES execute_code_func(name, NULL, NULL, NULL)
314+
315+
# TODO: test PyImport_GetImporter()
316+
# TODO: test PyImport_ReloadModule()
317+
# TODO: test PyImport_ExtendInittab()
318+
# PyImport_AppendInittab() is tested by test_embed
319+
320+
321+
if __name__ == "__main__":
322+
unittest.main()

‎Lib/test/test_import/__init__.py

Copy file name to clipboardExpand all lines: Lib/test/test_import/__init__.py
-24Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3330,30 +3330,6 @@ def test_basic_multiple_interpreters_reset_each(self):
33303330
# * module's global state was initialized, not reset
33313331

33323332

3333-
@cpython_only
3334-
class CAPITests(unittest.TestCase):
3335-
def test_pyimport_addmodule(self):
3336-
# gh-105922: Test PyImport_AddModuleRef(), PyImport_AddModule()
3337-
# and PyImport_AddModuleObject()
3338-
_testcapi = import_module("_testcapi")
3339-
for name in (
3340-
'sys', # frozen module
3341-
'test', # package
3342-
__name__, # package.module
3343-
):
3344-
_testcapi.check_pyimport_addmodule(name)
3345-
3346-
def test_pyimport_addmodule_create(self):
3347-
# gh-105922: Test PyImport_AddModuleRef(), create a new module
3348-
_testcapi = import_module("_testcapi")
3349-
name = 'dontexist'
3350-
self.assertNotIn(name, sys.modules)
3351-
self.addCleanup(unload, name)
3352-
3353-
mod = _testcapi.check_pyimport_addmodule(name)
3354-
self.assertIs(mod, sys.modules[name])
3355-
3356-
33573333
if __name__ == '__main__':
33583334
# Test needs to be a package, so we can do relative imports.
33593335
unittest.main()

‎Modules/Setup.stdlib.in

Copy file name to clipboardExpand all lines: Modules/Setup.stdlib.in
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@
164164
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
165165
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
166166
@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
167-
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.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
167+
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.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
168168
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
169169
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
170170

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.