[Python-checkins] bpo-44914: Add tests for some invariants of tp_version_tag (GH-27774)

markshannon webhook-mailer at python.org
Mon Aug 16 15:19:06 EDT 2021


https://github.com/python/cpython/commit/d84d2c4985457733602d46dc4ee77290da4e9539
commit: d84d2c4985457733602d46dc4ee77290da4e9539
branch: main
author: Ken Jin <28750310+Fidget-Spinner at users.noreply.github.com>
committer: markshannon <mark at hotpy.org>
date: 2021-08-16T20:18:36+01:00
summary:

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

files:
A Lib/test/test_type_cache.py
M Modules/_testcapimodule.c

diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py
new file mode 100644
index 0000000000000..8502f6b0584b0
--- /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
+
+
+ at support.cpython_only
+ at 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 f338e89f426da..c35eac78ebe88 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 */
 };
 



More information about the Python-checkins mailing list