Skip to content

Commit

Permalink
gh-93649: Move _testcapi tests to specific files (#129544)
Browse files Browse the repository at this point in the history
Move many functions from _testcapimodule.c into more specific files
in Modules/_testcapi/.

In moved code:

* Replace get_testerror() with PyExc_AssertionError.
* Replace raiseTestError() with
  PyErr_Format(PyExc_AssertionError, ...).
  • Loading branch information
vstinner authored Feb 1, 2025
1 parent 7d0521d commit 71ae933
Show file tree
Hide file tree
Showing 9 changed files with 651 additions and 646 deletions.
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

0 comments on commit 71ae933

Please sign in to comment.