Skip to content

Commit d9bcdda

Browse files
gh-116417: Add _testlimitedcapi C extension (#116419)
Add a new C extension "_testlimitedcapi" which is only built with the limited C API. Move heaptype_relative.c and vectorcall_limited.c from Modules/_testcapi/ to Modules/_testlimitedcapi/. * configure: add _testlimitedcapi test extension. * Update generate_stdlib_module_names.py. * Update make check-c-globals. Co-authored-by: Erlend E. Aasland <[email protected]>
1 parent d9ccde2 commit d9bcdda

23 files changed

+394
-109
lines changed

Lib/test/support/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1153,8 +1153,9 @@ def refcount_test(test):
11531153
def requires_limited_api(test):
11541154
try:
11551155
import _testcapi
1156+
import _testlimitedcapi
11561157
except ImportError:
1157-
return unittest.skip('needs _testcapi module')(test)
1158+
return unittest.skip('needs _testcapi and _testlimitedcapi modules')(test)
11581159
return test
11591160

11601161
def requires_specialization(test):

Lib/test/test_call.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import _testcapi
66
except ImportError:
77
_testcapi = None
8+
try:
9+
import _testlimitedcapi
10+
except ImportError:
11+
_testlimitedcapi = None
812
import struct
913
import collections
1014
import itertools
@@ -837,12 +841,12 @@ def get_a(x):
837841
@requires_limited_api
838842
def test_vectorcall_limited_incoming(self):
839843
from _testcapi import pyobject_vectorcall
840-
obj = _testcapi.LimitedVectorCallClass()
844+
obj = _testlimitedcapi.LimitedVectorCallClass()
841845
self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called")
842846

843847
@requires_limited_api
844848
def test_vectorcall_limited_outgoing(self):
845-
from _testcapi import call_vectorcall
849+
from _testlimitedcapi import call_vectorcall
846850

847851
args_captured = []
848852
kwargs_captured = []
@@ -858,7 +862,7 @@ def f(*args, **kwargs):
858862

859863
@requires_limited_api
860864
def test_vectorcall_limited_outgoing_method(self):
861-
from _testcapi import call_vectorcall_method
865+
from _testlimitedcapi import call_vectorcall_method
862866

863867
args_captured = []
864868
kwargs_captured = []

Lib/test/test_capi/test_misc.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
# Skip this test if the _testcapi module isn't available.
4848
_testcapi = import_helper.import_module('_testcapi')
4949

50+
import _testlimitedcapi
5051
import _testinternalcapi
5152

5253

@@ -1124,7 +1125,7 @@ def test_heaptype_relative_sizes(self):
11241125
# Test subclassing using "relative" basicsize, see PEP 697
11251126
def check(extra_base_size, extra_size):
11261127
Base, Sub, instance, data_ptr, data_offset, data_size = (
1127-
_testcapi.make_sized_heaptypes(
1128+
_testlimitedcapi.make_sized_heaptypes(
11281129
extra_base_size, -extra_size))
11291130

11301131
# no alignment shenanigans when inheriting directly
@@ -1152,11 +1153,11 @@ def check(extra_base_size, extra_size):
11521153

11531154
# we don't reserve (requested + alignment) or more data
11541155
self.assertLess(data_size - extra_size,
1155-
_testcapi.ALIGNOF_MAX_ALIGN_T)
1156+
_testlimitedcapi.ALIGNOF_MAX_ALIGN_T)
11561157

11571158
# The offsets/sizes we calculated should be aligned.
1158-
self.assertEqual(data_offset % _testcapi.ALIGNOF_MAX_ALIGN_T, 0)
1159-
self.assertEqual(data_size % _testcapi.ALIGNOF_MAX_ALIGN_T, 0)
1159+
self.assertEqual(data_offset % _testlimitedcapi.ALIGNOF_MAX_ALIGN_T, 0)
1160+
self.assertEqual(data_size % _testlimitedcapi.ALIGNOF_MAX_ALIGN_T, 0)
11601161

11611162
sizes = sorted({0, 1, 2, 3, 4, 7, 8, 123,
11621163
object.__basicsize__,
@@ -1182,7 +1183,7 @@ def test_heaptype_inherit_itemsize(self):
11821183
object.__basicsize__+1})
11831184
for extra_size in sizes:
11841185
with self.subTest(extra_size=extra_size):
1185-
Sub = _testcapi.subclass_var_heaptype(
1186+
Sub = _testlimitedcapi.subclass_var_heaptype(
11861187
_testcapi.HeapCCollection, -extra_size, 0, 0)
11871188
collection = Sub(1, 2, 3)
11881189
collection.set_data_to_3s()
@@ -1196,7 +1197,7 @@ def test_heaptype_invalid_inheritance(self):
11961197
with self.assertRaises(SystemError,
11971198
msg="Cannot extend variable-size class without "
11981199
+ "Py_TPFLAGS_ITEMS_AT_END"):
1199-
_testcapi.subclass_heaptype(int, -8, 0)
1200+
_testlimitedcapi.subclass_heaptype(int, -8, 0)
12001201

12011202
def test_heaptype_relative_members(self):
12021203
"""Test HeapCCollection subclasses work properly"""
@@ -1209,7 +1210,7 @@ def test_heaptype_relative_members(self):
12091210
for offset in sizes:
12101211
with self.subTest(extra_base_size=extra_base_size, extra_size=extra_size, offset=offset):
12111212
if offset < extra_size:
1212-
Sub = _testcapi.make_heaptype_with_member(
1213+
Sub = _testlimitedcapi.make_heaptype_with_member(
12131214
extra_base_size, -extra_size, offset, True)
12141215
Base = Sub.mro()[1]
12151216
instance = Sub()
@@ -1228,29 +1229,29 @@ def test_heaptype_relative_members(self):
12281229
instance.set_memb_relative(0)
12291230
else:
12301231
with self.assertRaises(SystemError):
1231-
Sub = _testcapi.make_heaptype_with_member(
1232+
Sub = _testlimitedcapi.make_heaptype_with_member(
12321233
extra_base_size, -extra_size, offset, True)
12331234
with self.assertRaises(SystemError):
1234-
Sub = _testcapi.make_heaptype_with_member(
1235+
Sub = _testlimitedcapi.make_heaptype_with_member(
12351236
extra_base_size, extra_size, offset, True)
12361237
with self.subTest(extra_base_size=extra_base_size, extra_size=extra_size):
12371238
with self.assertRaises(SystemError):
1238-
Sub = _testcapi.make_heaptype_with_member(
1239+
Sub = _testlimitedcapi.make_heaptype_with_member(
12391240
extra_base_size, -extra_size, -1, True)
12401241

12411242
def test_heaptype_relative_members_errors(self):
12421243
with self.assertRaisesRegex(
12431244
SystemError,
12441245
r"With Py_RELATIVE_OFFSET, basicsize must be negative"):
1245-
_testcapi.make_heaptype_with_member(0, 1234, 0, True)
1246+
_testlimitedcapi.make_heaptype_with_member(0, 1234, 0, True)
12461247
with self.assertRaisesRegex(
12471248
SystemError, r"Member offset out of range \(0\.\.-basicsize\)"):
1248-
_testcapi.make_heaptype_with_member(0, -8, 1234, True)
1249+
_testlimitedcapi.make_heaptype_with_member(0, -8, 1234, True)
12491250
with self.assertRaisesRegex(
12501251
SystemError, r"Member offset out of range \(0\.\.-basicsize\)"):
1251-
_testcapi.make_heaptype_with_member(0, -8, -1, True)
1252+
_testlimitedcapi.make_heaptype_with_member(0, -8, -1, True)
12521253

1253-
Sub = _testcapi.make_heaptype_with_member(0, -8, 0, True)
1254+
Sub = _testlimitedcapi.make_heaptype_with_member(0, -8, 0, True)
12541255
instance = Sub()
12551256
with self.assertRaisesRegex(
12561257
SystemError, r"PyMember_GetOne used with Py_RELATIVE_OFFSET"):
@@ -2264,10 +2265,19 @@ def test_gilstate_matches_current(self):
22642265
_testcapi.test_current_tstate_matches()
22652266

22662267

2268+
def get_test_funcs(mod, exclude_prefix=None):
2269+
funcs = {}
2270+
for name in dir(mod):
2271+
if not name.startswith('test_'):
2272+
continue
2273+
if exclude_prefix is not None and name.startswith(exclude_prefix):
2274+
continue
2275+
funcs[name] = getattr(mod, name)
2276+
return funcs
2277+
2278+
22672279
class Test_testcapi(unittest.TestCase):
2268-
locals().update((name, getattr(_testcapi, name))
2269-
for name in dir(_testcapi)
2270-
if name.startswith('test_'))
2280+
locals().update(get_test_funcs(_testcapi))
22712281

22722282
# Suppress warning from PyUnicode_FromUnicode().
22732283
@warnings_helper.ignore_warnings(category=DeprecationWarning)
@@ -2278,11 +2288,13 @@ def test_version_api_data(self):
22782288
self.assertEqual(_testcapi.Py_Version, sys.hexversion)
22792289

22802290

2291+
class Test_testlimitedcapi(unittest.TestCase):
2292+
locals().update(get_test_funcs(_testlimitedcapi))
2293+
2294+
22812295
class Test_testinternalcapi(unittest.TestCase):
2282-
locals().update((name, getattr(_testinternalcapi, name))
2283-
for name in dir(_testinternalcapi)
2284-
if name.startswith('test_')
2285-
and not name.startswith('test_lock_'))
2296+
locals().update(get_test_funcs(_testinternalcapi,
2297+
exclude_prefix='test_lock_'))
22862298

22872299

22882300
@threading_helper.requires_working_threading()

Modules/Setup.stdlib.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@
162162
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
163163
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
164164
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
165-
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c _testcapi/time.c
165+
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c _testcapi/time.c
166+
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/heaptype_relative.c
166167
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
167168
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
168169

Modules/_testcapi/clinic/vectorcall_limited.c.h

Lines changed: 0 additions & 20 deletions
This file was deleted.

Modules/_testcapi/parts.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,4 @@ int _PyTestCapi_Init_Sys(PyObject *module);
6161
int _PyTestCapi_Init_Hash(PyObject *module);
6262
int _PyTestCapi_Init_Time(PyObject *module);
6363

64-
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
65-
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);
66-
6764
#endif // Py_TESTCAPI_PARTS_H

Modules/_testcapimodule.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4098,12 +4098,6 @@ PyInit__testcapi(void)
40984098
if (_PyTestCapi_Init_PyAtomic(m) < 0) {
40994099
return NULL;
41004100
}
4101-
if (_PyTestCapi_Init_VectorcallLimited(m) < 0) {
4102-
return NULL;
4103-
}
4104-
if (_PyTestCapi_Init_HeaptypeRelative(m) < 0) {
4105-
return NULL;
4106-
}
41074101
if (_PyTestCapi_Init_Hash(m) < 0) {
41084102
return NULL;
41094103
}

Modules/_testlimitedcapi.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Test the limited C API.
3+
*
4+
* The 'test_*' functions exported by this module are run as part of the
5+
* standard Python regression test, via Lib/test/test_capi.py.
6+
*/
7+
8+
#include "_testlimitedcapi/parts.h"
9+
10+
static PyMethodDef TestMethods[] = {
11+
{NULL, NULL} /* sentinel */
12+
};
13+
14+
static struct PyModuleDef _testlimitedcapimodule = {
15+
PyModuleDef_HEAD_INIT,
16+
.m_name = "_testlimitedcapi",
17+
.m_size = 0,
18+
.m_methods = TestMethods,
19+
};
20+
21+
PyMODINIT_FUNC
22+
PyInit__testlimitedcapi(void)
23+
{
24+
PyObject *mod = PyModule_Create(&_testlimitedcapimodule);
25+
if (mod == NULL) {
26+
return NULL;
27+
}
28+
29+
if (_PyTestCapi_Init_VectorcallLimited(mod) < 0) {
30+
return NULL;
31+
}
32+
if (_PyTestCapi_Init_HeaptypeRelative(mod) < 0) {
33+
return NULL;
34+
}
35+
return mod;
36+
}

Modules/_testlimitedcapi/clinic/vectorcall_limited.c.h

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/_testcapi/heaptype_relative.c renamed to Modules/_testlimitedcapi/heaptype_relative.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
#include "pyconfig.h" // Py_GIL_DISABLED
2-
3-
#ifndef Py_GIL_DISABLED
4-
#define Py_LIMITED_API 0x030c0000 // 3.12
5-
#endif
6-
71
#include "parts.h"
82
#include <stddef.h> // max_align_t
93
#include <string.h> // memset

Modules/_testlimitedcapi/parts.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef Py_TESTLIMITEDCAPI_PARTS_H
2+
#define Py_TESTLIMITEDCAPI_PARTS_H
3+
4+
// Always enable assertions
5+
#undef NDEBUG
6+
7+
#include "pyconfig.h" // Py_GIL_DISABLED
8+
9+
// Use the limited C API
10+
#ifndef Py_GIL_DISABLED
11+
# define Py_LIMITED_API 0x030c0000 // 3.12
12+
#endif
13+
14+
// Make sure that the internal C API cannot be used.
15+
#undef Py_BUILD_CORE_MODULE
16+
#undef Py_BUILD_CORE_BUILTIN
17+
18+
#include "Python.h"
19+
20+
#ifdef Py_BUILD_CORE
21+
# error "Py_BUILD_CORE macro must not be defined"
22+
#endif
23+
24+
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
25+
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);
26+
27+
#endif // Py_TESTLIMITEDCAPI_PARTS_H

0 commit comments

Comments
 (0)