Skip to content

gh-98627: Add the _testsinglephase Module #99039

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
machinery = util.import_importlib('importlib.machinery')


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

Expand Down
117 changes: 100 additions & 17 deletions Lib/test/test_importlib/extension/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
from test.support.script_helper import assert_python_failure


class LoaderTests(abc.LoaderTests):
class LoaderTests:

"""Test load_module() for extension modules."""
"""Test ExtensionFileLoader."""

def setUp(self):
if not self.machinery.EXTENSION_SUFFIXES:
Expand All @@ -32,15 +32,6 @@ def load_module(self, fullname):
warnings.simplefilter("ignore", DeprecationWarning)
return self.loader.load_module(fullname)

def test_load_module_API(self):
# Test the default argument for load_module().
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
self.loader.load_module()
self.loader.load_module(None)
with self.assertRaises(ImportError):
self.load_module('XXX')

def test_equality(self):
other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
util.EXTENSIONS.file_path)
Expand All @@ -51,6 +42,15 @@ def test_inequality(self):
util.EXTENSIONS.file_path)
self.assertNotEqual(self.loader, other)

def test_load_module_API(self):
# Test the default argument for load_module().
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
self.loader.load_module()
self.loader.load_module(None)
with self.assertRaises(ImportError):
self.load_module('XXX')

def test_module(self):
with util.uncache(util.EXTENSIONS.name):
module = self.load_module(util.EXTENSIONS.name)
Expand All @@ -68,12 +68,6 @@ def test_module(self):
# No extension module in a package available for testing.
test_lacking_parent = None

def test_module_reuse(self):
with util.uncache(util.EXTENSIONS.name):
module1 = self.load_module(util.EXTENSIONS.name)
module2 = self.load_module(util.EXTENSIONS.name)
self.assertIs(module1, module2)

# No easy way to trigger a failure after a successful import.
test_state_after_failure = None

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

def test_module_reuse(self):
with util.uncache(util.EXTENSIONS.name):
module1 = self.load_module(util.EXTENSIONS.name)
module2 = self.load_module(util.EXTENSIONS.name)
self.assertIs(module1, module2)

def test_is_package(self):
self.assertFalse(self.loader.is_package(util.EXTENSIONS.name))
for suffix in self.machinery.EXTENSION_SUFFIXES:
path = os.path.join('some', 'path', 'pkg', '__init__' + suffix)
loader = self.machinery.ExtensionFileLoader('pkg', path)
self.assertTrue(loader.is_package('pkg'))


(Frozen_LoaderTests,
Source_LoaderTests
) = util.test_both(LoaderTests, machinery=machinery)


class SinglePhaseExtensionModuleTests(abc.LoaderTests):
# Test loading extension modules without multi-phase initialization.

def setUp(self):
if not self.machinery.EXTENSION_SUFFIXES:
raise unittest.SkipTest("Requires dynamic loading support.")
self.name = '_testsinglephase'
if self.name in sys.builtin_module_names:
raise unittest.SkipTest(
f"{self.name} is a builtin module"
)
finder = self.machinery.FileFinder(None)
self.spec = importlib.util.find_spec(self.name)
assert self.spec
self.loader = self.machinery.ExtensionFileLoader(
self.name, self.spec.origin)

