bpo-44914: Add tests for some invariants of tp_version_tag (GH-27774)

This commit is contained in:
Ken Jin 2021-08-17 03:18:36 +08:00 committed by GitHub
parent 62bd97303e
commit d84d2c4985
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 0 deletions

View file

@ -0,0 +1,47 @@
""" Tests for the internal type cache in CPython. """
import unittest
from test import support
from test.support import import_helper
try:
from sys import _clear_type_cache
except ImportError:
_clear_type_cache = None
# Skip this test if the _testcapi module isn't available.
type_get_version = import_helper.import_module('_testcapi').type_get_version
@support.cpython_only
@unittest.skipIf(_clear_type_cache is None, "requires sys._clear_type_cache")
class TypeCacheTests(unittest.TestCase):
def test_tp_version_tag_unique(self):
"""tp_version_tag should be unique assuming no overflow, even after
clearing type cache.
"""
# Check if global version tag has already overflowed.
Y = type('Y', (), {})
Y.x = 1
Y.x # Force a _PyType_Lookup, populating version tag
y_ver = type_get_version(Y)
# Overflow, or not enough left to conduct the test.
if y_ver == 0 or y_ver > 0xFFFFF000:
self.skipTest("Out of type version tags")
# Note: try to avoid any method lookups within this loop,
# It will affect global version tag.
all_version_tags = []
append_result = all_version_tags.append
assertNotEqual = self.assertNotEqual
for _ in range(30):
_clear_type_cache()
X = type('Y', (), {})
X.x = 1
X.x
tp_version_tag_after = type_get_version(X)
assertNotEqual(tp_version_tag_after, 0, msg="Version overflowed")
append_result(tp_version_tag_after)
self.assertEqual(len(set(all_version_tags)), 30,
msg=f"{all_version_tags} contains non-unique versions")
if __name__ == "__main__":
support.run_unittest(TypeCacheTests)

View file

@ -5575,6 +5575,23 @@ test_fatal_error(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}
// type->tp_version_tag
static PyObject *
type_get_version(PyObject *self, PyObject *type)
{
if (!PyType_Check(type)) {
PyErr_SetString(PyExc_TypeError, "argument must be a type");
return NULL;
}
PyObject *res = PyLong_FromUnsignedLong(
((PyTypeObject *)type)->tp_version_tag);
if (res == NULL) {
assert(PyErr_Occurred());
return NULL;
}
return res;
}
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*);
@ -5854,6 +5871,7 @@ static PyMethodDef TestMethods[] = {
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
{"fatal_error", test_fatal_error, METH_VARARGS,
PyDoc_STR("fatal_error(message, release_gil=False): call Py_FatalError(message)")},
{"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")},
{NULL, NULL} /* sentinel */
};