Skip to content
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

gh-116417: Add _testlimitedcapi C extension #116419

Merged
merged 8 commits into from
Mar 7, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1153,8 +1153,9 @@ def refcount_test(test):
def requires_limited_api(test):
try:
import _testcapi
import _testlimitedcapi
except ImportError:
return unittest.skip('needs _testcapi module')(test)
return unittest.skip('needs _testcapi and _testlimitedcapi modules')(test)
return test

def requires_specialization(test):
Expand Down
10 changes: 7 additions & 3 deletions Lib/test/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
import _testcapi
except ImportError:
_testcapi = None
try:
import _testlimitedcapi
except ImportError:
_testlimitedcapi = None
import struct
import collections
import itertools
Expand Down Expand Up @@ -837,12 +841,12 @@ def get_a(x):
@requires_limited_api
def test_vectorcall_limited_incoming(self):
from _testcapi import pyobject_vectorcall
obj = _testcapi.LimitedVectorCallClass()
obj = _testlimitedcapi.LimitedVectorCallClass()
self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called")

@requires_limited_api
def test_vectorcall_limited_outgoing(self):
from _testcapi import call_vectorcall
from _testlimitedcapi import call_vectorcall

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

@requires_limited_api
def test_vectorcall_limited_outgoing_method(self):
from _testcapi import call_vectorcall_method
from _testlimitedcapi import call_vectorcall_method

args_captured = []
kwargs_captured = []
Expand Down
54 changes: 33 additions & 21 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
# Skip this test if the _testcapi module isn't available.
_testcapi = import_helper.import_module('_testcapi')

import _testlimitedcapi
import _testinternalcapi


