-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathtest_memo.py
More file actions
executable file
·305 lines (222 loc) · 8.33 KB
/
Copy pathtest_memo.py
File metadata and controls
executable file
·305 lines (222 loc) · 8.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# SPDX-License-Identifier: LGPL-2.1-or-later
# Copyright (c) 2025-2026 Wojciech Sebastian Kozlowski
import pytest
import dbzero as db0
from .memo_test_types import MemoTestClass, TriColor, MemoAnyAttrs
from abc import ABC, abstractmethod
from dataclasses import dataclass
class RegularPyClass:
pass
@db0.memo
class MemoTestEQClass:
def __init__(self, value):
self.value = value
def __eq__(self, value):
if isinstance(value, MemoTestEQClass):
return self.value == value.value
@db0.memo
class MemoClassWithSetter:
def __init__(self, value):
self._value = value
@property
def value(self):
return self._value
@value.setter
def value(self, new_value):
self._value = new_value + 1
@db0.memo
class MemoDerivedClassNoInit(MemoTestClass):
pass
@db0.memo
class MemoAbstractBase(ABC):
@abstractmethod
def describe(self):
pass
@db0.memo
class MemoConcreteDerived(MemoAbstractBase):
def __init__(self, value):
self.value = value
def describe(self):
return f"value={self.value}"
@db0.memo
class MemoManualAbstractMethods:
__abstractmethods__ = frozenset({"describe"})
def __init__(self, value):
self.value = value
def describe(self):
return f"value={self.value}"
def test_memo_rejects_removed_option():
removed_option = {"immut" + "able": True}
with pytest.raises(TypeError):
@db0.memo(**removed_option)
class RemovedImmutableMemoOption:
pass
def test_memo_rejects_removed_cache_option():
removed_option = {"no_" + "cache": True}
with pytest.raises(TypeError):
@db0.memo(**removed_option)
class RemovedCacheMemoOption:
pass
def test_memo_respects_abc_abstract_instantiation_check(db0_fixture):
with pytest.raises(TypeError, match="abstract class"):
MemoAbstractBase()
def test_memo_allows_concrete_abc_derived_class(db0_fixture):
obj = MemoConcreteDerived(123)
assert obj.describe() == "value=123"
def test_memo_manual_abstractmethods_without_abc_does_not_block_instantiation(db0_fixture):
obj = MemoManualAbstractMethods(123)
assert obj.describe() == "value=123"
def test_memo_is_instance_operator(db0_fixture):
obj_1 = MemoTestClass(999)
obj_2 = db0.fetch(db0.uuid(obj_1))
assert obj_1 is obj_2
def test_memo_default_eq_operator(db0_fixture):
obj_1 = MemoTestClass(999)
obj_2 = db0.fetch(db0.uuid(obj_1))
# by default, the __eq__ operator fallbacks to the identity operator
assert obj_1 == obj_2
def test_memo_overloaded_eq_operator(db0_fixture):
obj_1 = MemoTestClass(999)
obj_2 = MemoTestClass(999)
obj_3 = MemoTestEQClass(999)
obj_4 = MemoTestEQClass(999)
assert obj_1 != obj_2
assert obj_3 == obj_4
assert obj_1 != obj_3
def test_is_memo(db0_fixture):
assert db0.is_memo(MemoTestClass(1)) == True
assert db0.is_memo(TriColor.RED) == False
assert db0.is_memo(1) == False
assert db0.is_memo("asd") == False
assert db0.is_memo([1, 2, 3]) == False
assert db0.is_memo({"a": 1, "b": 2}) == False
assert db0.is_memo(db0.list([1,2,3])) == False
def test_is_memo_for_types(db0_fixture):
assert db0.is_memo(MemoTestClass) == True
assert db0.is_memo(RegularPyClass) == False
@pytest.mark.skip(reason="Skipping due to unresolved issue #237")
def test_memo_property_decorator_issue1(db0_fixture):
"""
Issue: https://github.com/wskozlowski/dbzero/issues/237
"""
test_obj = MemoClassWithSetter(1)
assert test_obj.value == 1
test_obj.value = 2
assert test_obj.value == 3
del test_obj.value
assert not hasattr(test_obj, "_value")
@pytest.mark.parametrize("db0_slab_size", [{"slab_size": 1 << 20}], indirect=True)
def test_memo_gc_issue1(db0_slab_size):
"""
Issue: this test was causing a segfault on gc.collect() in Python 3.13, but not on earlier versions
Resolution:
"""
import gc
from .data_for_tests import test_strings
count = 0
for _ in range(50000):
str = test_strings[count % len(test_strings)]
obj = MemoTestClass(str)
del obj
count += 1
gc.collect()
def test_type_as_member(db0_fixture):
obj_1 = MemoTestClass(RegularPyClass)
assert obj_1.value is RegularPyClass
obj_1 = MemoTestClass(MemoTestEQClass)
assert obj_1.value is MemoTestEQClass
def test_unreference_type_member(db0_fixture):
ref_cnt_1 = db0.getrefcount(MemoTestEQClass)
obj_1 = MemoTestClass(MemoTestEQClass)
ref_cnt_2 = db0.getrefcount(MemoTestEQClass)
assert ref_cnt_2 > ref_cnt_1
obj_1.value = None
assert db0.getrefcount(MemoTestEQClass) < ref_cnt_2
def test_memo_field_deletion(db0_fixture):
obj_1 = MemoTestClass(1)
del obj_1.value
assert not hasattr(obj_1, "value")
@db0.memo
class MemoClassWithDel:
def __init__(self, value):
self.value = value
# create value_2 and delete it before object is initialized
self.value_2 = value + 1
del self.value_2
def test_memo_field_deletion_in_pre_init(db0_fixture):
obj_1 = MemoClassWithDel(1)
with pytest.raises(AttributeError):
assert obj_1.value_2 is None
def test_memo_type_as_memo_member(db0_fixture):
obj_1 = MemoTestClass(MemoTestEQClass)
obj_2 = MemoTestClass(MemoClassWithDel)
assert obj_1.value is MemoTestEQClass
assert obj_2.value is not MemoTestEQClass
obj_x = obj_1.value(1)
assert obj_x.value == 1
assert type(obj_x) is MemoTestEQClass
@db0.memo(id="/Division By Zero/project/tests/MemoTypeIdClass")
class MemoTypeIdClass:
def __init__(self):
self.value = 123
def test_memo_class_with_typeid(db0_fixture):
obj_1 = MemoTypeIdClass()
assert obj_1.value == 123
@db0.memo
class MemoConditionalMember:
def __init__(self, cond_value):
if cond_value > 10:
self.cond_value = None
return
self.cond_value = cond_value
def test_memo_unset_value_defaults_to_none(db0_fixture):
obj_1 = MemoConditionalMember(15)
assert obj_1.cond_value is None
def test_reassign_deleted_member(db0_fixture):
obj_1 = MemoTestClass(1)
del obj_1.value
assert not hasattr(obj_1, "value")
# assign after deletion
obj_1.value = False
assert obj_1.value == False
# delete again
del obj_1.value
assert not hasattr(obj_1, "value")
# assign with full-length value
obj_1.value = "Full Length Value"
assert obj_1.value == "Full Length Value"
def test_selective_assign_members(db0_fixture):
obj_1 = MemoAnyAttrs(f1 = 0, f2 = 1, f3 = 2)
# NOTE: additional slot assigned to pack-2 values (initially unused)
assert len(db0.describe(obj_1)["field_layout"]["pos_vt"]) == 4
# assigned at first use
obj_1.f2 = False
assert len(db0.describe(obj_1)["field_layout"]["pos_vt"]) == 4
_ = MemoAnyAttrs(f4 = False, f5 = 1, f6 = 2, f7 = 3.5, f8 = 1, f9 = 2)
obj_3 = MemoAnyAttrs(f7 = 3.5, f8 = 1, f9 = 2)
assert len(db0.describe(obj_3)["field_layout"]["pos_vt"]) == 3
# NOTE: pack-2 slot included on condition fill-rate is at least 50%
obj_4 = MemoAnyAttrs(f4 = False, f5 = 1, f6 = 2, f7 = 3.5)
assert len(db0.describe(obj_4)["field_layout"]["pos_vt"]) >= 4
obj_5 = MemoAnyAttrs(f4 = False, f6 = 1, f9 = 11)
# too spread apart, only some fraction of slots to be allocated to pos-vt
assert len(db0.describe(obj_5)["field_layout"]["pos_vt"]) < 3
def test_memo_setattr(db0_fixture):
obj_1 = MemoTestClass(1)
obj_1.__setattr__("value", 10)
assert obj_1.value == 10
obj_1.__setattr__("new_field", 20)
assert obj_1.new_field == 20
def test_memo_derived_no_init(db0_fixture):
obj_1 = MemoDerivedClassNoInit(123)
assert obj_1.value == 123
class UnsupportedType:
def __init__(self, data):
self.data = data
def test_unsupported_types_cannot_be_stored_as_regular_members(db0_fixture):
"""Unless explcit opt-in for unsupported non-peristent types"""
with pytest.raises(Exception):
_ = MemoTestClass(UnsupportedType("data"))
def test_memo_class_names_are_not_mangled(db0_fixture):
obj_1 = MemoTestClass(1)
assert type(obj_1).__name__ == "MemoTestClass"