Skip to content

Commit 815f21b

Browse files
authored
[3.14] gh-144490: Test the internal C API in test_cext (#144678)
Backport changes from the main branch. Test also datetime.h in test_cppext.
1 parent 306049b commit 815f21b

File tree

5 files changed

+133
-39
lines changed

5 files changed

+133
-39
lines changed

Lib/test/test_cext/__init__.py

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from test import support
1313

1414

15-
SOURCE = os.path.join(os.path.dirname(__file__), 'extension.c')
15+
SOURCES = [
16+
os.path.join(os.path.dirname(__file__), 'extension.c'),
17+
]
1618
SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
1719

1820

@@ -28,29 +30,13 @@
2830
@support.requires_venv_with_pip()
2931
@support.requires_subprocess()
3032
@support.requires_resource('cpu')
31-
class TestExt(unittest.TestCase):
33+
class BaseTests:
34+
TEST_INTERNAL_C_API = False
35+
3236
# Default build with no options
3337
def test_build(self):
3438
self.check_build('_test_cext')
3539

36-
def test_build_c11(self):
37-
self.check_build('_test_c11_cext', std='c11')
38-
39-
@unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c99")
40-
def test_build_c99(self):
41-
# In public docs, we say C API is compatible with C11. However,
42-
# in practice we do maintain C99 compatibility in public headers.
43-
# Please ask the C API WG before adding a new C11-only feature.
44-
self.check_build('_test_c99_cext', std='c99')
45-
46-
@support.requires_gil_enabled('incompatible with Free Threading')
47-
def test_build_limited(self):
48-
self.check_build('_test_limited_cext', limited=True)
49-
50-
@support.requires_gil_enabled('broken for now with Free Threading')
51-
def test_build_limited_c11(self):
52-
self.check_build('_test_limited_c11_cext', limited=True, std='c11')
53-
5440
def check_build(self, extension_name, std=None, limited=False):
5541
venv_dir = 'env'
5642
with support.setup_venv_with_pip_setuptools(venv_dir) as python_exe:
@@ -61,7 +47,9 @@ def _check_build(self, extension_name, python_exe, std, limited):
6147
pkg_dir = 'pkg'
6248
os.mkdir(pkg_dir)
6349
shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP)))
64-
shutil.copy(SOURCE, os.path.join(pkg_dir, os.path.basename(SOURCE)))
50+
for source in SOURCES:
51+
dest = os.path.join(pkg_dir, os.path.basename(source))
52+
shutil.copy(source, dest)
6553

6654
def run_cmd(operation, cmd):
6755
env = os.environ.copy()
@@ -70,6 +58,7 @@ def run_cmd(operation, cmd):
7058
if limited:
7159
env['CPYTHON_TEST_LIMITED'] = '1'
7260
env['CPYTHON_TEST_EXT_NAME'] = extension_name
61+
env['TEST_INTERNAL_C_API'] = str(int(self.TEST_INTERNAL_C_API))
7362
if support.verbose:
7463
print('Run:', ' '.join(map(shlex.quote, cmd)))
7564
subprocess.run(cmd, check=True, env=env)
@@ -110,5 +99,29 @@ def run_cmd(operation, cmd):
11099
run_cmd('Import', cmd)
111100

112101

102+
class TestPublicCAPI(BaseTests, unittest.TestCase):
103+
@support.requires_gil_enabled('incompatible with Free Threading')
104+
def test_build_limited(self):
105+
self.check_build('_test_limited_cext', limited=True)
106+
107+
@support.requires_gil_enabled('broken for now with Free Threading')
108+
def test_build_limited_c11(self):
109+
self.check_build('_test_limited_c11_cext', limited=True, std='c11')
110+
111+
def test_build_c11(self):
112+
self.check_build('_test_c11_cext', std='c11')
113+
114+
@unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c99")
115+
def test_build_c99(self):
116+
# In public docs, we say C API is compatible with C11. However,
117+
# in practice we do maintain C99 compatibility in public headers.
118+
# Please ask the C API WG before adding a new C11-only feature.
119+
self.check_build('_test_c99_cext', std='c99')
120+
121+
122+
class TestInteralCAPI(BaseTests, unittest.TestCase):
123+
TEST_INTERNAL_C_API = True
124+
125+
113126
if __name__ == "__main__":
114127
unittest.main()

Lib/test/test_cext/extension.c

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
// gh-116869: Basic C test extension to check that the Python C API
22
// does not emit C compiler warnings.
3+
//
4+
// Test also the internal C API if the TEST_INTERNAL_C_API macro is defined.
35

