| 
1 | 1 | """ Tests for the internal type cache in CPython. """  | 
2 | 2 | import unittest  | 
 | 3 | +import dis  | 
3 | 4 | from test import support  | 
4 | 5 | from test.support import import_helper  | 
5 | 6 | try:  | 
 | 
8 | 9 |     _clear_type_cache = None  | 
9 | 10 | 
 
  | 
10 | 11 | # Skip this test if the _testcapi module isn't available.  | 
11 |  | -type_get_version = import_helper.import_module('_testcapi').type_get_version  | 
12 |  | -type_assign_version = import_helper.import_module('_testcapi').type_assign_version  | 
 | 12 | +_testcapi = import_helper.import_module("_testcapi")  | 
 | 13 | +type_get_version = _testcapi.type_get_version  | 
 | 14 | +type_assign_specific_version_unsafe = _testcapi.type_assign_specific_version_unsafe  | 
 | 15 | +type_assign_version = _testcapi.type_assign_version  | 
 | 16 | +type_modified = _testcapi.type_modified  | 
13 | 17 | 
 
  | 
14 | 18 | 
 
  | 
15 | 19 | @support.cpython_only  | 
@@ -56,6 +60,183 @@ class C:  | 
56 | 60 |         self.assertNotEqual(type_get_version(C), 0)  | 
57 | 61 |         self.assertNotEqual(type_get_version(C), c_ver)  | 
58 | 62 | 
 
  | 
 | 63 | +    def test_type_assign_specific_version(self):  | 
 | 64 | +        """meta-test for type_assign_specific_version_unsafe"""  | 
 | 65 | +        class C:  | 
 | 66 | +            pass  | 
 | 67 | + | 
 | 68 | +        type_assign_version(C)  | 
 | 69 | +        orig_version = type_get_version(C)  | 
 | 70 | +        self.assertNotEqual(orig_version, 0)  | 
 | 71 | + | 
 | 72 | +        type_modified(C)  | 
 | 73 | +        type_assign_specific_version_unsafe(C, orig_version + 5)  | 
 | 74 | +        type_assign_version(C)  # this should do nothing  | 
 | 75 | + | 
 | 76 | +        new_version = type_get_version(C)  | 
 | 77 | +        self.assertEqual(new_version, orig_version + 5)  | 
 | 78 | + | 
 | 79 | +        _clear_type_cache()  | 
 | 80 | + | 
 | 81 | + | 
 | 82 | +@support.cpython_only  | 
 | 83 | +class TypeCacheWithSpecializationTests(unittest.TestCase):  | 
 | 84 | +    def tearDown(self):  | 
 | 85 | +        _clear_type_cache()  | 
 | 86 | + | 
 | 87 | +    def _assign_and_check_valid_version(self, user_type):  | 
 | 88 | +        type_modified(user_type)  | 
 | 89 | +        type_assign_version(user_type)  | 
 | 90 | +        self.assertNotEqual(type_get_version(user_type), 0)  | 
 | 91 | + | 
 | 92 | +    def _assign_and_check_version_0(self, user_type):  | 
 | 93 | +        type_modified(user_type)  | 
 | 94 | +        type_assign_specific_version_unsafe(user_type, 0)  | 
 | 95 | +        self.assertEqual(type_get_version(user_type), 0)  | 
 | 96 | + | 
 | 97 | +    def _all_opnames(self, func):  | 
 | 98 | +        return set(instr.opname for instr in dis.Bytecode(func, adaptive=True))  | 
 | 99 | + | 
 | 100 | +    def _check_specialization(self, func, arg, opname, *, should_specialize):  | 
 | 101 | +        self.assertIn(opname, self._all_opnames(func))  | 
 | 102 | + | 
 | 103 | +        for _ in range(100):  | 
 | 104 | +            func(arg)  | 
 | 105 | + | 
 | 106 | +        if should_specialize:  | 
 | 107 | +            self.assertNotIn(opname, self._all_opnames(func))  | 
 | 108 | +        else:  | 
 | 109 | +            self.assertIn(opname, self._all_opnames(func))  | 
 | 110 | + | 
 | 111 | +    def test_class_load_attr_specialization_user_type(self):  | 
 | 112 | +        class A:  | 
 | 113 | +            def foo(self):  | 
 | 114 | +                pass  | 
 | 115 | + | 
 | 116 | +        self._assign_and_check_valid_version(A)  | 
 | 117 | + | 
 | 118 | +        def load_foo_1(type_):  | 
 | 119 | +            type_.foo  | 
 | 120 | + | 
 | 121 | +        self._check_specialization(load_foo_1, A, "LOAD_ATTR", should_specialize=True)  | 
 | 122 | +        del load_foo_1  | 
 | 123 | + | 
 | 124 | +        self._assign_and_check_version_0(A)  | 
 | 125 | + | 
 | 126 | +        def load_foo_2(type_):  | 
 | 127 | +            return type_.foo  | 
 | 128 | + | 
 | 129 | +        self._check_specialization(load_foo_2, A, "LOAD_ATTR", should_specialize=False)  | 
 | 130 | + | 
 | 131 | +    def test_class_load_attr_specialization_static_type(self):  | 
 | 132 | +        self._assign_and_check_valid_version(str)  | 
 | 133 | +        self._assign_and_check_valid_version(bytes)  | 
 | 134 | + | 
 | 135 | +        def get_capitalize_1(type_):  | 
 | 136 | +            return type_.capitalize  | 
 | 137 | + | 
 | 138 | +        self._check_specialization(get_capitalize_1, str, "LOAD_ATTR", should_specialize=True)  | 
 | 139 | +        self.assertEqual(get_capitalize_1(str)('hello'), 'Hello')  | 
 | 140 | +        self.assertEqual(get_capitalize_1(bytes)(b'hello'), b'Hello')  | 
 | 141 | +        del get_capitalize_1  | 
 | 142 | + | 
 | 143 | +        # Permanently overflow the static type version counter, and force str and bytes  | 
 | 144 | +        # to have tp_version_tag == 0  | 
 | 145 | +        for _ in range(2**16):  | 
 | 146 | +            type_modified(str)  | 
 | 147 | +            type_assign_version(str)  | 
 | 148 | +            type_modified(bytes)  | 
 | 149 | +            type_assign_version(bytes)  | 
 | 150 | + | 
 | 151 | +        self.assertEqual(type_get_version(str), 0)  | 
 | 152 | +        self.assertEqual(type_get_version(bytes), 0)  | 
 | 153 | + | 
 | 154 | +        def get_capitalize_2(type_):  | 
 | 155 | +            return type_.capitalize  | 
 | 156 | + | 
 | 157 | +        self._check_specialization(get_capitalize_2, str, "LOAD_ATTR", should_specialize=False)  | 
 | 158 | +        self.assertEqual(get_capitalize_2(str)('hello'), 'Hello')  | 
 | 159 | +        self.assertEqual(get_capitalize_2(bytes)(b'hello'), b'Hello')  | 
 | 160 | + | 
 | 161 | +    def test_property_load_attr_specialization_user_type(self):  | 
 | 162 | +        class G:  | 
 | 163 | +            @property  | 
 | 164 | +            def x(self):  | 
 | 165 | +                return 9  | 
 | 166 | + | 
 | 167 | +        self._assign_and_check_valid_version(G)  | 
 | 168 | + | 
 | 169 | +        def load_x_1(instance):  | 
 | 170 | +            instance.x  | 
 | 171 | + | 
 | 172 | +        self._check_specialization(load_x_1, G(), "LOAD_ATTR", should_specialize=True)  | 
 | 173 | +        del load_x_1  | 
 | 174 | + | 
 | 175 | +        self._assign_and_check_version_0(G)  | 
 | 176 | + | 
 | 177 | +        def load_x_2(instance):  | 
 | 178 | +            instance.x  | 
 | 179 | + | 
 | 180 | +        self._check_specialization(load_x_2, G(), "LOAD_ATTR", should_specialize=False)  | 
 | 181 | + | 
 | 182 | +    def test_store_attr_specialization_user_type(self):  | 
 | 183 | +        class B:  | 
 | 184 | +            __slots__ = ("bar",)  | 
 | 185 | + | 
 | 186 | +        self._assign_and_check_valid_version(B)  | 
 | 187 | + | 
 | 188 | +        def store_bar_1(type_):  | 
 | 189 | +            type_.bar = 10  | 
 | 190 | + | 
 | 191 | +        self._check_specialization(store_bar_1, B(), "STORE_ATTR", should_specialize=True)  | 
 | 192 | +        del store_bar_1  | 
 | 193 | + | 
 | 194 | +        self._assign_and_check_version_0(B)  | 
 | 195 | + | 
 | 196 | +        def store_bar_2(type_):  | 
 | 197 | +            type_.bar = 10  | 
 | 198 | + | 
 | 199 | +        self._check_specialization(store_bar_2, B(), "STORE_ATTR", should_specialize=False)  | 
 | 200 | + | 
 | 201 | +    def test_class_call_specialization_user_type(self):  | 
 | 202 | +        class F:  | 
 | 203 | +            def __init__(self):  | 
 | 204 | +                pass  | 
 | 205 | + | 
 | 206 | +        self._assign_and_check_valid_version(F)  | 
 | 207 | + | 
 | 208 | +        def call_class_1(type_):  | 
 | 209 | +            type_()  | 
 | 210 | + | 
 | 211 | +        self._check_specialization(call_class_1, F, "CALL", should_specialize=True)  | 
 | 212 | +        del call_class_1  | 
 | 213 | + | 
 | 214 | +        self._assign_and_check_version_0(F)  | 
 | 215 | + | 
 | 216 | +        def call_class_2(type_):  | 
 | 217 | +            type_()  | 
 | 218 | + | 
 | 219 | +        self._check_specialization(call_class_2, F, "CALL", should_specialize=False)  | 
 | 220 | + | 
 | 221 | +    def test_to_bool_specialization_user_type(self):  | 
 | 222 | +        class H:  | 
 | 223 | +            pass  | 
 | 224 | + | 
 | 225 | +        self._assign_and_check_valid_version(H)  | 
 | 226 | + | 
 | 227 | +        def to_bool_1(instance):  | 
 | 228 | +            not instance  | 
 | 229 | + | 
 | 230 | +        self._check_specialization(to_bool_1, H(), "TO_BOOL", should_specialize=True)  | 
 | 231 | +        del to_bool_1  | 
 | 232 | + | 
 | 233 | +        self._assign_and_check_version_0(H)  | 
 | 234 | + | 
 | 235 | +        def to_bool_2(instance):  | 
 | 236 | +            not instance  | 
 | 237 | + | 
 | 238 | +        self._check_specialization(to_bool_2, H(), "TO_BOOL", should_specialize=False)  | 
 | 239 | + | 
59 | 240 | 
 
  | 
60 | 241 | if __name__ == "__main__":  | 
61 | 242 |     unittest.main()  | 
0 commit comments