def load_module(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return self.loader.load_module(self.name)

def load_module_by_name(self, fullname):
# Load a module from the test extension by name.
origin = self.spec.origin
loader = self.machinery.ExtensionFileLoader(fullname, origin)
spec = importlib.util.spec_from_loader(fullname, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module

def test_module(self):
# Test loading an extension module.
with util.uncache(self.name):
module = self.load_module()
for attr, value in [('__name__', self.name),
('__file__', self.spec.origin),
('__package__', '')]:
self.assertEqual(getattr(module, attr), value)
with self.assertRaises(AttributeError):
module.__path__
self.assertIs(module, sys.modules[self.name])
self.assertIsInstance(module.__loader__,
self.machinery.ExtensionFileLoader)

# No extension module as __init__ available for testing.
test_package = None

# No extension module in a package available for testing.
test_lacking_parent = None

# No easy way to trigger a failure after a successful import.
test_state_after_failure = None

def test_unloadable(self):
name = 'asdfjkl;'
with self.assertRaises(ImportError) as cm:
self.load_module_by_name(name)
self.assertEqual(cm.exception.name, name)

def test_unloadable_nonascii(self):
# Test behavior with nonexistent module with non-ASCII name.
name = 'fo\xf3'
with self.assertRaises(ImportError) as cm:
self.load_module_by_name(name)
self.assertEqual(cm.exception.name, name)

# It may make sense to add the equivalent to
# the following MultiPhaseExtensionModuleTests tests:
#
# * test_nonmodule
# * test_nonmodule_with_methods
# * test_bad_modules
# * test_nonascii


(Frozen_SinglePhaseExtensionModuleTests,
Source_SinglePhaseExtensionModuleTests
) = util.test_both(SinglePhaseExtensionModuleTests, machinery=machinery)


class MultiPhaseExtensionModuleTests(abc.LoaderTests):
# Test loading extension modules with multi-phase initialization (PEP 489).

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_importlib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
EXTENSIONS.ext = None
EXTENSIONS.filename = None
EXTENSIONS.file_path = None
EXTENSIONS.name = '_testcapi'
EXTENSIONS.name = '_testsinglephase'

def _extension_details():
global EXTENSIONS
Expand Down
1 change: 1 addition & 0 deletions Modules/Setup
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ PYTHONPATH=$(COREPYTHONPATH)
#_testcapi _testcapimodule.c
#_testimportmultiple _testimportmultiple.c
#_testmultiphase _testmultiphase.c
#_testsinglephase _testsinglephase.c

# ---
# Uncommenting the following line tells makesetup that all following modules
Expand Down
1 change: 1 addition & 0 deletions Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
*shared*
@MODULE__TESTIMPORTMULTIPLE_TRUE@_testimportmultiple _testimportmultiple.c
@MODULE__TESTMULTIPHASE_TRUE@_testmultiphase _testmultiphase.c
@MODULE__TESTMULTIPHASE_TRUE@_testsinglephase _testsinglephase.c
@MODULE__CTYPES_TEST_TRUE@_ctypes_test _ctypes/_ctypes_test.c

# Limited API template modules; must be built as shared modules.
Expand Down
78 changes: 78 additions & 0 deletions Modules/_testsinglephase.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@

/* Testing module for single-phase initialization of extension modules
*/
#ifndef Py_BUILD_CORE_BUILTIN
# define Py_BUILD_CORE_MODULE 1
#endif

#include "Python.h"
#include "pycore_namespace.h" // _PyNamespace_New()


/* Function of two integers returning integer */

PyDoc_STRVAR(testexport_foo_doc,
"foo(i,j)\n\
\n\
Return the sum of i and j.");

static PyObject *
testexport_foo(PyObject *self, PyObject *args)
{
long i, j;
long res;
if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
return NULL;
res = i + j;
return PyLong_FromLong(res);
}


static PyMethodDef TestMethods[] = {
{"foo", testexport_foo, METH_VARARGS,
testexport_foo_doc},
{NULL, NULL} /* sentinel */
};


static struct PyModuleDef _testsinglephase = {
PyModuleDef_HEAD_INIT,
.m_name = "_testsinglephase",
.m_doc = PyDoc_STR("Test module _testsinglephase (main)"),
.m_size = -1, // no module state
.m_methods = TestMethods,
};


PyMODINIT_FUNC
PyInit__testsinglephase(void)
{
PyObject *module = PyModule_Create(&_testsinglephase);
if (module == NULL) {
return NULL;
}

/* Add an exception type */
PyObject *temp = PyErr_NewException("_testsinglephase.error", NULL, NULL);
if (temp == NULL) {
goto error;
}
if (PyModule_AddObject(module, "error", temp) != 0) {
Py_DECREF(temp);
goto error;
}

if (PyModule_AddIntConstant(module, "int_const", 1969) != 0) {
goto error;
}

if (PyModule_AddStringConstant(module, "str_const", "something different") != 0) {
goto error;
}

return module;

error:
Py_DECREF(module);
return NULL;
}
115 changes: 115 additions & 0 deletions PCbuild/_testsinglephase.vcxproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM">
<Configuration>Debug</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGInstrument|ARM">
<Configuration>PGInstrument</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGInstrument|ARM64">
<Configuration>PGInstrument</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGInstrument|Win32">
<Configuration>PGInstrument</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGInstrument|x64">
<Configuration>PGInstrument</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGUpdate|ARM">
<Configuration>PGUpdate</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGUpdate|ARM64">
<Configuration>PGUpdate</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGUpdate|Win32">
<Configuration>PGUpdate</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGUpdate|x64">
<Configuration>PGUpdate</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM">
<Configuration>Release</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{2097F1C1-597C-4167-93E3-656A7D6339B2}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>_testsinglephase</RootNamespace>
<SupportPGO>false</SupportPGO>
</PropertyGroup>
<Import Project="python.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<TargetExt>.pyd</TargetExt>
</PropertyGroup>
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="pyproject.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\Modules\_testsinglephase.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="pythoncore.vcxproj">
<Project>{cf7ac3d1-e2df-41d2-bea6-1e2556cdea26}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
Loading