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-93649: Move _testcapi tests to specific files #129544

Merged
merged 2 commits into from
Feb 1, 2025
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
36 changes: 0 additions & 36 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,42 +403,6 @@ def test_buildvalue_ints(self):
def test_buildvalue_N(self):
_testcapi.test_buildvalue_N()

def check_negative_refcount(self, code):
# bpo-35059: Check that Py_DECREF() reports the correct filename
# when calling _Py_NegativeRefcount() to abort Python.
code = textwrap.dedent(code)
rc, out, err = assert_python_failure('-c', code)
self.assertRegex(err,
br'_testcapimodule\.c:[0-9]+: '
br'_Py_NegativeRefcount: Assertion failed: '
br'object has negative ref count')

@unittest.skipUnless(hasattr(_testcapi, 'negative_refcount'),
'need _testcapi.negative_refcount()')
def test_negative_refcount(self):
code = """
import _testcapi
from test import support

with support.SuppressCrashReport():
_testcapi.negative_refcount()
"""
self.check_negative_refcount(code)

@unittest.skipUnless(hasattr(_testcapi, 'decref_freed_object'),
'need _testcapi.decref_freed_object()')
@support.skip_if_sanitizer("use after free on purpose",
address=True, memory=True, ub=True)
def test_decref_freed_object(self):
code = """
import _testcapi
from test import support

with support.SuppressCrashReport():
_testcapi.decref_freed_object()
"""
self.check_negative_refcount(code)

def test_trashcan_subclass(self):
# bpo-35983: Check that the trashcan mechanism for "list" is NOT
# activated when its tp_dealloc is being called by a subclass
Expand Down
40 changes: 40 additions & 0 deletions Lib/test/test_capi/test_object.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import enum
import textwrap
import unittest
from test import support
from test.support import import_helper
from test.support import os_helper
from test.support import threading_helper
from test.support.script_helper import assert_python_failure


_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
_testcapi = import_helper.import_module('_testcapi')
Expand Down Expand Up @@ -170,5 +173,42 @@ def silly_func(obj):
self.assertTrue(_testinternalcapi.has_deferred_refcount(silly_list))


class CAPITest(unittest.TestCase):
def check_negative_refcount(self, code):
# bpo-35059: Check that Py_DECREF() reports the correct filename
# when calling _Py_NegativeRefcount() to abort Python.
code = textwrap.dedent(code)
rc, out, err = assert_python_failure('-c', code)
self.assertRegex(err,
br'object\.c:[0-9]+: '
br'_Py_NegativeRefcount: Assertion failed: '
br'object has negative ref count')

@unittest.skipUnless(hasattr(_testcapi, 'negative_refcount'),
'need _testcapi.negative_refcount()')
def test_negative_refcount(self):
code = """
import _testcapi
from test import support

with support.SuppressCrashReport():
_testcapi.negative_refcount()
"""
self.check_negative_refcount(code)

@unittest.skipUnless(hasattr(_testcapi, 'decref_freed_object'),
'need _testcapi.decref_freed_object()')
@support.skip_if_sanitizer("use after free on purpose",
address=True, memory=True, ub=True)
def test_decref_freed_object(self):
code = """
import _testcapi
from test import support

with support.SuppressCrashReport():
_testcapi.decref_freed_object()
"""
self.check_negative_refcount(code)

if __name__ == "__main__":
unittest.main()
78 changes: 78 additions & 0 deletions Modules/_testcapi/dict.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,83 @@ dict_popstring_null(PyObject *self, PyObject *args)
RETURN_INT(PyDict_PopString(dict, key, NULL));
}


static int
test_dict_inner(PyObject *self, int count)
{
Py_ssize_t pos = 0, iterations = 0;
int i;
PyObject *dict = PyDict_New();
PyObject *v, *k;

if (dict == NULL)
return -1;

for (i = 0; i < count; i++) {
v = PyLong_FromLong(i);
if (v == NULL) {
goto error;
}
if (PyDict_SetItem(dict, v, v) < 0) {
Py_DECREF(v);
goto error;
}
Py_DECREF(v);
}

k = v = UNINITIALIZED_PTR;
while (PyDict_Next(dict, &pos, &k, &v)) {
PyObject *o;
iterations++;

assert(k != UNINITIALIZED_PTR);
assert(v != UNINITIALIZED_PTR);
i = PyLong_AS_LONG(v) + 1;
o = PyLong_FromLong(i);
if (o == NULL) {
goto error;
}
if (PyDict_SetItem(dict, k, o) < 0) {
Py_DECREF(o);
goto error;
}
Py_DECREF(o);
k = v = UNINITIALIZED_PTR;
}
assert(k == UNINITIALIZED_PTR);
assert(v == UNINITIALIZED_PTR);

Py_DECREF(dict);

if (iterations != count) {
PyErr_SetString(
PyExc_AssertionError,
"test_dict_iteration: dict iteration went wrong ");
return -1;
} else {
return 0;
}
error:
Py_DECREF(dict);
return -1;
}


