diff options
author | Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> | 2021-08-16 19:18:36 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-16 19:18:36 (GMT) |
commit | d84d2c4985457733602d46dc4ee77290da4e9539 (patch) | |
tree | ef23b495f84cd4777df8a1864b6b726c4ad0a70e | |
parent | 62bd97303eb6d1fb0109e4a57d38c2ba6b0be7ff (diff) | |
download | cpython-d84d2c4985457733602d46dc4ee77290da4e9539.zip cpython-d84d2c4985457733602d46dc4ee77290da4e9539.tar.gz cpython-d84d2c4985457733602d46dc4ee77290da4e9539.tar.bz2 |
bpo-44914: Add tests for some invariants of tp_version_tag (GH-27774)
-rw-r--r-- | Lib/test/test_type_cache.py | 47 | ||||
-rw-r--r-- | Modules/_testcapimodule.c | 18 |
2 files changed, 65 insertions, 0 deletions
diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py new file mode 100644 index 0000000..8502f6b --- /dev/null +++ b/Lib/test/test_type_cache.py @@ -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) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index f338e89..c35eac7 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -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 */ }; |