forked from python/cpython
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[3.12] [3.13] pythongh-128911: Add tests on the PyImport C API (pytho…
…nGH-128915) (pythonGH-128960) pythongh-128911: Add tests on the PyImport C API (pythonGH-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. (cherry picked from commit 34ded1a) Co-authored-by: Victor Stinner <[email protected]> Co-authored-by: Serhiy Storchaka <[email protected]> (cherry picked from commit d95ba9f)
- Loading branch information
1 parent
3d9b14c
commit 96e0bc4
Showing
8 changed files
with
610 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,313 @@ | ||
import importlib.util | ||
import os.path | ||
import sys | ||
import types | ||
import unittest | ||
from test.support import os_helper | ||
from test.support import import_helper | ||
from test.support.warnings_helper import check_warnings | ||
|
||
_testcapi = import_helper.import_module('_testcapi') | ||
NULL = None | ||
|
||
|
||
class ImportTests(unittest.TestCase): | ||
def test_getmagicnumber(self): | ||
# Test PyImport_GetMagicNumber() | ||
magic = _testcapi.PyImport_GetMagicNumber() | ||
self.assertEqual(magic, | ||
int.from_bytes(importlib.util.MAGIC_NUMBER, 'little')) | ||
|
||
def test_getmagictag(self): | ||
# Test PyImport_GetMagicTag() | ||
tag = _testcapi.PyImport_GetMagicTag() | ||
self.assertEqual(tag, sys.implementation.cache_tag) | ||
|
||
def test_getmoduledict(self): | ||
# Test PyImport_GetModuleDict() | ||
modules = _testcapi.PyImport_GetModuleDict() | ||
self.assertIs(modules, sys.modules) | ||
|
||
def check_import_loaded_module(self, import_module): | ||
for name in ('os', 'sys', 'test', 'unittest'): | ||
with self.subTest(name=name): | ||
self.assertIn(name, sys.modules) | ||
old_module = sys.modules[name] | ||
module = import_module(name) | ||
self.assertIsInstance(module, types.ModuleType) | ||
self.assertIs(module, old_module) | ||
|
||
def check_import_fresh_module(self, import_module): | ||
old_modules = dict(sys.modules) | ||
try: | ||
for name in ('colorsys', 'math'): | ||
with self.subTest(name=name): | ||
sys.modules.pop(name, None) | ||
module = import_module(name) | ||
self.assertIsInstance(module, types.ModuleType) | ||
self.assertIs(module, sys.modules[name]) | ||
self.assertEqual(module.__name__, name) | ||
finally: | ||
sys.modules.clear() | ||
sys.modules.update(old_modules) | ||
|
||
def test_getmodule(self): | ||
# Test PyImport_GetModule() | ||
getmodule = _testcapi.PyImport_GetModule | ||
self.check_import_loaded_module(getmodule) | ||
|
||
nonexistent = 'nonexistent' | ||
self.assertNotIn(nonexistent, sys.modules) | ||
self.assertIs(getmodule(nonexistent), KeyError) | ||
self.assertIs(getmodule(''), KeyError) | ||
self.assertIs(getmodule(object()), KeyError) | ||
|
||
self.assertRaises(TypeError, getmodule, []) # unhashable | ||
# CRASHES getmodule(NULL) | ||
|
||
def check_addmodule(self, add_module, accept_nonstr=False): | ||
# create a new module | ||
names = ['nonexistent'] | ||
if accept_nonstr: | ||
names.append(b'\xff') # non-UTF-8 | ||
for name in names: | ||
with self.subTest(name=name): | ||
self.assertNotIn(name, sys.modules) | ||
try: | ||
module = add_module(name) | ||
self.assertIsInstance(module, types.ModuleType) | ||
self.assertEqual(module.__name__, name) | ||
self.assertIs(module, sys.modules[name]) | ||
finally: | ||
sys.modules.pop(name, None) | ||
|
||
# get an existing module | ||
self.check_import_loaded_module(add_module) | ||
|
||
def test_addmoduleobject(self): | ||
# Test PyImport_AddModuleObject() | ||
addmoduleobject = _testcapi.PyImport_AddModuleObject | ||
self.check_addmodule(addmoduleobject, accept_nonstr=True) | ||
|
||
self.assertRaises(TypeError, addmoduleobject, []) # unhashable | ||
# CRASHES addmoduleobject(NULL) | ||
|
||
def test_addmodule(self): | ||
# Test PyImport_AddModule() | ||
addmodule = _testcapi.PyImport_AddModule | ||
self.check_addmodule(addmodule) | ||
|
||
self.assertRaises(UnicodeDecodeError, addmodule, b'\xff') | ||
# CRASHES addmodule(NULL) | ||
|
||
def check_import_func(self, import_module): | ||
self.check_import_loaded_module(import_module) | ||
self.check_import_fresh_module(import_module) | ||
self.assertRaises(ModuleNotFoundError, import_module, 'nonexistent') | ||
self.assertRaises(ValueError, import_module, '') | ||
|
||
def test_import(self): | ||
# Test PyImport_Import() | ||
import_ = _testcapi.PyImport_Import | ||
self.check_import_func(import_) | ||
|
||
self.assertRaises(TypeError, import_, b'os') | ||
self.assertRaises(SystemError, import_, NULL) | ||
|
||
def test_importmodule(self): | ||
# Test PyImport_ImportModule() | ||
importmodule = _testcapi.PyImport_ImportModule | ||
self.check_import_func(importmodule) | ||
|
||
self.assertRaises(UnicodeDecodeError, importmodule, b'\xff') | ||
# CRASHES importmodule(NULL) | ||
|
||
def test_importmodulenoblock(self): | ||
# Test deprecated PyImport_ImportModuleNoBlock() | ||
importmodulenoblock = _testcapi.PyImport_ImportModuleNoBlock | ||
self.check_import_func(importmodulenoblock) | ||
|
||
self.assertRaises(UnicodeDecodeError, importmodulenoblock, b'\xff') | ||
# CRASHES importmodulenoblock(NULL) | ||
|
||
def check_frozen_import(self, import_frozen_module): | ||
# Importing a frozen module executes its code, so start by unloading | ||
# the module to execute the code in a new (temporary) module. | ||
old_zipimport = sys.modules.pop('zipimport') | ||
try: | ||
self.assertEqual(import_frozen_module('zipimport'), 1) | ||
|
||
# import zipimport again | ||
self.assertEqual(import_frozen_module('zipimport'), 1) | ||
finally: | ||
sys.modules['zipimport'] = old_zipimport | ||
|
||
# not a frozen module | ||
self.assertEqual(import_frozen_module('sys'), 0) | ||
self.assertEqual(import_frozen_module('nonexistent'), 0) | ||
self.assertEqual(import_frozen_module(''), 0) | ||
|
||
def test_importfrozenmodule(self): | ||
# Test PyImport_ImportFrozenModule() | ||
importfrozenmodule = _testcapi.PyImport_ImportFrozenModule | ||
self.check_frozen_import(importfrozenmodule) | ||
|
||
self.assertRaises(UnicodeDecodeError, importfrozenmodule, b'\xff') | ||
# CRASHES importfrozenmodule(NULL) | ||
|
||
def test_importfrozenmoduleobject(self): | ||
# Test PyImport_ImportFrozenModuleObject() | ||
importfrozenmoduleobject = _testcapi.PyImport_ImportFrozenModuleObject | ||
self.check_frozen_import(importfrozenmoduleobject) | ||
self.assertEqual(importfrozenmoduleobject(b'zipimport'), 0) | ||
self.assertEqual(importfrozenmoduleobject(NULL), 0) | ||
|
||
def test_importmoduleex(self): | ||
# Test PyImport_ImportModuleEx() | ||
importmoduleex = _testcapi.PyImport_ImportModuleEx | ||
self.check_import_func(lambda name: importmoduleex(name, NULL, NULL, NULL)) | ||
|
||
self.assertRaises(ModuleNotFoundError, importmoduleex, 'nonexistent', NULL, NULL, NULL) | ||
self.assertRaises(ValueError, importmoduleex, '', NULL, NULL, NULL) | ||
self.assertRaises(UnicodeDecodeError, importmoduleex, b'\xff', NULL, NULL, NULL) | ||
# CRASHES importmoduleex(NULL, NULL, NULL, NULL) | ||
|
||
def check_importmodulelevel(self, importmodulelevel): | ||
self.check_import_func(lambda name: importmodulelevel(name, NULL, NULL, NULL, 0)) | ||
|
||
self.assertRaises(ModuleNotFoundError, importmodulelevel, 'nonexistent', NULL, NULL, NULL, 0) | ||
self.assertRaises(ValueError, importmodulelevel, '', NULL, NULL, NULL, 0) | ||
|
||
if __package__: | ||
self.assertIs(importmodulelevel('test_import', globals(), NULL, NULL, 1), | ||
sys.modules['test.test_capi.test_import']) | ||
self.assertIs(importmodulelevel('test_capi', globals(), NULL, NULL, 2), | ||
sys.modules['test.test_capi']) | ||
self.assertRaises(ValueError, importmodulelevel, 'os', NULL, NULL, NULL, -1) | ||
with self.assertWarns(ImportWarning): | ||
self.assertRaises(KeyError, importmodulelevel, 'test_import', {}, NULL, NULL, 1) | ||
self.assertRaises(TypeError, importmodulelevel, 'test_import', [], NULL, NULL, 1) | ||
|
||
def test_importmodulelevel(self): | ||
# Test PyImport_ImportModuleLevel() | ||
importmodulelevel = _testcapi.PyImport_ImportModuleLevel | ||
self.check_importmodulelevel(importmodulelevel) | ||
|
||
self.assertRaises(UnicodeDecodeError, importmodulelevel, b'\xff', NULL, NULL, NULL, 0) | ||
# CRASHES importmodulelevel(NULL, NULL, NULL, NULL, 0) | ||
|
||
def test_importmodulelevelobject(self): | ||
# Test PyImport_ImportModuleLevelObject() | ||
importmodulelevel = _testcapi.PyImport_ImportModuleLevelObject | ||
self.check_importmodulelevel(importmodulelevel) | ||
|
||
self.assertRaises(TypeError, importmodulelevel, b'os', NULL, NULL, NULL, 0) | ||
self.assertRaises(ValueError, importmodulelevel, NULL, NULL, NULL, NULL, 0) | ||
|
||
def check_executecodemodule(self, execute_code, *args): | ||
name = 'test_import_executecode' | ||
try: | ||
# Create a temporary module where the code will be executed | ||
self.assertNotIn(name, sys.modules) | ||
module = _testcapi.PyImport_AddModule(name) | ||
self.assertFalse(hasattr(module, 'attr')) | ||
|
||
# Execute the code | ||
code = compile('attr = 1', '<test>', 'exec') | ||
module2 = execute_code(name, code, *args) | ||
self.assertIs(module2, module) | ||
|
||
# Check the function side effects | ||
self.assertEqual(module.attr, 1) | ||
finally: | ||
sys.modules.pop(name, None) | ||
return module.__spec__.origin | ||
|
||
def test_executecodemodule(self): | ||
# Test PyImport_ExecCodeModule() | ||
execcodemodule = _testcapi.PyImport_ExecCodeModule | ||
self.check_executecodemodule(execcodemodule) | ||
|
||
code = compile('attr = 1', '<test>', 'exec') | ||
self.assertRaises(UnicodeDecodeError, execcodemodule, b'\xff', code) | ||
# CRASHES execcodemodule(NULL, code) | ||
# CRASHES execcodemodule(name, NULL) | ||
|
||
def test_executecodemoduleex(self): | ||
# Test PyImport_ExecCodeModuleEx() | ||
execcodemoduleex = _testcapi.PyImport_ExecCodeModuleEx | ||
|
||
# Test NULL path (it should not crash) | ||
self.check_executecodemodule(execcodemoduleex, NULL) | ||
|
||
# Test non-NULL path | ||
pathname = b'pathname' | ||
origin = self.check_executecodemodule(execcodemoduleex, pathname) | ||
self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname))) | ||
|
||
pathname = os_helper.TESTFN_UNDECODABLE | ||
if pathname: | ||
origin = self.check_executecodemodule(execcodemoduleex, pathname) | ||
self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname))) | ||
|
||
code = compile('attr = 1', '<test>', 'exec') | ||
self.assertRaises(UnicodeDecodeError, execcodemoduleex, b'\xff', code, NULL) | ||
# CRASHES execcodemoduleex(NULL, code, NULL) | ||
# CRASHES execcodemoduleex(name, NULL, NULL) | ||
|
||
def check_executecode_pathnames(self, execute_code_func, object=False): | ||
# Test non-NULL pathname and NULL cpathname | ||
|
||
# Test NULL paths (it should not crash) | ||
self.check_executecodemodule(execute_code_func, NULL, NULL) | ||
|
||
pathname = 'pathname' | ||
origin = self.check_executecodemodule(execute_code_func, pathname, NULL) | ||
self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname))) | ||
origin = self.check_executecodemodule(execute_code_func, NULL, pathname) | ||
if not object: | ||
self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname))) | ||
|
||
pathname = os_helper.TESTFN_UNDECODABLE | ||
if pathname: | ||
if object: | ||
pathname = os.fsdecode(pathname) | ||
origin = self.check_executecodemodule(execute_code_func, pathname, NULL) | ||
self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname))) | ||
self.check_executecodemodule(execute_code_func, NULL, pathname) | ||
|
||
# Test NULL pathname and non-NULL cpathname | ||
pyc_filename = importlib.util.cache_from_source(__file__) | ||
py_filename = importlib.util.source_from_cache(pyc_filename) | ||
origin = self.check_executecodemodule(execute_code_func, NULL, pyc_filename) | ||
if not object: | ||
self.assertEqual(origin, py_filename) | ||
|
||
def test_executecodemodulewithpathnames(self): | ||
# Test PyImport_ExecCodeModuleWithPathnames() | ||
execute_code_func = _testcapi.PyImport_ExecCodeModuleWithPathnames | ||
self.check_executecode_pathnames(execute_code_func) | ||
|
||
code = compile('attr = 1', '<test>', 'exec') | ||
self.assertRaises(UnicodeDecodeError, execute_code_func, b'\xff', code, NULL, NULL) | ||
# CRASHES execute_code_func(NULL, code, NULL, NULL) | ||
# CRASHES execute_code_func(name, NULL, NULL, NULL) | ||
|
||
def test_executecodemoduleobject(self): | ||
# Test PyImport_ExecCodeModuleObject() | ||
execute_code_func = _testcapi.PyImport_ExecCodeModuleObject | ||
self.check_executecode_pathnames(execute_code_func, object=True) | ||
|
||
code = compile('attr = 1', '<test>', 'exec') | ||
self.assertRaises(TypeError, execute_code_func, [], code, NULL, NULL) | ||
# CRASHES execute_code_func(NULL, code, NULL, NULL) | ||
# CRASHES execute_code_func(name, NULL, NULL, NULL) | ||
|
||
# TODO: test PyImport_GetImporter() | ||
# TODO: test PyImport_ReloadModule() | ||
# TODO: test PyImport_ExtendInittab() | ||
# PyImport_AppendInittab() is tested by test_embed | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.