46
// Always enable assertions
57
#undef NDEBUG
68

9+
#ifdef TEST_INTERNAL_C_API
10+
# define Py_BUILD_CORE_MODULE 1
11+
#endif
12+
713
#include "Python.h"
14+
#include "datetime.h"
15+
16+
#ifdef TEST_INTERNAL_C_API
17+
// gh-135906: Check for compiler warnings in the internal C API.
18+
// - Cython uses pycore_frame.h.
19+
// - greenlet uses pycore_frame.h, pycore_interpframe_structs.h and
20+
// pycore_interpframe.h.
21+
# include "internal/pycore_frame.h"
22+
# include "internal/pycore_gc.h"
23+
# include "internal/pycore_interp.h"
24+
# include "internal/pycore_interpframe.h"
25+
# include "internal/pycore_interpframe_structs.h"
26+
# include "internal/pycore_object.h"
27+
# include "internal/pycore_pystate.h"
28+
#endif
829

930
#ifndef MODULE_NAME
1031
# error "MODULE_NAME macro must be defined"
@@ -30,27 +51,43 @@ _testcext_add(PyObject *Py_UNUSED(module), PyObject *args)
3051
}
3152

3253

54+
static PyObject *
55+
test_datetime(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
56+
{
57+
// datetime.h is excluded from the limited C API
58+
#ifndef Py_LIMITED_API
59+
PyDateTime_IMPORT;
60+
if (PyErr_Occurred()) {
61+
return NULL;
62+
}
63+
#endif
64+
65+
Py_RETURN_NONE;
66+
}
67+
68+
3369
static PyMethodDef _testcext_methods[] = {
3470
{"add", _testcext_add, METH_VARARGS, _testcext_add_doc},
71+
{"test_datetime", test_datetime, METH_NOARGS, NULL},
3572
{NULL, NULL, 0, NULL} // sentinel
3673
};
3774

3875

3976
static int
40-
_testcext_exec(
41-
#ifdef __STDC_VERSION__
42-
PyObject *module
43-
#else
44-
PyObject *Py_UNUSED(module)
45-
#endif
46-
)
77+
_testcext_exec(PyObject *module)
4778
{
79+
PyObject *result;
80+
4881
#ifdef __STDC_VERSION__
4982
if (PyModule_AddIntMacro(module, __STDC_VERSION__) < 0) {
5083
return -1;
5184
}
5285
#endif
5386

87+
result = PyObject_CallMethod(module, "test_datetime", "");
88+
if (!result) return -1;
89+
Py_DECREF(result);
90+
5491
// test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR()
5592
Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int));
5693
assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0);

Lib/test/test_cext/setup.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@
1414

