Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
Avoid the datetime module.
  • Loading branch information
vstinner committed Jan 17, 2025
1 parent fc2f8cd commit 33b8507
Show file tree
Hide file tree
Showing 2 changed files with 282 additions and 22 deletions.
157 changes: 136 additions & 21 deletions Lib/test/test_capi/test_import.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import importlib.util
import os.path
import sys
import types
import unittest
from test.support import import_helper
from test.support.warnings_helper import check_warnings

_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
NULL = None


class ImportTests(unittest.TestCase):
Expand Down Expand Up @@ -36,7 +38,7 @@ def check_import_loaded_module(self, import_module):
def check_import_fresh_module(self, import_module):
old_modules = dict(sys.modules)
try:
for name in ('colorsys', 'datetime', 'math'):
for name in ('colorsys', 'math'):
with self.subTest(name=name):
sys.modules.pop(name, None)
module = import_module(name)
Expand All @@ -57,32 +59,43 @@ def test_getmodule(self):
self.assertIsNone(_testlimitedcapi.PyImport_GetModule(''))
self.assertIsNone(_testlimitedcapi.PyImport_GetModule(object()))

def check_addmodule(self, add_module):
def check_addmodule(self, add_module, accept_nonstr=False):
# create a new module
name = 'nonexistent'
self.assertNotIn(name, sys.modules)
try:
module = add_module(name)
self.assertIsInstance(module, types.ModuleType)
self.assertIs(module, sys.modules[name])
finally:
sys.modules.pop(name, None)
names = ['nonexistent']
if accept_nonstr:
# PyImport_AddModuleObject() accepts non-string names
names.append(object())
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()
self.check_addmodule(_testlimitedcapi.PyImport_AddModuleObject)
self.check_addmodule(_testlimitedcapi.PyImport_AddModuleObject,
accept_nonstr=True)

def test_addmodule(self):
# Test PyImport_AddModule()
self.check_addmodule(_testlimitedcapi.PyImport_AddModule)

# CRASHES PyImport_AddModule(NULL)

def test_addmoduleref(self):
# Test PyImport_AddModuleRef()
self.check_addmodule(_testlimitedcapi.PyImport_AddModuleRef)

# CRASHES PyImport_AddModuleRef(NULL)

def check_import_func(self, import_module):
self.check_import_loaded_module(import_module)
self.check_import_fresh_module(import_module)
Expand All @@ -106,19 +119,121 @@ def test_importmodulenoblock(self):
with check_warnings(('', DeprecationWarning)):
self.check_import_func(_testlimitedcapi.PyImport_ImportModuleNoBlock)

# TODO: test PyImport_ExecCodeModule()
# TODO: test PyImport_ExecCodeModuleEx()
# TODO: test PyImport_ExecCodeModuleWithPathnames()
# TODO: test PyImport_ExecCodeModuleObject()
# TODO: test PyImport_ImportModuleLevel()
# TODO: test PyImport_ImportModuleLevelObject()
# TODO: test PyImport_ImportModuleEx()
def check_frozen_import(self, import_frozen_module):
# Importing a frozen module executes its code, so starts 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)
finally:
sys.modules['zipimport'] = old_zipimport

# not a frozen module
self.assertEqual(import_frozen_module('sys'), 0)

def test_importfrozenmodule(self):
# Test PyImport_ImportFrozenModule()
self.check_frozen_import(_testlimitedcapi.PyImport_ImportFrozenModule)

# CRASHES PyImport_ImportFrozenModule(NULL)

def test_importfrozenmoduleobject(self):
# Test PyImport_ImportFrozenModuleObject()
PyImport_ImportFrozenModuleObject = _testlimitedcapi.PyImport_ImportFrozenModuleObject
self.check_frozen_import(PyImport_ImportFrozenModuleObject)

# Bad name is treated as "not found"
self.assertEqual(PyImport_ImportFrozenModuleObject(None), 0)

def test_importmoduleex(self):
# Test PyImport_ImportModuleEx()
def import_module(name):
return _testlimitedcapi.PyImport_ImportModuleEx(
name, globals(), {}, [])

self.check_import_func(import_module)

def test_importmodulelevel(self):
# Test PyImport_ImportModuleLevel()
def import_module(name):
return _testlimitedcapi.PyImport_ImportModuleLevel(
name, globals(), {}, [], 0)

self.check_import_func(import_module)

