aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/test/test_cext
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_cext')
-rw-r--r--Lib/test/test_cext/__init__.py72
-rw-r--r--Lib/test/test_cext/create_moduledef.c29
-rw-r--r--Lib/test/test_cext/extension.c51
-rw-r--r--Lib/test/test_cext/setup.py43
4 files changed, 160 insertions, 35 deletions
diff --git a/Lib/test/test_cext/__init__.py b/Lib/test/test_cext/__init__.py
index 46fde541494..fb93c6ccbb6 100644
--- a/Lib/test/test_cext/__init__.py
+++ b/Lib/test/test_cext/__init__.py
@@ -12,7 +12,10 @@ import unittest
from test import support
-SOURCE = os.path.join(os.path.dirname(__file__), 'extension.c')
+SOURCES = [
+ os.path.join(os.path.dirname(__file__), 'extension.c'),
+ os.path.join(os.path.dirname(__file__), 'create_moduledef.c'),
+]
SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
@@ -28,40 +31,29 @@ SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
@support.requires_venv_with_pip()
@support.requires_subprocess()
@support.requires_resource('cpu')
-class TestExt(unittest.TestCase):
+class BaseTests:
+ TEST_INTERNAL_C_API = False
+
# Default build with no options
def test_build(self):
self.check_build('_test_cext')
- def test_build_c11(self):
- self.check_build('_test_c11_cext', std='c11')
-
- @unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c99")
- def test_build_c99(self):
- # In public docs, we say C API is compatible with C11. However,
- # in practice we do maintain C99 compatibility in public headers.
- # Please ask the C API WG before adding a new C11-only feature.
- self.check_build('_test_c99_cext', std='c99')
-
- @support.requires_gil_enabled('incompatible with Free Threading')
- def test_build_limited(self):
- self.check_build('_test_limited_cext', limited=True)
-
- @support.requires_gil_enabled('broken for now with Free Threading')
- def test_build_limited_c11(self):
- self.check_build('_test_limited_c11_cext', limited=True, std='c11')
-
- def check_build(self, extension_name, std=None, limited=False):
+ def check_build(self, extension_name, std=None, limited=False,
+ opaque_pyobject=False):
venv_dir = 'env'
with support.setup_venv_with_pip_setuptools(venv_dir) as python_exe:
self._check_build(extension_name, python_exe,
- std=std, limited=limited)
+ std=std, limited=limited,
+ opaque_pyobject=opaque_pyobject)
- def _check_build(self, extension_name, python_exe, std, limited):
+ def _check_build(self, extension_name, python_exe, std, limited,
+ opaque_pyobject):
pkg_dir = 'pkg'
os.mkdir(pkg_dir)
shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP)))
- shutil.copy(SOURCE, os.path.join(pkg_dir, os.path.basename(SOURCE)))
+ for source in SOURCES:
+ dest = os.path.join(pkg_dir, os.path.basename(source))
+ shutil.copy(source, dest)
def run_cmd(operation, cmd):
env = os.environ.copy()
@@ -69,7 +61,10 @@ class TestExt(unittest.TestCase):
env['CPYTHON_TEST_STD'] = std
if limited:
env['CPYTHON_TEST_LIMITED'] = '1'
+ if opaque_pyobject:
+ env['CPYTHON_TEST_OPAQUE_PYOBJECT'] = '1'
env['CPYTHON_TEST_EXT_NAME'] = extension_name
+ env['TEST_INTERNAL_C_API'] = str(int(self.TEST_INTERNAL_C_API))
if support.verbose:
print('Run:', ' '.join(map(shlex.quote, cmd)))
subprocess.run(cmd, check=True, env=env)
@@ -110,5 +105,34 @@ class TestExt(unittest.TestCase):
run_cmd('Import', cmd)
+class TestPublicCAPI(BaseTests, unittest.TestCase):
+ @support.requires_gil_enabled('incompatible with Free Threading')
+ def test_build_limited(self):
+ self.check_build('_test_limited_cext', limited=True)
+
+ @support.requires_gil_enabled('broken for now with Free Threading')
+ def test_build_limited_c11(self):
+ self.check_build('_test_limited_c11_cext', limited=True, std='c11')
+
+ def test_build_c11(self):
+ self.check_build('_test_c11_cext', std='c11')
+
+ def test_build_opaque_pyobject(self):
+ # Test with _Py_OPAQUE_PYOBJECT
+ self.check_build('_test_limited_opaque_cext', limited=True,
+ opaque_pyobject=True)
+
+ @unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c99")
+ def test_build_c99(self):
+ # In public docs, we say C API is compatible with C11. However,
+ # in practice we do maintain C99 compatibility in public headers.
+ # Please ask the C API WG before adding a new C11-only feature.
+ self.check_build('_test_c99_cext', std='c99')
+
+
+class TestInteralCAPI(BaseTests, unittest.TestCase):
+ TEST_INTERNAL_C_API = True
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_cext/create_moduledef.c b/Lib/test/test_cext/create_moduledef.c
new file mode 100644
index 00000000000..249c3163552
--- /dev/null
+++ b/Lib/test/test_cext/create_moduledef.c
@@ -0,0 +1,29 @@
+// Workaround for testing _Py_OPAQUE_PYOBJECT.
+// See end of 'extension.c'
+
+
+#undef _Py_OPAQUE_PYOBJECT
+#undef Py_LIMITED_API
+#include "Python.h"
+
+
+// (repeated definition to avoid creating a header)
+extern PyObject *testcext_create_moduledef(
+ const char *name, const char *doc,
+ PyMethodDef *methods, PyModuleDef_Slot *slots);
+
+PyObject *testcext_create_moduledef(
+ const char *name, const char *doc,
+ PyMethodDef *methods, PyModuleDef_Slot *slots) {
+
+ static struct PyModuleDef _testcext_module = {
+ PyModuleDef_HEAD_INIT,
+ };
+ if (!_testcext_module.m_name) {
+ _testcext_module.m_name = name;
+ _testcext_module.m_doc = doc;
+ _testcext_module.m_methods = methods;
+ _testcext_module.m_slots = slots;
+ }
+ return PyModuleDef_Init(&_testcext_module);
+}
diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c
index 64629c5a6da..73fc67ae59d 100644
--- a/Lib/test/test_cext/extension.c
+++ b/Lib/test/test_cext/extension.c
@@ -1,11 +1,31 @@
// gh-116869: Basic C test extension to check that the Python C API
// does not emit C compiler warnings.
+//
+// Test also the internal C API if the TEST_INTERNAL_C_API macro is defined.
// Always enable assertions
#undef NDEBUG
+#ifdef TEST_INTERNAL_C_API
+# define Py_BUILD_CORE_MODULE 1
+#endif
+
#include "Python.h"
+#ifdef TEST_INTERNAL_C_API
+ // gh-135906: Check for compiler warnings in the internal C API.
+ // - Cython uses pycore_frame.h.
+ // - greenlet uses pycore_frame.h, pycore_interpframe_structs.h and
+ // pycore_interpframe.h.
+# include "internal/pycore_frame.h"
+# include "internal/pycore_gc.h"
+# include "internal/pycore_interp.h"
+# include "internal/pycore_interpframe.h"
+# include "internal/pycore_interpframe_structs.h"
+# include "internal/pycore_object.h"
+# include "internal/pycore_pystate.h"
+#endif
+
#ifndef MODULE_NAME
# error "MODULE_NAME macro must be defined"
#endif
@@ -58,6 +78,9 @@ _testcext_exec(
return 0;
}
+#define _FUNC_NAME(NAME) PyInit_ ## NAME
+#define FUNC_NAME(NAME) _FUNC_NAME(NAME)
+
// Converting from function pointer to void* has undefined behavior, but
// works on all known platforms, and CPython's module and type slots currently
// need it.
@@ -76,9 +99,10 @@ static PyModuleDef_Slot _testcext_slots[] = {
_Py_COMP_DIAG_POP
-
PyDoc_STRVAR(_testcext_doc, "C test extension.");
+#ifndef _Py_OPAQUE_PYOBJECT
+
static struct PyModuleDef _testcext_module = {
PyModuleDef_HEAD_INIT, // m_base
STR(MODULE_NAME), // m_name
@@ -92,11 +116,30 @@ static struct PyModuleDef _testcext_module = {
};
-#define _FUNC_NAME(NAME) PyInit_ ## NAME
-#define FUNC_NAME(NAME) _FUNC_NAME(NAME)
-
PyMODINIT_FUNC
FUNC_NAME(MODULE_NAME)(void)
{
return PyModuleDef_Init(&_testcext_module);
}
+
+#else // _Py_OPAQUE_PYOBJECT
+
+// Opaque PyObject means that PyModuleDef is also opaque and cannot be
+// declared statically. See PEP 793.
+// So, this part of module creation is split into a separate source file
+// which uses non-limited API.
+
+// (repeated definition to avoid creating a header)
+extern PyObject *testcext_create_moduledef(
+ const char *name, const char *doc,
+ PyMethodDef *methods, PyModuleDef_Slot *slots);
+
+
+PyMODINIT_FUNC
+FUNC_NAME(MODULE_NAME)(void)
+{
+ return testcext_create_moduledef(
+ STR(MODULE_NAME), _testcext_doc, _testcext_methods, _testcext_slots);
+}
+
+#endif // _Py_OPAQUE_PYOBJECT
diff --git a/Lib/test/test_cext/setup.py b/Lib/test/test_cext/setup.py
index 1275282983f..4d71e4751f7 100644
--- a/Lib/test/test_cext/setup.py
+++ b/Lib/test/test_cext/setup.py
@@ -14,10 +14,15 @@ SOURCE = 'extension.c'
if not support.MS_WINDOWS:
# C compiler flags for GCC and clang
- CFLAGS = [
+ BASE_CFLAGS = [
# The purpose of test_cext extension is to check that building a C
# extension using the Python C API does not emit C compiler warnings.
'-Werror',
+ ]
+
+ # C compiler flags for GCC and clang
+ PUBLIC_CFLAGS = [
+ *BASE_CFLAGS,
# gh-120593: Check the 'const' qualifier
'-Wcast-qual',
@@ -26,27 +31,43 @@ if not support.MS_WINDOWS:
'-pedantic-errors',
]
if not support.Py_GIL_DISABLED:
- CFLAGS.append(
+ PUBLIC_CFLAGS.append(
# gh-116869: The Python C API must be compatible with building
# with the -Werror=declaration-after-statement compiler flag.
'-Werror=declaration-after-statement',
)
+ INTERNAL_CFLAGS = [*BASE_CFLAGS]
else:
# MSVC compiler flags
- CFLAGS = [
- # Display warnings level 1 to 4
- '/W4',
+ BASE_CFLAGS = [
# Treat all compiler warnings as compiler errors
'/WX',
]
+ PUBLIC_CFLAGS = [
+ *BASE_CFLAGS,
+ # Display warnings level 1 to 4
+ '/W4',
+ ]
+ INTERNAL_CFLAGS = [
+ *BASE_CFLAGS,
+ # Display warnings level 1 to 3
+ '/W3',
+ ]
def main():
std = os.environ.get("CPYTHON_TEST_STD", "")
module_name = os.environ["CPYTHON_TEST_EXT_NAME"]
limited = bool(os.environ.get("CPYTHON_TEST_LIMITED", ""))
+ opaque_pyobject = bool(os.environ.get("CPYTHON_TEST_OPAQUE_PYOBJECT", ""))
+ internal = bool(int(os.environ.get("TEST_INTERNAL_C_API", "0")))
- cflags = list(CFLAGS)
+ sources = [SOURCE]
+
+ if not internal:
+ cflags = list(PUBLIC_CFLAGS)
+ else:
+ cflags = list(INTERNAL_CFLAGS)
cflags.append(f'-DMODULE_NAME={module_name}')
# Add -std=STD or /std:STD (MSVC) compiler flag
@@ -75,6 +96,14 @@ def main():
version = sys.hexversion
cflags.append(f'-DPy_LIMITED_API={version:#x}')
+ # Define _Py_OPAQUE_PYOBJECT macro
+ if opaque_pyobject:
+ cflags.append(f'-D_Py_OPAQUE_PYOBJECT')
+ sources.append('create_moduledef.c')
+
+ if internal:
+ cflags.append('-DTEST_INTERNAL_C_API=1')
+
# On Windows, add PCbuild\amd64\ to include and library directories
include_dirs = []
library_dirs = []
@@ -99,7 +128,7 @@ def main():
ext = Extension(
module_name,
- sources=[SOURCE],
+ sources=sources,
extra_compile_args=cflags,
include_dirs=include_dirs,
library_dirs=library_dirs)