1515
if not support.MS_WINDOWS:
1616
# C compiler flags for GCC and clang
17-
CFLAGS = [
17+
BASE_CFLAGS = [
1818
# The purpose of test_cext extension is to check that building a C
1919
# extension using the Python C API does not emit C compiler warnings.
2020
'-Werror',
21+
]
22+
23+
# C compiler flags for GCC and clang
24+
PUBLIC_CFLAGS = [
25+
*BASE_CFLAGS,
2126

2227
# gh-120593: Check the 'const' qualifier
2328
'-Wcast-qual',
@@ -26,27 +31,42 @@
2631
'-pedantic-errors',
2732
]
2833
if not support.Py_GIL_DISABLED:
29-
CFLAGS.append(
34+
PUBLIC_CFLAGS.append(
3035
# gh-116869: The Python C API must be compatible with building
3136
# with the -Werror=declaration-after-statement compiler flag.
3237
'-Werror=declaration-after-statement',
3338
)
39+
INTERNAL_CFLAGS = [*BASE_CFLAGS]
3440
else:
3541
# MSVC compiler flags
36-
CFLAGS = [
37-
# Display warnings level 1 to 4
38-
'/W4',
42+
BASE_CFLAGS = [
3943
# Treat all compiler warnings as compiler errors
4044
'/WX',
4145
]
46+
PUBLIC_CFLAGS = [
47+
*BASE_CFLAGS,
48+
# Display warnings level 1 to 4
49+
'/W4',
50+
]
51+
INTERNAL_CFLAGS = [
52+
*BASE_CFLAGS,
53+
# Display warnings level 1 to 3
54+
'/W3',
55+
]
4256

4357

4458
def main():
4559
std = os.environ.get("CPYTHON_TEST_STD", "")
4660
module_name = os.environ["CPYTHON_TEST_EXT_NAME"]
4761
limited = bool(os.environ.get("CPYTHON_TEST_LIMITED", ""))
62+
internal = bool(int(os.environ.get("TEST_INTERNAL_C_API", "0")))
4863

49-
cflags = list(CFLAGS)
64+
sources = [SOURCE]
65+
66+
if not internal:
67+
cflags = list(PUBLIC_CFLAGS)
68+
else:
69+
cflags = list(INTERNAL_CFLAGS)
5070
cflags.append(f'-DMODULE_NAME={module_name}')
5171

5272
# Add -std=STD or /std:STD (MSVC) compiler flag
@@ -75,6 +95,9 @@ def main():
7595
version = sys.hexversion
7696
cflags.append(f'-DPy_LIMITED_API={version:#x}')
7797

98+
if internal:
99+
cflags.append('-DTEST_INTERNAL_C_API=1')
100+
78101
# On Windows, add PCbuild\amd64\ to include and library directories
79102
include_dirs = []
80103
library_dirs = []
@@ -90,7 +113,7 @@ def main():
90113
print(f"Add PCbuild directory: {pcbuild}")
91114

92115
# Display information to help debugging
93-
for env_name in ('CC', 'CFLAGS'):
116+
for env_name in ('CC', 'CFLAGS', 'CPPFLAGS'):
94117
if env_name in os.environ:
95118
print(f"{env_name} env var: {os.environ[env_name]!r}")
96119
else:
@@ -99,7 +122,7 @@ def main():
99122

100123
ext = Extension(
101124
module_name,
102-
sources=[SOURCE],
125+
sources=sources,
103126
extra_compile_args=cflags,
104127
include_dirs=include_dirs,
105128
library_dirs=library_dirs)

Lib/test/test_cppext/extension.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111
#endif
1212

1313
#include "Python.h"
14+
#include "datetime.h"
1415

1516
#ifdef TEST_INTERNAL_C_API
1617
// gh-135906: Check for compiler warnings in the internal C API
1718
# include "internal/pycore_frame.h"
1819
// mimalloc emits many compiler warnings when Python is built in debug
1920
// mode (when MI_DEBUG is not zero).
20-
// mimalloc emits compiler warnings when Python is built on Windows.
21-
# if !defined(Py_DEBUG) && !defined(MS_WINDOWS)
21+
// mimalloc emits compiler warnings when Python is built on Windows
22+
// and macOS.
23+
# if !defined(Py_DEBUG) && !defined(MS_WINDOWS) && !defined(__APPLE__)
2224
# include "internal/pycore_backoff.h"
2325
# include "internal/pycore_cell.h"
2426
# endif
@@ -230,11 +232,26 @@ test_virtual_object(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
230232
Py_RETURN_NONE;
231233
}
232234

235+
static PyObject *
236+
test_datetime(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
237+
{
238+
// datetime.h is excluded from the limited C API
239+
#ifndef Py_LIMITED_API
240+
PyDateTime_IMPORT;
241+
if (PyErr_Occurred()) {
242+
return NULL;
243+
}
244+
#endif
245+
246+
Py_RETURN_NONE;
247+
}
248+
233249
static PyMethodDef _testcppext_methods[] = {
234250
{"add", _testcppext_add, METH_VARARGS, _testcppext_add_doc},
235251
{"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
236252
{"test_unicode", test_unicode, METH_NOARGS, _Py_NULL},
237253
{"test_virtual_object", test_virtual_object, METH_NOARGS, _Py_NULL},
254+
{"test_datetime", test_datetime, METH_NOARGS, _Py_NULL},
238255
// Note: _testcppext_exec currently runs all test functions directly.
239256
// When adding a new one, add a call there.
240257

@@ -263,6 +280,10 @@ _testcppext_exec(PyObject *module)
263280
if (!result) return -1;
264281
Py_DECREF(result);
265282

283+
result = PyObject_CallMethod(module, "test_datetime", "");
284+
if (!result) return -1;
285+
Py_DECREF(result);
286+
266287
// test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR()
267288
Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int));
268289
assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0);

Lib/test/test_cppext/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def main():
101101
print(f"Add PCbuild directory: {pcbuild}")
102102

103103
# Display information to help debugging
104-
for env_name in ('CC', 'CFLAGS', 'CPPFLAGS'):
104+
for env_name in ('CC', 'CXX', 'CFLAGS', 'CPPFLAGS', 'CXXFLAGS'):
105105
if env_name in os.environ:
106106
print(f"{env_name} env var: {os.environ[env_name]!r}")
107107
else:

0 commit comments

Comments
 (0)