def test_importmodulelevelobject(self):
# Test PyImport_ImportModuleLevelObject()
def import_module(name):
return _testlimitedcapi.PyImport_ImportModuleLevelObject(
name, globals(), {}, [], 0)

self.check_import_func(import_module)

def check_executecodemodule(self, execute_code, pathname=None):
name = 'test_import_executecode'
try:
# Create a temporary module where the code will be executed
self.assertNotIn(name, sys.modules)
module = _testlimitedcapi.PyImport_AddModuleRef(name)
self.assertNotHasAttr(module, 'attr')

# Execute the code
if pathname is not None:
code_filename = pathname
else:
code_filename = '<string>'
code = compile('attr = 1', code_filename, 'exec')
module2 = execute_code(name, code)
self.assertIs(module2, module)

# Check the function side effects
self.assertEqual(module.attr, 1)
if pathname is not None:
self.assertEqual(module.__spec__.origin, pathname)
finally:
sys.modules.pop(name, None)

def test_executecodemodule(self):
# Test PyImport_ExecCodeModule()
self.check_executecodemodule(_testlimitedcapi.PyImport_ExecCodeModule)

def test_executecodemoduleex(self):
# Test PyImport_ExecCodeModuleEx()
pathname = os.path.abspath('pathname')

def execute_code(name, code):
return _testlimitedcapi.PyImport_ExecCodeModuleEx(name, code,
pathname)
self.check_executecodemodule(execute_code, pathname)

def check_executecode_pathnames(self, execute_code_func):
# Test non-NULL pathname and NULL cpathname
pathname = os.path.abspath('pathname')

def execute_code1(name, code):
return execute_code_func(name, code, pathname, NULL)
self.check_executecodemodule(execute_code1, 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)

def execute_code2(name, code):
return execute_code_func(name, code, NULL, pyc_filename)
self.check_executecodemodule(execute_code2, py_filename)

def test_executecodemodulewithpathnames(self):
# Test PyImport_ExecCodeModuleWithPathnames()
self.check_executecode_pathnames(_testlimitedcapi.PyImport_ExecCodeModuleWithPathnames)

def test_executecodemoduleobject(self):
# Test PyImport_ExecCodeModuleObject()
self.check_executecode_pathnames(_testlimitedcapi.PyImport_ExecCodeModuleObject)

# TODO: test PyImport_GetImporter()
# TODO: test PyImport_ReloadModule()
# TODO: test PyImport_ImportFrozenModuleObject()
# TODO: test PyImport_ImportFrozenModule()
# TODO: test PyImport_AppendInittab()
# TODO: test PyImport_ExtendInittab()
# PyImport_AppendInittab() is tested by test_embed


if __name__ == "__main__":
Expand Down
147 changes: 146 additions & 1 deletion Modules/_testlimitedcapi/import.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Need limited C API version 3.7 for PyImport_GetModule()
// Need limited C API version 3.13 for PyImport_AddModuleRef()
#include "pyconfig.h" // Py_GIL_DISABLED
#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API)
# define Py_LIMITED_API 0x030d0000
Expand Down Expand Up @@ -118,6 +118,142 @@ pyimport_importmodulenoblock(PyObject *Py_UNUSED(module), PyObject *args)
}


/* Test PyImport_ImportModuleEx() */
static PyObject *
pyimport_importmoduleex(PyObject *Py_UNUSED(module), PyObject *args)
{
const char *name;
PyObject *globals, *locals, *fromlist;
if (!PyArg_ParseTuple(args, "sOOO",
&name, &globals, &locals, &fromlist)) {
return NULL;
}

return PyImport_ImportModuleEx(name, globals, locals, fromlist);
}


/* Test PyImport_ImportModuleLevel() */
static PyObject *
pyimport_importmodulelevel(PyObject *Py_UNUSED(module), PyObject *args)
{
const char *name;
PyObject *globals, *locals, *fromlist;
int level;
if (!PyArg_ParseTuple(args, "sOOOi",
&name, &globals, &locals, &fromlist, &level)) {
return NULL;
}

return PyImport_ImportModuleLevel(name, globals, locals, fromlist, level);
}


/* Test PyImport_ImportModuleLevelObject() */
static PyObject *
pyimport_importmodulelevelobject(PyObject *Py_UNUSED(module), PyObject *args)
{
PyObject *name, *globals, *locals, *fromlist;
int level;
if (!PyArg_ParseTuple(args, "OOOOi",
&name, &globals, &locals, &fromlist, &level)) {
return NULL;
}

return PyImport_ImportModuleLevelObject(name, globals, locals, fromlist, level);
}