Expand Down Expand Up @@ -1124,7 +1125,7 @@ def test_heaptype_relative_sizes(self):
# Test subclassing using "relative" basicsize, see PEP 697
def check(extra_base_size, extra_size):
Base, Sub, instance, data_ptr, data_offset, data_size = (
_testcapi.make_sized_heaptypes(
_testlimitedcapi.make_sized_heaptypes(
extra_base_size, -extra_size))

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

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

# The offsets/sizes we calculated should be aligned.
self.assertEqual(data_offset % _testcapi.ALIGNOF_MAX_ALIGN_T, 0)
self.assertEqual(data_size % _testcapi.ALIGNOF_MAX_ALIGN_T, 0)
self.assertEqual(data_offset % _testlimitedcapi.ALIGNOF_MAX_ALIGN_T, 0)
self.assertEqual(data_size % _testlimitedcapi.ALIGNOF_MAX_ALIGN_T, 0)

sizes = sorted({0, 1, 2, 3, 4, 7, 8, 123,
object.__basicsize__,
Expand All @@ -1182,7 +1183,7 @@ def test_heaptype_inherit_itemsize(self):
object.__basicsize__+1})
for extra_size in sizes:
with self.subTest(extra_size=extra_size):
Sub = _testcapi.subclass_var_heaptype(
Sub = _testlimitedcapi.subclass_var_heaptype(
_testcapi.HeapCCollection, -extra_size, 0, 0)
collection = Sub(1, 2, 3)
collection.set_data_to_3s()
Expand All @@ -1196,7 +1197,7 @@ def test_heaptype_invalid_inheritance(self):
with self.assertRaises(SystemError,
msg="Cannot extend variable-size class without "
+ "Py_TPFLAGS_ITEMS_AT_END"):
_testcapi.subclass_heaptype(int, -8, 0)
_testlimitedcapi.subclass_heaptype(int, -8, 0)

def test_heaptype_relative_members(self):
"""Test HeapCCollection subclasses work properly"""
Expand All @@ -1209,7 +1210,7 @@ def test_heaptype_relative_members(self):
for offset in sizes:
with self.subTest(extra_base_size=extra_base_size, extra_size=extra_size, offset=offset):
if offset < extra_size:
Sub = _testcapi.make_heaptype_with_member(
Sub = _testlimitedcapi.make_heaptype_with_member(
extra_base_size, -extra_size, offset, True)
Base = Sub.mro()[1]
instance = Sub()
Expand All @@ -1228,29 +1229,29 @@ def test_heaptype_relative_members(self):
instance.set_memb_relative(0)
else:
with self.assertRaises(SystemError):
Sub = _testcapi.make_heaptype_with_member(
Sub = _testlimitedcapi.make_heaptype_with_member(
extra_base_size, -extra_size, offset, True)
with self.assertRaises(SystemError):
Sub = _testcapi.make_heaptype_with_member(
Sub = _testlimitedcapi.make_heaptype_with_member(
extra_base_size, extra_size, offset, True)
with self.subTest(extra_base_size=extra_base_size, extra_size=extra_size):
with self.assertRaises(SystemError):
Sub = _testcapi.make_heaptype_with_member(
Sub = _testlimitedcapi.make_heaptype_with_member(
extra_base_size, -extra_size, -1, True)

def test_heaptype_relative_members_errors(self):
with self.assertRaisesRegex(
SystemError,
r"With Py_RELATIVE_OFFSET, basicsize must be negative"):
_testcapi.make_heaptype_with_member(0, 1234, 0, True)
_testlimitedcapi.make_heaptype_with_member(0, 1234, 0, True)
with self.assertRaisesRegex(
SystemError, r"Member offset out of range \(0\.\.-basicsize\)"):
_testcapi.make_heaptype_with_member(0, -8, 1234, True)
_testlimitedcapi.make_heaptype_with_member(0, -8, 1234, True)
with self.assertRaisesRegex(
SystemError, r"Member offset out of range \(0\.\.-basicsize\)"):
_testcapi.make_heaptype_with_member(0, -8, -1, True)
_testlimitedcapi.make_heaptype_with_member(0, -8, -1, True)

Sub = _testcapi.make_heaptype_with_member(0, -8, 0, True)
Sub = _testlimitedcapi.make_heaptype_with_member(0, -8, 0, True)
instance = Sub()
with self.assertRaisesRegex(
SystemError, r"PyMember_GetOne used with Py_RELATIVE_OFFSET"):
Expand Down Expand Up @@ -2264,10 +2265,19 @@ def test_gilstate_matches_current(self):
_testcapi.test_current_tstate_matches()


def get_test_funcs(mod, exclude_prefix=None):
funcs = {}
for name in dir(mod):
if not name.startswith('test_'):
continue
if exclude_prefix is not None and name.startswith(exclude_prefix):
continue
funcs[name] = getattr(mod, name)
return funcs


class Test_testcapi(unittest.TestCase):
locals().update((name, getattr(_testcapi, name))
for name in dir(_testcapi)
if name.startswith('test_'))
locals().update(get_test_funcs(_testcapi))

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


class Test_testlimitedcapi(unittest.TestCase):
locals().update(get_test_funcs(_testlimitedcapi))


class Test_testinternalcapi(unittest.TestCase):
locals().update((name, getattr(_testinternalcapi, name))
for name in dir(_testinternalcapi)
if name.startswith('test_')
and not name.startswith('test_lock_'))
locals().update(get_test_funcs(_testinternalcapi,
exclude_prefix='test_lock_'))


@threading_helper.requires_working_threading()
Expand Down
3 changes: 2 additions & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
@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
@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
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/heaptype_relative.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c

Expand Down
20 changes: 0 additions & 20 deletions Modules/_testcapi/clinic/vectorcall_limited.c.h

This file was deleted.

3 changes: 0 additions & 3 deletions Modules/_testcapi/parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,4 @@ int _PyTestCapi_Init_Sys(PyObject *module);
int _PyTestCapi_Init_Hash(PyObject *module);
int _PyTestCapi_Init_Time(PyObject *module);

int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);

#endif // Py_TESTCAPI_PARTS_H
6 changes: 0 additions & 6 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4098,12 +4098,6 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_PyAtomic(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_VectorcallLimited(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_HeaptypeRelative(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_Hash(m) < 0) {
return NULL;
}
Expand Down
36 changes: 36 additions & 0 deletions Modules/_testlimitedcapi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Test the limited C API.
*
* The 'test_*' functions exported by this module are run as part of the
* standard Python regression test, via Lib/test/test_capi.py.
*/

#include "_testlimitedcapi/parts.h"

static PyMethodDef TestMethods[] = {
{NULL, NULL} /* sentinel */
};

static struct PyModuleDef _testlimitedcapimodule = {
PyModuleDef_HEAD_INIT,
.m_name = "_testlimitedcapi",
.m_size = 0,
.m_methods = TestMethods,
};

PyMODINIT_FUNC
PyInit__testlimitedcapi(void)
{
PyObject *mod = PyModule_Create(&_testlimitedcapimodule);
if (mod == NULL) {
return NULL;
}

if (_PyTestCapi_Init_VectorcallLimited(mod) < 0) {
return NULL;
}
if (_PyTestCapi_Init_HeaptypeRelative(mod) < 0) {
return NULL;
}
return mod;
}
20 changes: 20 additions & 0 deletions Modules/_testlimitedcapi/clinic/vectorcall_limited.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
#include "pyconfig.h" // Py_GIL_DISABLED

#ifndef Py_GIL_DISABLED
#define Py_LIMITED_API 0x030c0000 // 3.12
#endif

#include "parts.h"
#include <stddef.h> // max_align_t
#include <string.h> // memset
Expand Down
27 changes: 27 additions & 0 deletions Modules/_testlimitedcapi/parts.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef Py_TESTLIMITEDCAPI_PARTS_H
#define Py_TESTLIMITEDCAPI_PARTS_H

// Always enable assertions
#undef NDEBUG

#include "pyconfig.h" // Py_GIL_DISABLED

// Use the limited C API
#ifndef Py_GIL_DISABLED
# define Py_LIMITED_API 0x030c0000 // 3.12
#endif

// Make sure that the internal C API cannot be used.
#undef Py_BUILD_CORE_MODULE
#undef Py_BUILD_CORE_BUILTIN

#include "Python.h"

#ifdef Py_BUILD_CORE
# error "Py_BUILD_CORE macro must not be defined"
#endif

int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);

#endif // Py_TESTLIMITEDCAPI_PARTS_H