|
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