/* Test PyImport_ImportFrozenModule() */
static PyObject *
pyimport_importfrozenmodule(PyObject *Py_UNUSED(module), PyObject *args)
{
const char *name;
if (!PyArg_ParseTuple(args, "s", &name)) {
return NULL;
}

int res = PyImport_ImportFrozenModule(name);
if (res < 0) {
return NULL;
}
return PyLong_FromLong(res);
}


/* Test PyImport_ImportFrozenModuleObject() */
static PyObject *
pyimport_importfrozenmoduleobject(PyObject *Py_UNUSED(module), PyObject *name)
{
int res = PyImport_ImportFrozenModuleObject(name);
if (res < 0) {
return NULL;
}
return PyLong_FromLong(res);
}


/* Test PyImport_ExecCodeModule() */
static PyObject *
pyimport_executecodemodule(PyObject *Py_UNUSED(module), PyObject *args)
{
const char *name;
PyObject *code;
if (!PyArg_ParseTuple(args, "sO", &name, &code)) {
return NULL;
}

return PyImport_ExecCodeModule(name, code);
}


/* Test PyImport_ExecCodeModuleEx() */
static PyObject *
pyimport_executecodemoduleex(PyObject *Py_UNUSED(module), PyObject *args)
{
const char *name;
PyObject *code;
const char *pathname;
if (!PyArg_ParseTuple(args, "sOs", &name, &code, &pathname)) {
return NULL;
}

return PyImport_ExecCodeModuleEx(name, code, pathname);
}


/* Test PyImport_ExecCodeModuleWithPathnames() */
static PyObject *
pyimport_executecodemodulewithpathnames(PyObject *Py_UNUSED(module), PyObject *args)
{
const char *name;
PyObject *code;
const char *pathname;
const char *cpathname;
if (!PyArg_ParseTuple(args, "sOzz", &name, &code, &pathname, &cpathname)) {
return NULL;
}

return PyImport_ExecCodeModuleWithPathnames(name, code,
pathname, cpathname);
}


/* Test PyImport_ExecCodeModuleObject() */
static PyObject *
pyimport_executecodemoduleobject(PyObject *Py_UNUSED(module), PyObject *args)
{
PyObject *name, *code, *pathname, *cpathname;
if (!PyArg_ParseTuple(args, "OOOO", &name, &code, &pathname, &cpathname)) {
return NULL;
}
NULLABLE(pathname);
NULLABLE(cpathname);

return PyImport_ExecCodeModuleObject(name, code, pathname, cpathname);
}


static PyMethodDef test_methods[] = {
{"PyImport_GetMagicNumber", pyimport_getmagicnumber, METH_NOARGS},
{"PyImport_GetMagicTag", pyimport_getmagictag, METH_NOARGS},
Expand All @@ -129,6 +265,15 @@ static PyMethodDef test_methods[] = {
{"PyImport_Import", pyimport_import, METH_O},
{"PyImport_ImportModule", pyimport_importmodule, METH_VARARGS},
{"PyImport_ImportModuleNoBlock", pyimport_importmodulenoblock, METH_VARARGS},
{"PyImport_ImportModuleEx", pyimport_importmoduleex, METH_VARARGS},
{"PyImport_ImportModuleLevel", pyimport_importmodulelevel, METH_VARARGS},
{"PyImport_ImportModuleLevelObject", pyimport_importmodulelevelobject, METH_VARARGS},
{"PyImport_ImportFrozenModule", pyimport_importfrozenmodule, METH_VARARGS},
{"PyImport_ImportFrozenModuleObject", pyimport_importfrozenmoduleobject, METH_O},
{"PyImport_ExecCodeModule", pyimport_executecodemodule, METH_VARARGS},
{"PyImport_ExecCodeModuleEx", pyimport_executecodemoduleex, METH_VARARGS},
{"PyImport_ExecCodeModuleWithPathnames", pyimport_executecodemodulewithpathnames, METH_VARARGS},
{"PyImport_ExecCodeModuleObject", pyimport_executecodemoduleobject, METH_VARARGS},
{NULL},
};

Expand Down

0 comments on commit 33b8507

Please sign in to comment.