static PyObject*
test_dict_iteration(PyObject* self, PyObject *Py_UNUSED(ignored))
{
int i;

for (i = 0; i < 200; i++) {
if (test_dict_inner(self, i) < 0) {
return NULL;
}
}

Py_RETURN_NONE;
}


static PyMethodDef test_methods[] = {
{"dict_containsstring", dict_containsstring, METH_VARARGS},
{"dict_getitemref", dict_getitemref, METH_VARARGS},
Expand All @@ -191,6 +268,7 @@ static PyMethodDef test_methods[] = {
{"dict_pop_null", dict_pop_null, METH_VARARGS},
{"dict_popstring", dict_popstring, METH_VARARGS},
{"dict_popstring_null", dict_popstring_null, METH_VARARGS},
{"test_dict_iteration", test_dict_iteration, METH_NOARGS},
{NULL},
};

Expand Down
59 changes: 59 additions & 0 deletions Modules/_testcapi/float.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,68 @@ _testcapi_float_unpack_impl(PyObject *module, const char *data,
return PyFloat_FromDouble(d);
}


/* Test PyOS_string_to_double. */
static PyObject *
test_string_to_double(PyObject *self, PyObject *Py_UNUSED(ignored))
{
double result;
const char *msg;

#define CHECK_STRING(STR, expected) \
do { \
result = PyOS_string_to_double(STR, NULL, NULL); \
if (result == -1.0 && PyErr_Occurred()) { \
return NULL; \
} \
if (result != (double)expected) { \
msg = "conversion of " STR " to float failed"; \
goto fail; \
} \
} while (0)

#define CHECK_INVALID(STR) \
do { \
result = PyOS_string_to_double(STR, NULL, NULL); \
if (result == -1.0 && PyErr_Occurred()) { \
if (PyErr_ExceptionMatches(PyExc_ValueError)) { \
PyErr_Clear(); \
} \
else { \
return NULL; \
} \
} \
else { \
msg = "conversion of " STR " didn't raise ValueError"; \
goto fail; \
} \
} while (0)

CHECK_STRING("0.1", 0.1);
CHECK_STRING("1.234", 1.234);
CHECK_STRING("-1.35", -1.35);
CHECK_STRING(".1e01", 1.0);
CHECK_STRING("2.e-2", 0.02);

CHECK_INVALID(" 0.1");
CHECK_INVALID("\t\n-3");
CHECK_INVALID(".123 ");
CHECK_INVALID("3\n");
CHECK_INVALID("123abc");

Py_RETURN_NONE;
fail:
PyErr_Format(PyExc_AssertionError, "test_string_to_double: %s", msg);
return NULL;
#undef CHECK_STRING
#undef CHECK_INVALID
}


static PyMethodDef test_methods[] = {
_TESTCAPI_FLOAT_PACK_METHODDEF
_TESTCAPI_FLOAT_UNPACK_METHODDEF
{"test_string_to_double", test_string_to_double, METH_NOARGS},
{NULL},
};

Expand Down
51 changes: 45 additions & 6 deletions Modules/_testcapi/list.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,61 @@ list_extend(PyObject* Py_UNUSED(module), PyObject *args)
}


static PyObject*
test_list_api(PyObject *self, PyObject *Py_UNUSED(ignored))
{
PyObject* list;
int i;

/* SF bug 132008: PyList_Reverse segfaults */
#define NLIST 30
list = PyList_New(NLIST);
if (list == (PyObject*)NULL)
return (PyObject*)NULL;
/* list = range(NLIST) */
for (i = 0; i < NLIST; ++i) {
PyObject* anint = PyLong_FromLong(i);
if (anint == (PyObject*)NULL) {
Py_DECREF(list);
return (PyObject*)NULL;
}
PyList_SET_ITEM(list, i, anint);
}
/* list.reverse(), via PyList_Reverse() */
i = PyList_Reverse(list); /* should not blow up! */
if (i != 0) {
Py_DECREF(list);
return (PyObject*)NULL;
}
/* Check that list == range(29, -1, -1) now */
for (i = 0; i < NLIST; ++i) {
PyObject* anint = PyList_GET_ITEM(list, i);
if (PyLong_AS_LONG(anint) != NLIST-1-i) {
PyErr_SetString(PyExc_AssertionError,
"test_list_api: reverse screwed up");
Py_DECREF(list);
return (PyObject*)NULL;
}
}
Py_DECREF(list);
#undef NLIST

Py_RETURN_NONE;
}


static PyMethodDef test_methods[] = {
{"list_get_size", list_get_size, METH_O},
{"list_get_item", list_get_item, METH_VARARGS},
{"list_set_item", list_set_item, METH_VARARGS},
{"list_clear", list_clear, METH_O},
{"list_extend", list_extend, METH_VARARGS},

{"test_list_api", test_list_api, METH_NOARGS},
{NULL},
};

int
_PyTestCapi_Init_List(PyObject *m)
{
if (PyModule_AddFunctions(m, test_methods) < 0) {
return -1;
}

return 0;
return PyModule_AddFunctions(m, test_methods);
}
Loading
Loading