1 | n/a | import gc |
---|
2 | n/a | import sys |
---|
3 | n/a | import unittest |
---|
4 | n/a | import collections |
---|
5 | n/a | import weakref |
---|
6 | n/a | import operator |
---|
7 | n/a | import contextlib |
---|
8 | n/a | import copy |
---|
9 | n/a | import time |
---|
10 | n/a | |
---|
11 | n/a | from test import support |
---|
12 | n/a | from test.support import script_helper |
---|
13 | n/a | |
---|
14 | n/a | # Used in ReferencesTestCase.test_ref_created_during_del() . |
---|
15 | n/a | ref_from_del = None |
---|
16 | n/a | |
---|
17 | n/a | # Used by FinalizeTestCase as a global that may be replaced by None |
---|
18 | n/a | # when the interpreter shuts down. |
---|
19 | n/a | _global_var = 'foobar' |
---|
20 | n/a | |
---|
21 | n/a | class C: |
---|
22 | n/a | def method(self): |
---|
23 | n/a | pass |
---|
24 | n/a | |
---|
25 | n/a | |
---|
26 | n/a | class Callable: |
---|
27 | n/a | bar = None |
---|
28 | n/a | |
---|
29 | n/a | def __call__(self, x): |
---|
30 | n/a | self.bar = x |
---|
31 | n/a | |
---|
32 | n/a | |
---|
33 | n/a | def create_function(): |
---|
34 | n/a | def f(): pass |
---|
35 | n/a | return f |
---|
36 | n/a | |
---|
37 | n/a | def create_bound_method(): |
---|
38 | n/a | return C().method |
---|
39 | n/a | |
---|
40 | n/a | |
---|
41 | n/a | class Object: |
---|
42 | n/a | def __init__(self, arg): |
---|
43 | n/a | self.arg = arg |
---|
44 | n/a | def __repr__(self): |
---|
45 | n/a | return "<Object %r>" % self.arg |
---|
46 | n/a | def __eq__(self, other): |
---|
47 | n/a | if isinstance(other, Object): |
---|
48 | n/a | return self.arg == other.arg |
---|
49 | n/a | return NotImplemented |
---|
50 | n/a | def __lt__(self, other): |
---|
51 | n/a | if isinstance(other, Object): |
---|
52 | n/a | return self.arg < other.arg |
---|
53 | n/a | return NotImplemented |
---|
54 | n/a | def __hash__(self): |
---|
55 | n/a | return hash(self.arg) |
---|
56 | n/a | def some_method(self): |
---|
57 | n/a | return 4 |
---|
58 | n/a | def other_method(self): |
---|
59 | n/a | return 5 |
---|
60 | n/a | |
---|
61 | n/a | |
---|
62 | n/a | class RefCycle: |
---|
63 | n/a | def __init__(self): |
---|
64 | n/a | self.cycle = self |
---|
65 | n/a | |
---|
66 | n/a | |
---|
67 | n/a | class TestBase(unittest.TestCase): |
---|
68 | n/a | |
---|
69 | n/a | def setUp(self): |
---|
70 | n/a | self.cbcalled = 0 |
---|
71 | n/a | |
---|
72 | n/a | def callback(self, ref): |
---|
73 | n/a | self.cbcalled += 1 |
---|
74 | n/a | |
---|
75 | n/a | |
---|
76 | n/a | @contextlib.contextmanager |
---|
77 | n/a | def collect_in_thread(period=0.0001): |
---|
78 | n/a | """ |
---|
79 | n/a | Ensure GC collections happen in a different thread, at a high frequency. |
---|
80 | n/a | """ |
---|
81 | n/a | threading = support.import_module('threading') |
---|
82 | n/a | please_stop = False |
---|
83 | n/a | |
---|
84 | n/a | def collect(): |
---|
85 | n/a | while not please_stop: |
---|
86 | n/a | time.sleep(period) |
---|
87 | n/a | gc.collect() |
---|
88 | n/a | |
---|
89 | n/a | with support.disable_gc(): |
---|
90 | n/a | t = threading.Thread(target=collect) |
---|
91 | n/a | t.start() |
---|
92 | n/a | try: |
---|
93 | n/a | yield |
---|
94 | n/a | finally: |
---|
95 | n/a | please_stop = True |
---|
96 | n/a | t.join() |
---|
97 | n/a | |
---|
98 | n/a | |
---|
99 | n/a | class ReferencesTestCase(TestBase): |
---|
100 | n/a | |
---|
101 | n/a | def test_basic_ref(self): |
---|
102 | n/a | self.check_basic_ref(C) |
---|
103 | n/a | self.check_basic_ref(create_function) |
---|
104 | n/a | self.check_basic_ref(create_bound_method) |
---|
105 | n/a | |
---|
106 | n/a | # Just make sure the tp_repr handler doesn't raise an exception. |
---|
107 | n/a | # Live reference: |
---|
108 | n/a | o = C() |
---|
109 | n/a | wr = weakref.ref(o) |
---|
110 | n/a | repr(wr) |
---|
111 | n/a | # Dead reference: |
---|
112 | n/a | del o |
---|
113 | n/a | repr(wr) |
---|
114 | n/a | |
---|
115 | n/a | def test_basic_callback(self): |
---|
116 | n/a | self.check_basic_callback(C) |
---|
117 | n/a | self.check_basic_callback(create_function) |
---|
118 | n/a | self.check_basic_callback(create_bound_method) |
---|
119 | n/a | |
---|
120 | n/a | @support.cpython_only |
---|
121 | n/a | def test_cfunction(self): |
---|
122 | n/a | import _testcapi |
---|
123 | n/a | create_cfunction = _testcapi.create_cfunction |
---|
124 | n/a | f = create_cfunction() |
---|
125 | n/a | wr = weakref.ref(f) |
---|
126 | n/a | self.assertIs(wr(), f) |
---|
127 | n/a | del f |
---|
128 | n/a | self.assertIsNone(wr()) |
---|
129 | n/a | self.check_basic_ref(create_cfunction) |
---|
130 | n/a | self.check_basic_callback(create_cfunction) |
---|
131 | n/a | |
---|
132 | n/a | def test_multiple_callbacks(self): |
---|
133 | n/a | o = C() |
---|
134 | n/a | ref1 = weakref.ref(o, self.callback) |
---|
135 | n/a | ref2 = weakref.ref(o, self.callback) |
---|
136 | n/a | del o |
---|
137 | n/a | self.assertIsNone(ref1(), "expected reference to be invalidated") |
---|
138 | n/a | self.assertIsNone(ref2(), "expected reference to be invalidated") |
---|
139 | n/a | self.assertEqual(self.cbcalled, 2, |
---|
140 | n/a | "callback not called the right number of times") |
---|
141 | n/a | |
---|
142 | n/a | def test_multiple_selfref_callbacks(self): |
---|
143 | n/a | # Make sure all references are invalidated before callbacks are called |
---|
144 | n/a | # |
---|
145 | n/a | # What's important here is that we're using the first |
---|
146 | n/a | # reference in the callback invoked on the second reference |
---|
147 | n/a | # (the most recently created ref is cleaned up first). This |
---|
148 | n/a | # tests that all references to the object are invalidated |
---|
149 | n/a | # before any of the callbacks are invoked, so that we only |
---|
150 | n/a | # have one invocation of _weakref.c:cleanup_helper() active |
---|
151 | n/a | # for a particular object at a time. |
---|
152 | n/a | # |
---|
153 | n/a | def callback(object, self=self): |
---|
154 | n/a | self.ref() |
---|
155 | n/a | c = C() |
---|
156 | n/a | self.ref = weakref.ref(c, callback) |
---|
157 | n/a | ref1 = weakref.ref(c, callback) |
---|
158 | n/a | del c |
---|
159 | n/a | |
---|
160 | n/a | def test_constructor_kwargs(self): |
---|
161 | n/a | c = C() |
---|
162 | n/a | self.assertRaises(TypeError, weakref.ref, c, callback=None) |
---|
163 | n/a | |
---|
164 | n/a | def test_proxy_ref(self): |
---|
165 | n/a | o = C() |
---|
166 | n/a | o.bar = 1 |
---|
167 | n/a | ref1 = weakref.proxy(o, self.callback) |
---|
168 | n/a | ref2 = weakref.proxy(o, self.callback) |
---|
169 | n/a | del o |
---|
170 | n/a | |
---|
171 | n/a | def check(proxy): |
---|
172 | n/a | proxy.bar |
---|
173 | n/a | |
---|
174 | n/a | self.assertRaises(ReferenceError, check, ref1) |
---|
175 | n/a | self.assertRaises(ReferenceError, check, ref2) |
---|
176 | n/a | self.assertRaises(ReferenceError, bool, weakref.proxy(C())) |
---|
177 | n/a | self.assertEqual(self.cbcalled, 2) |
---|
178 | n/a | |
---|
179 | n/a | def check_basic_ref(self, factory): |
---|
180 | n/a | o = factory() |
---|
181 | n/a | ref = weakref.ref(o) |
---|
182 | n/a | self.assertIsNotNone(ref(), |
---|
183 | n/a | "weak reference to live object should be live") |
---|
184 | n/a | o2 = ref() |
---|
185 | n/a | self.assertIs(o, o2, |
---|
186 | n/a | "<ref>() should return original object if live") |
---|
187 | n/a | |
---|
188 | n/a | def check_basic_callback(self, factory): |
---|
189 | n/a | self.cbcalled = 0 |
---|
190 | n/a | o = factory() |
---|
191 | n/a | ref = weakref.ref(o, self.callback) |
---|
192 | n/a | del o |
---|
193 | n/a | self.assertEqual(self.cbcalled, 1, |
---|
194 | n/a | "callback did not properly set 'cbcalled'") |
---|
195 | n/a | self.assertIsNone(ref(), |
---|
196 | n/a | "ref2 should be dead after deleting object reference") |
---|
197 | n/a | |
---|
198 | n/a | def test_ref_reuse(self): |
---|
199 | n/a | o = C() |
---|
200 | n/a | ref1 = weakref.ref(o) |
---|
201 | n/a | # create a proxy to make sure that there's an intervening creation |
---|
202 | n/a | # between these two; it should make no difference |
---|
203 | n/a | proxy = weakref.proxy(o) |
---|
204 | n/a | ref2 = weakref.ref(o) |
---|
205 | n/a | self.assertIs(ref1, ref2, |
---|
206 | n/a | "reference object w/out callback should be re-used") |
---|
207 | n/a | |
---|
208 | n/a | o = C() |
---|
209 | n/a | proxy = weakref.proxy(o) |
---|
210 | n/a | ref1 = weakref.ref(o) |
---|
211 | n/a | ref2 = weakref.ref(o) |
---|
212 | n/a | self.assertIs(ref1, ref2, |
---|
213 | n/a | "reference object w/out callback should be re-used") |
---|
214 | n/a | self.assertEqual(weakref.getweakrefcount(o), 2, |
---|
215 | n/a | "wrong weak ref count for object") |
---|
216 | n/a | del proxy |
---|
217 | n/a | self.assertEqual(weakref.getweakrefcount(o), 1, |
---|
218 | n/a | "wrong weak ref count for object after deleting proxy") |
---|
219 | n/a | |
---|
220 | n/a | def test_proxy_reuse(self): |
---|
221 | n/a | o = C() |
---|
222 | n/a | proxy1 = weakref.proxy(o) |
---|
223 | n/a | ref = weakref.ref(o) |
---|
224 | n/a | proxy2 = weakref.proxy(o) |
---|
225 | n/a | self.assertIs(proxy1, proxy2, |
---|
226 | n/a | "proxy object w/out callback should have been re-used") |
---|
227 | n/a | |
---|
228 | n/a | def test_basic_proxy(self): |
---|
229 | n/a | o = C() |
---|
230 | n/a | self.check_proxy(o, weakref.proxy(o)) |
---|
231 | n/a | |
---|
232 | n/a | L = collections.UserList() |
---|
233 | n/a | p = weakref.proxy(L) |
---|
234 | n/a | self.assertFalse(p, "proxy for empty UserList should be false") |
---|
235 | n/a | p.append(12) |
---|
236 | n/a | self.assertEqual(len(L), 1) |
---|
237 | n/a | self.assertTrue(p, "proxy for non-empty UserList should be true") |
---|
238 | n/a | p[:] = [2, 3] |
---|
239 | n/a | self.assertEqual(len(L), 2) |
---|
240 | n/a | self.assertEqual(len(p), 2) |
---|
241 | n/a | self.assertIn(3, p, "proxy didn't support __contains__() properly") |
---|
242 | n/a | p[1] = 5 |
---|
243 | n/a | self.assertEqual(L[1], 5) |
---|
244 | n/a | self.assertEqual(p[1], 5) |
---|
245 | n/a | L2 = collections.UserList(L) |
---|
246 | n/a | p2 = weakref.proxy(L2) |
---|
247 | n/a | self.assertEqual(p, p2) |
---|
248 | n/a | ## self.assertEqual(repr(L2), repr(p2)) |
---|
249 | n/a | L3 = collections.UserList(range(10)) |
---|
250 | n/a | p3 = weakref.proxy(L3) |
---|
251 | n/a | self.assertEqual(L3[:], p3[:]) |
---|
252 | n/a | self.assertEqual(L3[5:], p3[5:]) |
---|
253 | n/a | self.assertEqual(L3[:5], p3[:5]) |
---|
254 | n/a | self.assertEqual(L3[2:5], p3[2:5]) |
---|
255 | n/a | |
---|
256 | n/a | def test_proxy_unicode(self): |
---|
257 | n/a | # See bug 5037 |
---|
258 | n/a | class C(object): |
---|
259 | n/a | def __str__(self): |
---|
260 | n/a | return "string" |
---|
261 | n/a | def __bytes__(self): |
---|
262 | n/a | return b"bytes" |
---|
263 | n/a | instance = C() |
---|
264 | n/a | self.assertIn("__bytes__", dir(weakref.proxy(instance))) |
---|
265 | n/a | self.assertEqual(bytes(weakref.proxy(instance)), b"bytes") |
---|
266 | n/a | |
---|
267 | n/a | def test_proxy_index(self): |
---|
268 | n/a | class C: |
---|
269 | n/a | def __index__(self): |
---|
270 | n/a | return 10 |
---|
271 | n/a | o = C() |
---|
272 | n/a | p = weakref.proxy(o) |
---|
273 | n/a | self.assertEqual(operator.index(p), 10) |
---|
274 | n/a | |
---|
275 | n/a | def test_proxy_div(self): |
---|
276 | n/a | class C: |
---|
277 | n/a | def __floordiv__(self, other): |
---|
278 | n/a | return 42 |
---|
279 | n/a | def __ifloordiv__(self, other): |
---|
280 | n/a | return 21 |
---|
281 | n/a | o = C() |
---|
282 | n/a | p = weakref.proxy(o) |
---|
283 | n/a | self.assertEqual(p // 5, 42) |
---|
284 | n/a | p //= 5 |
---|
285 | n/a | self.assertEqual(p, 21) |
---|
286 | n/a | |
---|
287 | n/a | # The PyWeakref_* C API is documented as allowing either NULL or |
---|
288 | n/a | # None as the value for the callback, where either means "no |
---|
289 | n/a | # callback". The "no callback" ref and proxy objects are supposed |
---|
290 | n/a | # to be shared so long as they exist by all callers so long as |
---|
291 | n/a | # they are active. In Python 2.3.3 and earlier, this guarantee |
---|
292 | n/a | # was not honored, and was broken in different ways for |
---|
293 | n/a | # PyWeakref_NewRef() and PyWeakref_NewProxy(). (Two tests.) |
---|
294 | n/a | |
---|
295 | n/a | def test_shared_ref_without_callback(self): |
---|
296 | n/a | self.check_shared_without_callback(weakref.ref) |
---|
297 | n/a | |
---|
298 | n/a | def test_shared_proxy_without_callback(self): |
---|
299 | n/a | self.check_shared_without_callback(weakref.proxy) |
---|
300 | n/a | |
---|
301 | n/a | def check_shared_without_callback(self, makeref): |
---|
302 | n/a | o = Object(1) |
---|
303 | n/a | p1 = makeref(o, None) |
---|
304 | n/a | p2 = makeref(o, None) |
---|
305 | n/a | self.assertIs(p1, p2, "both callbacks were None in the C API") |
---|
306 | n/a | del p1, p2 |
---|
307 | n/a | p1 = makeref(o) |
---|
308 | n/a | p2 = makeref(o, None) |
---|
309 | n/a | self.assertIs(p1, p2, "callbacks were NULL, None in the C API") |
---|
310 | n/a | del p1, p2 |
---|
311 | n/a | p1 = makeref(o) |
---|
312 | n/a | p2 = makeref(o) |
---|
313 | n/a | self.assertIs(p1, p2, "both callbacks were NULL in the C API") |
---|
314 | n/a | del p1, p2 |
---|
315 | n/a | p1 = makeref(o, None) |
---|
316 | n/a | p2 = makeref(o) |
---|
317 | n/a | self.assertIs(p1, p2, "callbacks were None, NULL in the C API") |
---|
318 | n/a | |
---|
319 | n/a | def test_callable_proxy(self): |
---|
320 | n/a | o = Callable() |
---|
321 | n/a | ref1 = weakref.proxy(o) |
---|
322 | n/a | |
---|
323 | n/a | self.check_proxy(o, ref1) |
---|
324 | n/a | |
---|
325 | n/a | self.assertIs(type(ref1), weakref.CallableProxyType, |
---|
326 | n/a | "proxy is not of callable type") |
---|
327 | n/a | ref1('twinkies!') |
---|
328 | n/a | self.assertEqual(o.bar, 'twinkies!', |
---|
329 | n/a | "call through proxy not passed through to original") |
---|
330 | n/a | ref1(x='Splat.') |
---|
331 | n/a | self.assertEqual(o.bar, 'Splat.', |
---|
332 | n/a | "call through proxy not passed through to original") |
---|
333 | n/a | |
---|
334 | n/a | # expect due to too few args |
---|
335 | n/a | self.assertRaises(TypeError, ref1) |
---|
336 | n/a | |
---|
337 | n/a | # expect due to too many args |
---|
338 | n/a | self.assertRaises(TypeError, ref1, 1, 2, 3) |
---|
339 | n/a | |
---|
340 | n/a | def check_proxy(self, o, proxy): |
---|
341 | n/a | o.foo = 1 |
---|
342 | n/a | self.assertEqual(proxy.foo, 1, |
---|
343 | n/a | "proxy does not reflect attribute addition") |
---|
344 | n/a | o.foo = 2 |
---|
345 | n/a | self.assertEqual(proxy.foo, 2, |
---|
346 | n/a | "proxy does not reflect attribute modification") |
---|
347 | n/a | del o.foo |
---|
348 | n/a | self.assertFalse(hasattr(proxy, 'foo'), |
---|
349 | n/a | "proxy does not reflect attribute removal") |
---|
350 | n/a | |
---|
351 | n/a | proxy.foo = 1 |
---|
352 | n/a | self.assertEqual(o.foo, 1, |
---|
353 | n/a | "object does not reflect attribute addition via proxy") |
---|
354 | n/a | proxy.foo = 2 |
---|
355 | n/a | self.assertEqual(o.foo, 2, |
---|
356 | n/a | "object does not reflect attribute modification via proxy") |
---|
357 | n/a | del proxy.foo |
---|
358 | n/a | self.assertFalse(hasattr(o, 'foo'), |
---|
359 | n/a | "object does not reflect attribute removal via proxy") |
---|
360 | n/a | |
---|
361 | n/a | def test_proxy_deletion(self): |
---|
362 | n/a | # Test clearing of SF bug #762891 |
---|
363 | n/a | class Foo: |
---|
364 | n/a | result = None |
---|
365 | n/a | def __delitem__(self, accessor): |
---|
366 | n/a | self.result = accessor |
---|
367 | n/a | g = Foo() |
---|
368 | n/a | f = weakref.proxy(g) |
---|
369 | n/a | del f[0] |
---|
370 | n/a | self.assertEqual(f.result, 0) |
---|
371 | n/a | |
---|
372 | n/a | def test_proxy_bool(self): |
---|
373 | n/a | # Test clearing of SF bug #1170766 |
---|
374 | n/a | class List(list): pass |
---|
375 | n/a | lyst = List() |
---|
376 | n/a | self.assertEqual(bool(weakref.proxy(lyst)), bool(lyst)) |
---|
377 | n/a | |
---|
378 | n/a | def test_getweakrefcount(self): |
---|
379 | n/a | o = C() |
---|
380 | n/a | ref1 = weakref.ref(o) |
---|
381 | n/a | ref2 = weakref.ref(o, self.callback) |
---|
382 | n/a | self.assertEqual(weakref.getweakrefcount(o), 2, |
---|
383 | n/a | "got wrong number of weak reference objects") |
---|
384 | n/a | |
---|
385 | n/a | proxy1 = weakref.proxy(o) |
---|
386 | n/a | proxy2 = weakref.proxy(o, self.callback) |
---|
387 | n/a | self.assertEqual(weakref.getweakrefcount(o), 4, |
---|
388 | n/a | "got wrong number of weak reference objects") |
---|
389 | n/a | |
---|
390 | n/a | del ref1, ref2, proxy1, proxy2 |
---|
391 | n/a | self.assertEqual(weakref.getweakrefcount(o), 0, |
---|
392 | n/a | "weak reference objects not unlinked from" |
---|
393 | n/a | " referent when discarded.") |
---|
394 | n/a | |
---|
395 | n/a | # assumes ints do not support weakrefs |
---|
396 | n/a | self.assertEqual(weakref.getweakrefcount(1), 0, |
---|
397 | n/a | "got wrong number of weak reference objects for int") |
---|
398 | n/a | |
---|
399 | n/a | def test_getweakrefs(self): |
---|
400 | n/a | o = C() |
---|
401 | n/a | ref1 = weakref.ref(o, self.callback) |
---|
402 | n/a | ref2 = weakref.ref(o, self.callback) |
---|
403 | n/a | del ref1 |
---|
404 | n/a | self.assertEqual(weakref.getweakrefs(o), [ref2], |
---|
405 | n/a | "list of refs does not match") |
---|
406 | n/a | |
---|
407 | n/a | o = C() |
---|
408 | n/a | ref1 = weakref.ref(o, self.callback) |
---|
409 | n/a | ref2 = weakref.ref(o, self.callback) |
---|
410 | n/a | del ref2 |
---|
411 | n/a | self.assertEqual(weakref.getweakrefs(o), [ref1], |
---|
412 | n/a | "list of refs does not match") |
---|
413 | n/a | |
---|
414 | n/a | del ref1 |
---|
415 | n/a | self.assertEqual(weakref.getweakrefs(o), [], |
---|
416 | n/a | "list of refs not cleared") |
---|
417 | n/a | |
---|
418 | n/a | # assumes ints do not support weakrefs |
---|
419 | n/a | self.assertEqual(weakref.getweakrefs(1), [], |
---|
420 | n/a | "list of refs does not match for int") |
---|
421 | n/a | |
---|
422 | n/a | def test_newstyle_number_ops(self): |
---|
423 | n/a | class F(float): |
---|
424 | n/a | pass |
---|
425 | n/a | f = F(2.0) |
---|
426 | n/a | p = weakref.proxy(f) |
---|
427 | n/a | self.assertEqual(p + 1.0, 3.0) |
---|
428 | n/a | self.assertEqual(1.0 + p, 3.0) # this used to SEGV |
---|
429 | n/a | |
---|
430 | n/a | def test_callbacks_protected(self): |
---|
431 | n/a | # Callbacks protected from already-set exceptions? |
---|
432 | n/a | # Regression test for SF bug #478534. |
---|
433 | n/a | class BogusError(Exception): |
---|
434 | n/a | pass |
---|
435 | n/a | data = {} |
---|
436 | n/a | def remove(k): |
---|
437 | n/a | del data[k] |
---|
438 | n/a | def encapsulate(): |
---|
439 | n/a | f = lambda : () |
---|
440 | n/a | data[weakref.ref(f, remove)] = None |
---|
441 | n/a | raise BogusError |
---|
442 | n/a | try: |
---|
443 | n/a | encapsulate() |
---|
444 | n/a | except BogusError: |
---|
445 | n/a | pass |
---|
446 | n/a | else: |
---|
447 | n/a | self.fail("exception not properly restored") |
---|
448 | n/a | try: |
---|
449 | n/a | encapsulate() |
---|
450 | n/a | except BogusError: |
---|
451 | n/a | pass |
---|
452 | n/a | else: |
---|
453 | n/a | self.fail("exception not properly restored") |
---|
454 | n/a | |
---|
455 | n/a | def test_sf_bug_840829(self): |
---|
456 | n/a | # "weakref callbacks and gc corrupt memory" |
---|
457 | n/a | # subtype_dealloc erroneously exposed a new-style instance |
---|
458 | n/a | # already in the process of getting deallocated to gc, |
---|
459 | n/a | # causing double-deallocation if the instance had a weakref |
---|
460 | n/a | # callback that triggered gc. |
---|
461 | n/a | # If the bug exists, there probably won't be an obvious symptom |
---|
462 | n/a | # in a release build. In a debug build, a segfault will occur |
---|
463 | n/a | # when the second attempt to remove the instance from the "list |
---|
464 | n/a | # of all objects" occurs. |
---|
465 | n/a | |
---|
466 | n/a | import gc |
---|
467 | n/a | |
---|
468 | n/a | class C(object): |
---|
469 | n/a | pass |
---|
470 | n/a | |
---|
471 | n/a | c = C() |
---|
472 | n/a | wr = weakref.ref(c, lambda ignore: gc.collect()) |
---|
473 | n/a | del c |
---|
474 | n/a | |
---|
475 | n/a | # There endeth the first part. It gets worse. |
---|
476 | n/a | del wr |
---|
477 | n/a | |
---|
478 | n/a | c1 = C() |
---|
479 | n/a | c1.i = C() |
---|
480 | n/a | wr = weakref.ref(c1.i, lambda ignore: gc.collect()) |
---|
481 | n/a | |
---|
482 | n/a | c2 = C() |
---|
483 | n/a | c2.c1 = c1 |
---|
484 | n/a | del c1 # still alive because c2 points to it |
---|
485 | n/a | |
---|
486 | n/a | # Now when subtype_dealloc gets called on c2, it's not enough just |
---|
487 | n/a | # that c2 is immune from gc while the weakref callbacks associated |
---|
488 | n/a | # with c2 execute (there are none in this 2nd half of the test, btw). |
---|
489 | n/a | # subtype_dealloc goes on to call the base classes' deallocs too, |
---|
490 | n/a | # so any gc triggered by weakref callbacks associated with anything |
---|
491 | n/a | # torn down by a base class dealloc can also trigger double |
---|
492 | n/a | # deallocation of c2. |
---|
493 | n/a | del c2 |
---|
494 | n/a | |
---|
495 | n/a | def test_callback_in_cycle_1(self): |
---|
496 | n/a | import gc |
---|
497 | n/a | |
---|
498 | n/a | class J(object): |
---|
499 | n/a | pass |
---|
500 | n/a | |
---|
501 | n/a | class II(object): |
---|
502 | n/a | def acallback(self, ignore): |
---|
503 | n/a | self.J |
---|
504 | n/a | |
---|
505 | n/a | I = II() |
---|
506 | n/a | I.J = J |
---|
507 | n/a | I.wr = weakref.ref(J, I.acallback) |
---|
508 | n/a | |
---|
509 | n/a | # Now J and II are each in a self-cycle (as all new-style class |
---|
510 | n/a | # objects are, since their __mro__ points back to them). I holds |
---|
511 | n/a | # both a weak reference (I.wr) and a strong reference (I.J) to class |
---|
512 | n/a | # J. I is also in a cycle (I.wr points to a weakref that references |
---|
513 | n/a | # I.acallback). When we del these three, they all become trash, but |
---|
514 | n/a | # the cycles prevent any of them from getting cleaned up immediately. |
---|
515 | n/a | # Instead they have to wait for cyclic gc to deduce that they're |
---|
516 | n/a | # trash. |
---|
517 | n/a | # |
---|
518 | n/a | # gc used to call tp_clear on all of them, and the order in which |
---|
519 | n/a | # it does that is pretty accidental. The exact order in which we |
---|
520 | n/a | # built up these things manages to provoke gc into running tp_clear |
---|
521 | n/a | # in just the right order (I last). Calling tp_clear on II leaves |
---|
522 | n/a | # behind an insane class object (its __mro__ becomes NULL). Calling |
---|
523 | n/a | # tp_clear on J breaks its self-cycle, but J doesn't get deleted |
---|
524 | n/a | # just then because of the strong reference from I.J. Calling |
---|
525 | n/a | # tp_clear on I starts to clear I's __dict__, and just happens to |
---|
526 | n/a | # clear I.J first -- I.wr is still intact. That removes the last |
---|
527 | n/a | # reference to J, which triggers the weakref callback. The callback |
---|
528 | n/a | # tries to do "self.J", and instances of new-style classes look up |
---|
529 | n/a | # attributes ("J") in the class dict first. The class (II) wants to |
---|
530 | n/a | # search II.__mro__, but that's NULL. The result was a segfault in |
---|
531 | n/a | # a release build, and an assert failure in a debug build. |
---|
532 | n/a | del I, J, II |
---|
533 | n/a | gc.collect() |
---|
534 | n/a | |
---|
535 | n/a | def test_callback_in_cycle_2(self): |
---|
536 | n/a | import gc |
---|
537 | n/a | |
---|
538 | n/a | # This is just like test_callback_in_cycle_1, except that II is an |
---|
539 | n/a | # old-style class. The symptom is different then: an instance of an |
---|
540 | n/a | # old-style class looks in its own __dict__ first. 'J' happens to |
---|
541 | n/a | # get cleared from I.__dict__ before 'wr', and 'J' was never in II's |
---|
542 | n/a | # __dict__, so the attribute isn't found. The difference is that |
---|
543 | n/a | # the old-style II doesn't have a NULL __mro__ (it doesn't have any |
---|
544 | n/a | # __mro__), so no segfault occurs. Instead it got: |
---|
545 | n/a | # test_callback_in_cycle_2 (__main__.ReferencesTestCase) ... |
---|
546 | n/a | # Exception exceptions.AttributeError: |
---|
547 | n/a | # "II instance has no attribute 'J'" in <bound method II.acallback |
---|
548 | n/a | # of <?.II instance at 0x00B9B4B8>> ignored |
---|
549 | n/a | |
---|
550 | n/a | class J(object): |
---|
551 | n/a | pass |
---|
552 | n/a | |
---|
553 | n/a | class II: |
---|
554 | n/a | def acallback(self, ignore): |
---|
555 | n/a | self.J |
---|
556 | n/a | |
---|
557 | n/a | I = II() |
---|
558 | n/a | I.J = J |
---|
559 | n/a | I.wr = weakref.ref(J, I.acallback) |
---|
560 | n/a | |
---|
561 | n/a | del I, J, II |
---|
562 | n/a | gc.collect() |
---|
563 | n/a | |
---|
564 | n/a | def test_callback_in_cycle_3(self): |
---|
565 | n/a | import gc |
---|
566 | n/a | |
---|
567 | n/a | # This one broke the first patch that fixed the last two. In this |
---|
568 | n/a | # case, the objects reachable from the callback aren't also reachable |
---|
569 | n/a | # from the object (c1) *triggering* the callback: you can get to |
---|
570 | n/a | # c1 from c2, but not vice-versa. The result was that c2's __dict__ |
---|
571 | n/a | # got tp_clear'ed by the time the c2.cb callback got invoked. |
---|
572 | n/a | |
---|
573 | n/a | class C: |
---|
574 | n/a | def cb(self, ignore): |
---|
575 | n/a | self.me |
---|
576 | n/a | self.c1 |
---|
577 | n/a | self.wr |
---|
578 | n/a | |
---|
579 | n/a | c1, c2 = C(), C() |
---|
580 | n/a | |
---|
581 | n/a | c2.me = c2 |
---|
582 | n/a | c2.c1 = c1 |
---|
583 | n/a | c2.wr = weakref.ref(c1, c2.cb) |
---|
584 | n/a | |
---|
585 | n/a | del c1, c2 |
---|
586 | n/a | gc.collect() |
---|
587 | n/a | |
---|
588 | n/a | def test_callback_in_cycle_4(self): |
---|
589 | n/a | import gc |
---|
590 | n/a | |
---|
591 | n/a | # Like test_callback_in_cycle_3, except c2 and c1 have different |
---|
592 | n/a | # classes. c2's class (C) isn't reachable from c1 then, so protecting |
---|
593 | n/a | # objects reachable from the dying object (c1) isn't enough to stop |
---|
594 | n/a | # c2's class (C) from getting tp_clear'ed before c2.cb is invoked. |
---|
595 | n/a | # The result was a segfault (C.__mro__ was NULL when the callback |
---|
596 | n/a | # tried to look up self.me). |
---|
597 | n/a | |
---|
598 | n/a | class C(object): |
---|
599 | n/a | def cb(self, ignore): |
---|
600 | n/a | self.me |
---|
601 | n/a | self.c1 |
---|
602 | n/a | self.wr |
---|
603 | n/a | |
---|
604 | n/a | class D: |
---|
605 | n/a | pass |
---|
606 | n/a | |
---|
607 | n/a | c1, c2 = D(), C() |
---|
608 | n/a | |
---|
609 | n/a | c2.me = c2 |
---|
610 | n/a | c2.c1 = c1 |
---|
611 | n/a | c2.wr = weakref.ref(c1, c2.cb) |
---|
612 | n/a | |
---|
613 | n/a | del c1, c2, C, D |
---|
614 | n/a | gc.collect() |
---|
615 | n/a | |
---|
616 | n/a | @support.requires_type_collecting |
---|
617 | n/a | def test_callback_in_cycle_resurrection(self): |
---|
618 | n/a | import gc |
---|
619 | n/a | |
---|
620 | n/a | # Do something nasty in a weakref callback: resurrect objects |
---|
621 | n/a | # from dead cycles. For this to be attempted, the weakref and |
---|
622 | n/a | # its callback must also be part of the cyclic trash (else the |
---|
623 | n/a | # objects reachable via the callback couldn't be in cyclic trash |
---|
624 | n/a | # to begin with -- the callback would act like an external root). |
---|
625 | n/a | # But gc clears trash weakrefs with callbacks early now, which |
---|
626 | n/a | # disables the callbacks, so the callbacks shouldn't get called |
---|
627 | n/a | # at all (and so nothing actually gets resurrected). |
---|
628 | n/a | |
---|
629 | n/a | alist = [] |
---|
630 | n/a | class C(object): |
---|
631 | n/a | def __init__(self, value): |
---|
632 | n/a | self.attribute = value |
---|
633 | n/a | |
---|
634 | n/a | def acallback(self, ignore): |
---|
635 | n/a | alist.append(self.c) |
---|
636 | n/a | |
---|
637 | n/a | c1, c2 = C(1), C(2) |
---|
638 | n/a | c1.c = c2 |
---|
639 | n/a | c2.c = c1 |
---|
640 | n/a | c1.wr = weakref.ref(c2, c1.acallback) |
---|
641 | n/a | c2.wr = weakref.ref(c1, c2.acallback) |
---|
642 | n/a | |
---|
643 | n/a | def C_went_away(ignore): |
---|
644 | n/a | alist.append("C went away") |
---|
645 | n/a | wr = weakref.ref(C, C_went_away) |
---|
646 | n/a | |
---|
647 | n/a | del c1, c2, C # make them all trash |
---|
648 | n/a | self.assertEqual(alist, []) # del isn't enough to reclaim anything |
---|
649 | n/a | |
---|
650 | n/a | gc.collect() |
---|
651 | n/a | # c1.wr and c2.wr were part of the cyclic trash, so should have |
---|
652 | n/a | # been cleared without their callbacks executing. OTOH, the weakref |
---|
653 | n/a | # to C is bound to a function local (wr), and wasn't trash, so that |
---|
654 | n/a | # callback should have been invoked when C went away. |
---|
655 | n/a | self.assertEqual(alist, ["C went away"]) |
---|
656 | n/a | # The remaining weakref should be dead now (its callback ran). |
---|
657 | n/a | self.assertEqual(wr(), None) |
---|
658 | n/a | |
---|
659 | n/a | del alist[:] |
---|
660 | n/a | gc.collect() |
---|
661 | n/a | self.assertEqual(alist, []) |
---|
662 | n/a | |
---|
663 | n/a | def test_callbacks_on_callback(self): |
---|
664 | n/a | import gc |
---|
665 | n/a | |
---|
666 | n/a | # Set up weakref callbacks *on* weakref callbacks. |
---|
667 | n/a | alist = [] |
---|
668 | n/a | def safe_callback(ignore): |
---|
669 | n/a | alist.append("safe_callback called") |
---|
670 | n/a | |
---|
671 | n/a | class C(object): |
---|
672 | n/a | def cb(self, ignore): |
---|
673 | n/a | alist.append("cb called") |
---|
674 | n/a | |
---|
675 | n/a | c, d = C(), C() |
---|
676 | n/a | c.other = d |
---|
677 | n/a | d.other = c |
---|
678 | n/a | callback = c.cb |
---|
679 | n/a | c.wr = weakref.ref(d, callback) # this won't trigger |
---|
680 | n/a | d.wr = weakref.ref(callback, d.cb) # ditto |
---|
681 | n/a | external_wr = weakref.ref(callback, safe_callback) # but this will |
---|
682 | n/a | self.assertIs(external_wr(), callback) |
---|
683 | n/a | |
---|
684 | n/a | # The weakrefs attached to c and d should get cleared, so that |
---|
685 | n/a | # C.cb is never called. But external_wr isn't part of the cyclic |
---|
686 | n/a | # trash, and no cyclic trash is reachable from it, so safe_callback |
---|
687 | n/a | # should get invoked when the bound method object callback (c.cb) |
---|
688 | n/a | # -- which is itself a callback, and also part of the cyclic trash -- |
---|
689 | n/a | # gets reclaimed at the end of gc. |
---|
690 | n/a | |
---|
691 | n/a | del callback, c, d, C |
---|
692 | n/a | self.assertEqual(alist, []) # del isn't enough to clean up cycles |
---|
693 | n/a | gc.collect() |
---|
694 | n/a | self.assertEqual(alist, ["safe_callback called"]) |
---|
695 | n/a | self.assertEqual(external_wr(), None) |
---|
696 | n/a | |
---|
697 | n/a | del alist[:] |
---|
698 | n/a | gc.collect() |
---|
699 | n/a | self.assertEqual(alist, []) |
---|
700 | n/a | |
---|
701 | n/a | def test_gc_during_ref_creation(self): |
---|
702 | n/a | self.check_gc_during_creation(weakref.ref) |
---|
703 | n/a | |
---|
704 | n/a | def test_gc_during_proxy_creation(self): |
---|
705 | n/a | self.check_gc_during_creation(weakref.proxy) |
---|
706 | n/a | |
---|
707 | n/a | def check_gc_during_creation(self, makeref): |
---|
708 | n/a | thresholds = gc.get_threshold() |
---|
709 | n/a | gc.set_threshold(1, 1, 1) |
---|
710 | n/a | gc.collect() |
---|
711 | n/a | class A: |
---|
712 | n/a | pass |
---|
713 | n/a | |
---|
714 | n/a | def callback(*args): |
---|
715 | n/a | pass |
---|
716 | n/a | |
---|
717 | n/a | referenced = A() |
---|
718 | n/a | |
---|
719 | n/a | a = A() |
---|
720 | n/a | a.a = a |
---|
721 | n/a | a.wr = makeref(referenced) |
---|
722 | n/a | |
---|
723 | n/a | try: |
---|
724 | n/a | # now make sure the object and the ref get labeled as |
---|
725 | n/a | # cyclic trash: |
---|
726 | n/a | a = A() |
---|
727 | n/a | weakref.ref(referenced, callback) |
---|
728 | n/a | |
---|
729 | n/a | finally: |
---|
730 | n/a | gc.set_threshold(*thresholds) |
---|
731 | n/a | |
---|
732 | n/a | def test_ref_created_during_del(self): |
---|
733 | n/a | # Bug #1377858 |
---|
734 | n/a | # A weakref created in an object's __del__() would crash the |
---|
735 | n/a | # interpreter when the weakref was cleaned up since it would refer to |
---|
736 | n/a | # non-existent memory. This test should not segfault the interpreter. |
---|
737 | n/a | class Target(object): |
---|
738 | n/a | def __del__(self): |
---|
739 | n/a | global ref_from_del |
---|
740 | n/a | ref_from_del = weakref.ref(self) |
---|
741 | n/a | |
---|
742 | n/a | w = Target() |
---|
743 | n/a | |
---|
744 | n/a | def test_init(self): |
---|
745 | n/a | # Issue 3634 |
---|
746 | n/a | # <weakref to class>.__init__() doesn't check errors correctly |
---|
747 | n/a | r = weakref.ref(Exception) |
---|
748 | n/a | self.assertRaises(TypeError, r.__init__, 0, 0, 0, 0, 0) |
---|
749 | n/a | # No exception should be raised here |
---|
750 | n/a | gc.collect() |
---|
751 | n/a | |
---|
752 | n/a | def test_classes(self): |
---|
753 | n/a | # Check that classes are weakrefable. |
---|
754 | n/a | class A(object): |
---|
755 | n/a | pass |
---|
756 | n/a | l = [] |
---|
757 | n/a | weakref.ref(int) |
---|
758 | n/a | a = weakref.ref(A, l.append) |
---|
759 | n/a | A = None |
---|
760 | n/a | gc.collect() |
---|
761 | n/a | self.assertEqual(a(), None) |
---|
762 | n/a | self.assertEqual(l, [a]) |
---|
763 | n/a | |
---|
764 | n/a | def test_equality(self): |
---|
765 | n/a | # Alive weakrefs defer equality testing to their underlying object. |
---|
766 | n/a | x = Object(1) |
---|
767 | n/a | y = Object(1) |
---|
768 | n/a | z = Object(2) |
---|
769 | n/a | a = weakref.ref(x) |
---|
770 | n/a | b = weakref.ref(y) |
---|
771 | n/a | c = weakref.ref(z) |
---|
772 | n/a | d = weakref.ref(x) |
---|
773 | n/a | # Note how we directly test the operators here, to stress both |
---|
774 | n/a | # __eq__ and __ne__. |
---|
775 | n/a | self.assertTrue(a == b) |
---|
776 | n/a | self.assertFalse(a != b) |
---|
777 | n/a | self.assertFalse(a == c) |
---|
778 | n/a | self.assertTrue(a != c) |
---|
779 | n/a | self.assertTrue(a == d) |
---|
780 | n/a | self.assertFalse(a != d) |
---|
781 | n/a | del x, y, z |
---|
782 | n/a | gc.collect() |
---|
783 | n/a | for r in a, b, c: |
---|
784 | n/a | # Sanity check |
---|
785 | n/a | self.assertIs(r(), None) |
---|
786 | n/a | # Dead weakrefs compare by identity: whether `a` and `d` are the |
---|
787 | n/a | # same weakref object is an implementation detail, since they pointed |
---|
788 | n/a | # to the same original object and didn't have a callback. |
---|
789 | n/a | # (see issue #16453). |
---|
790 | n/a | self.assertFalse(a == b) |
---|
791 | n/a | self.assertTrue(a != b) |
---|
792 | n/a | self.assertFalse(a == c) |
---|
793 | n/a | self.assertTrue(a != c) |
---|
794 | n/a | self.assertEqual(a == d, a is d) |
---|
795 | n/a | self.assertEqual(a != d, a is not d) |
---|
796 | n/a | |
---|
797 | n/a | def test_ordering(self): |
---|
798 | n/a | # weakrefs cannot be ordered, even if the underlying objects can. |
---|
799 | n/a | ops = [operator.lt, operator.gt, operator.le, operator.ge] |
---|
800 | n/a | x = Object(1) |
---|
801 | n/a | y = Object(1) |
---|
802 | n/a | a = weakref.ref(x) |
---|
803 | n/a | b = weakref.ref(y) |
---|
804 | n/a | for op in ops: |
---|
805 | n/a | self.assertRaises(TypeError, op, a, b) |
---|
806 | n/a | # Same when dead. |
---|
807 | n/a | del x, y |
---|
808 | n/a | gc.collect() |
---|
809 | n/a | for op in ops: |
---|
810 | n/a | self.assertRaises(TypeError, op, a, b) |
---|
811 | n/a | |
---|
812 | n/a | def test_hashing(self): |
---|
813 | n/a | # Alive weakrefs hash the same as the underlying object |
---|
814 | n/a | x = Object(42) |
---|
815 | n/a | y = Object(42) |
---|
816 | n/a | a = weakref.ref(x) |
---|
817 | n/a | b = weakref.ref(y) |
---|
818 | n/a | self.assertEqual(hash(a), hash(42)) |
---|
819 | n/a | del x, y |
---|
820 | n/a | gc.collect() |
---|
821 | n/a | # Dead weakrefs: |
---|
822 | n/a | # - retain their hash is they were hashed when alive; |
---|
823 | n/a | # - otherwise, cannot be hashed. |
---|
824 | n/a | self.assertEqual(hash(a), hash(42)) |
---|
825 | n/a | self.assertRaises(TypeError, hash, b) |
---|
826 | n/a | |
---|
827 | n/a | def test_trashcan_16602(self): |
---|
828 | n/a | # Issue #16602: when a weakref's target was part of a long |
---|
829 | n/a | # deallocation chain, the trashcan mechanism could delay clearing |
---|
830 | n/a | # of the weakref and make the target object visible from outside |
---|
831 | n/a | # code even though its refcount had dropped to 0. A crash ensued. |
---|
832 | n/a | class C: |
---|
833 | n/a | def __init__(self, parent): |
---|
834 | n/a | if not parent: |
---|
835 | n/a | return |
---|
836 | n/a | wself = weakref.ref(self) |
---|
837 | n/a | def cb(wparent): |
---|
838 | n/a | o = wself() |
---|
839 | n/a | self.wparent = weakref.ref(parent, cb) |
---|
840 | n/a | |
---|
841 | n/a | d = weakref.WeakKeyDictionary() |
---|
842 | n/a | root = c = C(None) |
---|
843 | n/a | for n in range(100): |
---|
844 | n/a | d[c] = c = C(c) |
---|
845 | n/a | del root |
---|
846 | n/a | gc.collect() |
---|
847 | n/a | |
---|
848 | n/a | def test_callback_attribute(self): |
---|
849 | n/a | x = Object(1) |
---|
850 | n/a | callback = lambda ref: None |
---|
851 | n/a | ref1 = weakref.ref(x, callback) |
---|
852 | n/a | self.assertIs(ref1.__callback__, callback) |
---|
853 | n/a | |
---|
854 | n/a | ref2 = weakref.ref(x) |
---|
855 | n/a | self.assertIsNone(ref2.__callback__) |
---|
856 | n/a | |
---|
857 | n/a | def test_callback_attribute_after_deletion(self): |
---|
858 | n/a | x = Object(1) |
---|
859 | n/a | ref = weakref.ref(x, self.callback) |
---|
860 | n/a | self.assertIsNotNone(ref.__callback__) |
---|
861 | n/a | del x |
---|
862 | n/a | support.gc_collect() |
---|
863 | n/a | self.assertIsNone(ref.__callback__) |
---|
864 | n/a | |
---|
865 | n/a | def test_set_callback_attribute(self): |
---|
866 | n/a | x = Object(1) |
---|
867 | n/a | callback = lambda ref: None |
---|
868 | n/a | ref1 = weakref.ref(x, callback) |
---|
869 | n/a | with self.assertRaises(AttributeError): |
---|
870 | n/a | ref1.__callback__ = lambda ref: None |
---|
871 | n/a | |
---|
872 | n/a | def test_callback_gcs(self): |
---|
873 | n/a | class ObjectWithDel(Object): |
---|
874 | n/a | def __del__(self): pass |
---|
875 | n/a | x = ObjectWithDel(1) |
---|
876 | n/a | ref1 = weakref.ref(x, lambda ref: support.gc_collect()) |
---|
877 | n/a | del x |
---|
878 | n/a | support.gc_collect() |
---|
879 | n/a | |
---|
880 | n/a | |
---|
881 | n/a | class SubclassableWeakrefTestCase(TestBase): |
---|
882 | n/a | |
---|
883 | n/a | def test_subclass_refs(self): |
---|
884 | n/a | class MyRef(weakref.ref): |
---|
885 | n/a | def __init__(self, ob, callback=None, value=42): |
---|
886 | n/a | self.value = value |
---|
887 | n/a | super().__init__(ob, callback) |
---|
888 | n/a | def __call__(self): |
---|
889 | n/a | self.called = True |
---|
890 | n/a | return super().__call__() |
---|
891 | n/a | o = Object("foo") |
---|
892 | n/a | mr = MyRef(o, value=24) |
---|
893 | n/a | self.assertIs(mr(), o) |
---|
894 | n/a | self.assertTrue(mr.called) |
---|
895 | n/a | self.assertEqual(mr.value, 24) |
---|
896 | n/a | del o |
---|
897 | n/a | self.assertIsNone(mr()) |
---|
898 | n/a | self.assertTrue(mr.called) |
---|
899 | n/a | |
---|
900 | n/a | def test_subclass_refs_dont_replace_standard_refs(self): |
---|
901 | n/a | class MyRef(weakref.ref): |
---|
902 | n/a | pass |
---|
903 | n/a | o = Object(42) |
---|
904 | n/a | r1 = MyRef(o) |
---|
905 | n/a | r2 = weakref.ref(o) |
---|
906 | n/a | self.assertIsNot(r1, r2) |
---|
907 | n/a | self.assertEqual(weakref.getweakrefs(o), [r2, r1]) |
---|
908 | n/a | self.assertEqual(weakref.getweakrefcount(o), 2) |
---|
909 | n/a | r3 = MyRef(o) |
---|
910 | n/a | self.assertEqual(weakref.getweakrefcount(o), 3) |
---|
911 | n/a | refs = weakref.getweakrefs(o) |
---|
912 | n/a | self.assertEqual(len(refs), 3) |
---|
913 | n/a | self.assertIs(r2, refs[0]) |
---|
914 | n/a | self.assertIn(r1, refs[1:]) |
---|
915 | n/a | self.assertIn(r3, refs[1:]) |
---|
916 | n/a | |
---|
917 | n/a | def test_subclass_refs_dont_conflate_callbacks(self): |
---|
918 | n/a | class MyRef(weakref.ref): |
---|
919 | n/a | pass |
---|
920 | n/a | o = Object(42) |
---|
921 | n/a | r1 = MyRef(o, id) |
---|
922 | n/a | r2 = MyRef(o, str) |
---|
923 | n/a | self.assertIsNot(r1, r2) |
---|
924 | n/a | refs = weakref.getweakrefs(o) |
---|
925 | n/a | self.assertIn(r1, refs) |
---|
926 | n/a | self.assertIn(r2, refs) |
---|
927 | n/a | |
---|
928 | n/a | def test_subclass_refs_with_slots(self): |
---|
929 | n/a | class MyRef(weakref.ref): |
---|
930 | n/a | __slots__ = "slot1", "slot2" |
---|
931 | n/a | def __new__(type, ob, callback, slot1, slot2): |
---|
932 | n/a | return weakref.ref.__new__(type, ob, callback) |
---|
933 | n/a | def __init__(self, ob, callback, slot1, slot2): |
---|
934 | n/a | self.slot1 = slot1 |
---|
935 | n/a | self.slot2 = slot2 |
---|
936 | n/a | def meth(self): |
---|
937 | n/a | return self.slot1 + self.slot2 |
---|
938 | n/a | o = Object(42) |
---|
939 | n/a | r = MyRef(o, None, "abc", "def") |
---|
940 | n/a | self.assertEqual(r.slot1, "abc") |
---|
941 | n/a | self.assertEqual(r.slot2, "def") |
---|
942 | n/a | self.assertEqual(r.meth(), "abcdef") |
---|
943 | n/a | self.assertFalse(hasattr(r, "__dict__")) |
---|
944 | n/a | |
---|
945 | n/a | def test_subclass_refs_with_cycle(self): |
---|
946 | n/a | """Confirm https://bugs.python.org/issue3100 is fixed.""" |
---|
947 | n/a | # An instance of a weakref subclass can have attributes. |
---|
948 | n/a | # If such a weakref holds the only strong reference to the object, |
---|
949 | n/a | # deleting the weakref will delete the object. In this case, |
---|
950 | n/a | # the callback must not be called, because the ref object is |
---|
951 | n/a | # being deleted. |
---|
952 | n/a | class MyRef(weakref.ref): |
---|
953 | n/a | pass |
---|
954 | n/a | |
---|
955 | n/a | # Use a local callback, for "regrtest -R::" |
---|
956 | n/a | # to detect refcounting problems |
---|
957 | n/a | def callback(w): |
---|
958 | n/a | self.cbcalled += 1 |
---|
959 | n/a | |
---|
960 | n/a | o = C() |
---|
961 | n/a | r1 = MyRef(o, callback) |
---|
962 | n/a | r1.o = o |
---|
963 | n/a | del o |
---|
964 | n/a | |
---|
965 | n/a | del r1 # Used to crash here |
---|
966 | n/a | |
---|
967 | n/a | self.assertEqual(self.cbcalled, 0) |
---|
968 | n/a | |
---|
969 | n/a | # Same test, with two weakrefs to the same object |
---|
970 | n/a | # (since code paths are different) |
---|
971 | n/a | o = C() |
---|
972 | n/a | r1 = MyRef(o, callback) |
---|
973 | n/a | r2 = MyRef(o, callback) |
---|
974 | n/a | r1.r = r2 |
---|
975 | n/a | r2.o = o |
---|
976 | n/a | del o |
---|
977 | n/a | del r2 |
---|
978 | n/a | |
---|
979 | n/a | del r1 # Used to crash here |
---|
980 | n/a | |
---|
981 | n/a | self.assertEqual(self.cbcalled, 0) |
---|
982 | n/a | |
---|
983 | n/a | |
---|
984 | n/a | class WeakMethodTestCase(unittest.TestCase): |
---|
985 | n/a | |
---|
986 | n/a | def _subclass(self): |
---|
987 | n/a | """Return an Object subclass overriding `some_method`.""" |
---|
988 | n/a | class C(Object): |
---|
989 | n/a | def some_method(self): |
---|
990 | n/a | return 6 |
---|
991 | n/a | return C |
---|
992 | n/a | |
---|
993 | n/a | def test_alive(self): |
---|
994 | n/a | o = Object(1) |
---|
995 | n/a | r = weakref.WeakMethod(o.some_method) |
---|
996 | n/a | self.assertIsInstance(r, weakref.ReferenceType) |
---|
997 | n/a | self.assertIsInstance(r(), type(o.some_method)) |
---|
998 | n/a | self.assertIs(r().__self__, o) |
---|
999 | n/a | self.assertIs(r().__func__, o.some_method.__func__) |
---|
1000 | n/a | self.assertEqual(r()(), 4) |
---|
1001 | n/a | |
---|
1002 | n/a | def test_object_dead(self): |
---|
1003 | n/a | o = Object(1) |
---|
1004 | n/a | r = weakref.WeakMethod(o.some_method) |
---|
1005 | n/a | del o |
---|
1006 | n/a | gc.collect() |
---|
1007 | n/a | self.assertIs(r(), None) |
---|
1008 | n/a | |
---|
1009 | n/a | def test_method_dead(self): |
---|
1010 | n/a | C = self._subclass() |
---|
1011 | n/a | o = C(1) |
---|
1012 | n/a | r = weakref.WeakMethod(o.some_method) |
---|
1013 | n/a | del C.some_method |
---|
1014 | n/a | gc.collect() |
---|
1015 | n/a | self.assertIs(r(), None) |
---|
1016 | n/a | |
---|
1017 | n/a | def test_callback_when_object_dead(self): |
---|
1018 | n/a | # Test callback behaviour when object dies first. |
---|
1019 | n/a | C = self._subclass() |
---|
1020 | n/a | calls = [] |
---|
1021 | n/a | def cb(arg): |
---|
1022 | n/a | calls.append(arg) |
---|
1023 | n/a | o = C(1) |
---|
1024 | n/a | r = weakref.WeakMethod(o.some_method, cb) |
---|
1025 | n/a | del o |
---|
1026 | n/a | gc.collect() |
---|
1027 | n/a | self.assertEqual(calls, [r]) |
---|
1028 | n/a | # Callback is only called once. |
---|
1029 | n/a | C.some_method = Object.some_method |
---|
1030 | n/a | gc.collect() |
---|
1031 | n/a | self.assertEqual(calls, [r]) |
---|
1032 | n/a | |
---|
1033 | n/a | def test_callback_when_method_dead(self): |
---|
1034 | n/a | # Test callback behaviour when method dies first. |
---|
1035 | n/a | C = self._subclass() |
---|
1036 | n/a | calls = [] |
---|
1037 | n/a | def cb(arg): |
---|
1038 | n/a | calls.append(arg) |
---|
1039 | n/a | o = C(1) |
---|
1040 | n/a | r = weakref.WeakMethod(o.some_method, cb) |
---|
1041 | n/a | del C.some_method |
---|
1042 | n/a | gc.collect() |
---|
1043 | n/a | self.assertEqual(calls, [r]) |
---|
1044 | n/a | # Callback is only called once. |
---|
1045 | n/a | del o |
---|
1046 | n/a | gc.collect() |
---|
1047 | n/a | self.assertEqual(calls, [r]) |
---|
1048 | n/a | |
---|
1049 | n/a | @support.cpython_only |
---|
1050 | n/a | def test_no_cycles(self): |
---|
1051 | n/a | # A WeakMethod doesn't create any reference cycle to itself. |
---|
1052 | n/a | o = Object(1) |
---|
1053 | n/a | def cb(_): |
---|
1054 | n/a | pass |
---|
1055 | n/a | r = weakref.WeakMethod(o.some_method, cb) |
---|
1056 | n/a | wr = weakref.ref(r) |
---|
1057 | n/a | del r |
---|
1058 | n/a | self.assertIs(wr(), None) |
---|
1059 | n/a | |
---|
1060 | n/a | def test_equality(self): |
---|
1061 | n/a | def _eq(a, b): |
---|
1062 | n/a | self.assertTrue(a == b) |
---|
1063 | n/a | self.assertFalse(a != b) |
---|
1064 | n/a | def _ne(a, b): |
---|
1065 | n/a | self.assertTrue(a != b) |
---|
1066 | n/a | self.assertFalse(a == b) |
---|
1067 | n/a | x = Object(1) |
---|
1068 | n/a | y = Object(1) |
---|
1069 | n/a | a = weakref.WeakMethod(x.some_method) |
---|
1070 | n/a | b = weakref.WeakMethod(y.some_method) |
---|
1071 | n/a | c = weakref.WeakMethod(x.other_method) |
---|
1072 | n/a | d = weakref.WeakMethod(y.other_method) |
---|
1073 | n/a | # Objects equal, same method |
---|
1074 | n/a | _eq(a, b) |
---|
1075 | n/a | _eq(c, d) |
---|
1076 | n/a | # Objects equal, different method |
---|
1077 | n/a | _ne(a, c) |
---|
1078 | n/a | _ne(a, d) |
---|
1079 | n/a | _ne(b, c) |
---|
1080 | n/a | _ne(b, d) |
---|
1081 | n/a | # Objects unequal, same or different method |
---|
1082 | n/a | z = Object(2) |
---|
1083 | n/a | e = weakref.WeakMethod(z.some_method) |
---|
1084 | n/a | f = weakref.WeakMethod(z.other_method) |
---|
1085 | n/a | _ne(a, e) |
---|
1086 | n/a | _ne(a, f) |
---|
1087 | n/a | _ne(b, e) |
---|
1088 | n/a | _ne(b, f) |
---|
1089 | n/a | del x, y, z |
---|
1090 | n/a | gc.collect() |
---|
1091 | n/a | # Dead WeakMethods compare by identity |
---|
1092 | n/a | refs = a, b, c, d, e, f |
---|
1093 | n/a | for q in refs: |
---|
1094 | n/a | for r in refs: |
---|
1095 | n/a | self.assertEqual(q == r, q is r) |
---|
1096 | n/a | self.assertEqual(q != r, q is not r) |
---|
1097 | n/a | |
---|
1098 | n/a | def test_hashing(self): |
---|
1099 | n/a | # Alive WeakMethods are hashable if the underlying object is |
---|
1100 | n/a | # hashable. |
---|
1101 | n/a | x = Object(1) |
---|
1102 | n/a | y = Object(1) |
---|
1103 | n/a | a = weakref.WeakMethod(x.some_method) |
---|
1104 | n/a | b = weakref.WeakMethod(y.some_method) |
---|
1105 | n/a | c = weakref.WeakMethod(y.other_method) |
---|
1106 | n/a | # Since WeakMethod objects are equal, the hashes should be equal. |
---|
1107 | n/a | self.assertEqual(hash(a), hash(b)) |
---|
1108 | n/a | ha = hash(a) |
---|
1109 | n/a | # Dead WeakMethods retain their old hash value |
---|
1110 | n/a | del x, y |
---|
1111 | n/a | gc.collect() |
---|
1112 | n/a | self.assertEqual(hash(a), ha) |
---|
1113 | n/a | self.assertEqual(hash(b), ha) |
---|
1114 | n/a | # If it wasn't hashed when alive, a dead WeakMethod cannot be hashed. |
---|
1115 | n/a | self.assertRaises(TypeError, hash, c) |
---|
1116 | n/a | |
---|
1117 | n/a | |
---|
1118 | n/a | class MappingTestCase(TestBase): |
---|
1119 | n/a | |
---|
1120 | n/a | COUNT = 10 |
---|
1121 | n/a | |
---|
1122 | n/a | def check_len_cycles(self, dict_type, cons): |
---|
1123 | n/a | N = 20 |
---|
1124 | n/a | items = [RefCycle() for i in range(N)] |
---|
1125 | n/a | dct = dict_type(cons(o) for o in items) |
---|
1126 | n/a | # Keep an iterator alive |
---|
1127 | n/a | it = dct.items() |
---|
1128 | n/a | try: |
---|
1129 | n/a | next(it) |
---|
1130 | n/a | except StopIteration: |
---|
1131 | n/a | pass |
---|
1132 | n/a | del items |
---|
1133 | n/a | gc.collect() |
---|
1134 | n/a | n1 = len(dct) |
---|
1135 | n/a | del it |
---|
1136 | n/a | gc.collect() |
---|
1137 | n/a | n2 = len(dct) |
---|
1138 | n/a | # one item may be kept alive inside the iterator |
---|
1139 | n/a | self.assertIn(n1, (0, 1)) |
---|
1140 | n/a | self.assertEqual(n2, 0) |
---|
1141 | n/a | |
---|
1142 | n/a | def test_weak_keyed_len_cycles(self): |
---|
1143 | n/a | self.check_len_cycles(weakref.WeakKeyDictionary, lambda k: (k, 1)) |
---|
1144 | n/a | |
---|
1145 | n/a | def test_weak_valued_len_cycles(self): |
---|
1146 | n/a | self.check_len_cycles(weakref.WeakValueDictionary, lambda k: (1, k)) |
---|
1147 | n/a | |
---|
1148 | n/a | def check_len_race(self, dict_type, cons): |
---|
1149 | n/a | # Extended sanity checks for len() in the face of cyclic collection |
---|
1150 | n/a | self.addCleanup(gc.set_threshold, *gc.get_threshold()) |
---|
1151 | n/a | for th in range(1, 100): |
---|
1152 | n/a | N = 20 |
---|
1153 | n/a | gc.collect(0) |
---|
1154 | n/a | gc.set_threshold(th, th, th) |
---|
1155 | n/a | items = [RefCycle() for i in range(N)] |
---|
1156 | n/a | dct = dict_type(cons(o) for o in items) |
---|
1157 | n/a | del items |
---|
1158 | n/a | # All items will be collected at next garbage collection pass |
---|
1159 | n/a | it = dct.items() |
---|
1160 | n/a | try: |
---|
1161 | n/a | next(it) |
---|
1162 | n/a | except StopIteration: |
---|
1163 | n/a | pass |
---|
1164 | n/a | n1 = len(dct) |
---|
1165 | n/a | del it |
---|
1166 | n/a | n2 = len(dct) |
---|
1167 | n/a | self.assertGreaterEqual(n1, 0) |
---|
1168 | n/a | self.assertLessEqual(n1, N) |
---|
1169 | n/a | self.assertGreaterEqual(n2, 0) |
---|
1170 | n/a | self.assertLessEqual(n2, n1) |
---|
1171 | n/a | |
---|
1172 | n/a | def test_weak_keyed_len_race(self): |
---|
1173 | n/a | self.check_len_race(weakref.WeakKeyDictionary, lambda k: (k, 1)) |
---|
1174 | n/a | |
---|
1175 | n/a | def test_weak_valued_len_race(self): |
---|
1176 | n/a | self.check_len_race(weakref.WeakValueDictionary, lambda k: (1, k)) |
---|
1177 | n/a | |
---|
1178 | n/a | def test_weak_values(self): |
---|
1179 | n/a | # |
---|
1180 | n/a | # This exercises d.copy(), d.items(), d[], del d[], len(d). |
---|
1181 | n/a | # |
---|
1182 | n/a | dict, objects = self.make_weak_valued_dict() |
---|
1183 | n/a | for o in objects: |
---|
1184 | n/a | self.assertEqual(weakref.getweakrefcount(o), 1) |
---|
1185 | n/a | self.assertIs(o, dict[o.arg], |
---|
1186 | n/a | "wrong object returned by weak dict!") |
---|
1187 | n/a | items1 = list(dict.items()) |
---|
1188 | n/a | items2 = list(dict.copy().items()) |
---|
1189 | n/a | items1.sort() |
---|
1190 | n/a | items2.sort() |
---|
1191 | n/a | self.assertEqual(items1, items2, |
---|
1192 | n/a | "cloning of weak-valued dictionary did not work!") |
---|
1193 | n/a | del items1, items2 |
---|
1194 | n/a | self.assertEqual(len(dict), self.COUNT) |
---|
1195 | n/a | del objects[0] |
---|
1196 | n/a | self.assertEqual(len(dict), self.COUNT - 1, |
---|
1197 | n/a | "deleting object did not cause dictionary update") |
---|
1198 | n/a | del objects, o |
---|
1199 | n/a | self.assertEqual(len(dict), 0, |
---|
1200 | n/a | "deleting the values did not clear the dictionary") |
---|
1201 | n/a | # regression on SF bug #447152: |
---|
1202 | n/a | dict = weakref.WeakValueDictionary() |
---|
1203 | n/a | self.assertRaises(KeyError, dict.__getitem__, 1) |
---|
1204 | n/a | dict[2] = C() |
---|
1205 | n/a | self.assertRaises(KeyError, dict.__getitem__, 2) |
---|
1206 | n/a | |
---|
1207 | n/a | def test_weak_keys(self): |
---|
1208 | n/a | # |
---|
1209 | n/a | # This exercises d.copy(), d.items(), d[] = v, d[], del d[], |
---|
1210 | n/a | # len(d), k in d. |
---|
1211 | n/a | # |
---|
1212 | n/a | dict, objects = self.make_weak_keyed_dict() |
---|
1213 | n/a | for o in objects: |
---|
1214 | n/a | self.assertEqual(weakref.getweakrefcount(o), 1, |
---|
1215 | n/a | "wrong number of weak references to %r!" % o) |
---|
1216 | n/a | self.assertIs(o.arg, dict[o], |
---|
1217 | n/a | "wrong object returned by weak dict!") |
---|
1218 | n/a | items1 = dict.items() |
---|
1219 | n/a | items2 = dict.copy().items() |
---|
1220 | n/a | self.assertEqual(set(items1), set(items2), |
---|
1221 | n/a | "cloning of weak-keyed dictionary did not work!") |
---|
1222 | n/a | del items1, items2 |
---|
1223 | n/a | self.assertEqual(len(dict), self.COUNT) |
---|
1224 | n/a | del objects[0] |
---|
1225 | n/a | self.assertEqual(len(dict), (self.COUNT - 1), |
---|
1226 | n/a | "deleting object did not cause dictionary update") |
---|
1227 | n/a | del objects, o |
---|
1228 | n/a | self.assertEqual(len(dict), 0, |
---|
1229 | n/a | "deleting the keys did not clear the dictionary") |
---|
1230 | n/a | o = Object(42) |
---|
1231 | n/a | dict[o] = "What is the meaning of the universe?" |
---|
1232 | n/a | self.assertIn(o, dict) |
---|
1233 | n/a | self.assertNotIn(34, dict) |
---|
1234 | n/a | |
---|
1235 | n/a | def test_weak_keyed_iters(self): |
---|
1236 | n/a | dict, objects = self.make_weak_keyed_dict() |
---|
1237 | n/a | self.check_iters(dict) |
---|
1238 | n/a | |
---|
1239 | n/a | # Test keyrefs() |
---|
1240 | n/a | refs = dict.keyrefs() |
---|
1241 | n/a | self.assertEqual(len(refs), len(objects)) |
---|
1242 | n/a | objects2 = list(objects) |
---|
1243 | n/a | for wr in refs: |
---|
1244 | n/a | ob = wr() |
---|
1245 | n/a | self.assertIn(ob, dict) |
---|
1246 | n/a | self.assertIn(ob, dict) |
---|
1247 | n/a | self.assertEqual(ob.arg, dict[ob]) |
---|
1248 | n/a | objects2.remove(ob) |
---|
1249 | n/a | self.assertEqual(len(objects2), 0) |
---|
1250 | n/a | |
---|
1251 | n/a | # Test iterkeyrefs() |
---|
1252 | n/a | objects2 = list(objects) |
---|
1253 | n/a | self.assertEqual(len(list(dict.keyrefs())), len(objects)) |
---|
1254 | n/a | for wr in dict.keyrefs(): |
---|
1255 | n/a | ob = wr() |
---|
1256 | n/a | self.assertIn(ob, dict) |
---|
1257 | n/a | self.assertIn(ob, dict) |
---|
1258 | n/a | self.assertEqual(ob.arg, dict[ob]) |
---|
1259 | n/a | objects2.remove(ob) |
---|
1260 | n/a | self.assertEqual(len(objects2), 0) |
---|
1261 | n/a | |
---|
1262 | n/a | def test_weak_valued_iters(self): |
---|
1263 | n/a | dict, objects = self.make_weak_valued_dict() |
---|
1264 | n/a | self.check_iters(dict) |
---|
1265 | n/a | |
---|
1266 | n/a | # Test valuerefs() |
---|
1267 | n/a | refs = dict.valuerefs() |
---|
1268 | n/a | self.assertEqual(len(refs), len(objects)) |
---|
1269 | n/a | objects2 = list(objects) |
---|
1270 | n/a | for wr in refs: |
---|
1271 | n/a | ob = wr() |
---|
1272 | n/a | self.assertEqual(ob, dict[ob.arg]) |
---|
1273 | n/a | self.assertEqual(ob.arg, dict[ob.arg].arg) |
---|
1274 | n/a | objects2.remove(ob) |
---|
1275 | n/a | self.assertEqual(len(objects2), 0) |
---|
1276 | n/a | |
---|
1277 | n/a | # Test itervaluerefs() |
---|
1278 | n/a | objects2 = list(objects) |
---|
1279 | n/a | self.assertEqual(len(list(dict.itervaluerefs())), len(objects)) |
---|
1280 | n/a | for wr in dict.itervaluerefs(): |
---|
1281 | n/a | ob = wr() |
---|
1282 | n/a | self.assertEqual(ob, dict[ob.arg]) |
---|
1283 | n/a | self.assertEqual(ob.arg, dict[ob.arg].arg) |
---|
1284 | n/a | objects2.remove(ob) |
---|
1285 | n/a | self.assertEqual(len(objects2), 0) |
---|
1286 | n/a | |
---|
1287 | n/a | def check_iters(self, dict): |
---|
1288 | n/a | # item iterator: |
---|
1289 | n/a | items = list(dict.items()) |
---|
1290 | n/a | for item in dict.items(): |
---|
1291 | n/a | items.remove(item) |
---|
1292 | n/a | self.assertFalse(items, "items() did not touch all items") |
---|
1293 | n/a | |
---|
1294 | n/a | # key iterator, via __iter__(): |
---|
1295 | n/a | keys = list(dict.keys()) |
---|
1296 | n/a | for k in dict: |
---|
1297 | n/a | keys.remove(k) |
---|
1298 | n/a | self.assertFalse(keys, "__iter__() did not touch all keys") |
---|
1299 | n/a | |
---|
1300 | n/a | # key iterator, via iterkeys(): |
---|
1301 | n/a | keys = list(dict.keys()) |
---|
1302 | n/a | for k in dict.keys(): |
---|
1303 | n/a | keys.remove(k) |
---|
1304 | n/a | self.assertFalse(keys, "iterkeys() did not touch all keys") |
---|
1305 | n/a | |
---|
1306 | n/a | # value iterator: |
---|
1307 | n/a | values = list(dict.values()) |
---|
1308 | n/a | for v in dict.values(): |
---|
1309 | n/a | values.remove(v) |
---|
1310 | n/a | self.assertFalse(values, |
---|
1311 | n/a | "itervalues() did not touch all values") |
---|
1312 | n/a | |
---|
1313 | n/a | def check_weak_destroy_while_iterating(self, dict, objects, iter_name): |
---|
1314 | n/a | n = len(dict) |
---|
1315 | n/a | it = iter(getattr(dict, iter_name)()) |
---|
1316 | n/a | next(it) # Trigger internal iteration |
---|
1317 | n/a | # Destroy an object |
---|
1318 | n/a | del objects[-1] |
---|
1319 | n/a | gc.collect() # just in case |
---|
1320 | n/a | # We have removed either the first consumed object, or another one |
---|
1321 | n/a | self.assertIn(len(list(it)), [len(objects), len(objects) - 1]) |
---|
1322 | n/a | del it |
---|
1323 | n/a | # The removal has been committed |
---|
1324 | n/a | self.assertEqual(len(dict), n - 1) |
---|
1325 | n/a | |
---|
1326 | n/a | def check_weak_destroy_and_mutate_while_iterating(self, dict, testcontext): |
---|
1327 | n/a | # Check that we can explicitly mutate the weak dict without |
---|
1328 | n/a | # interfering with delayed removal. |
---|
1329 | n/a | # `testcontext` should create an iterator, destroy one of the |
---|
1330 | n/a | # weakref'ed objects and then return a new key/value pair corresponding |
---|
1331 | n/a | # to the destroyed object. |
---|
1332 | n/a | with testcontext() as (k, v): |
---|
1333 | n/a | self.assertNotIn(k, dict) |
---|
1334 | n/a | with testcontext() as (k, v): |
---|
1335 | n/a | self.assertRaises(KeyError, dict.__delitem__, k) |
---|
1336 | n/a | self.assertNotIn(k, dict) |
---|
1337 | n/a | with testcontext() as (k, v): |
---|
1338 | n/a | self.assertRaises(KeyError, dict.pop, k) |
---|
1339 | n/a | self.assertNotIn(k, dict) |
---|
1340 | n/a | with testcontext() as (k, v): |
---|
1341 | n/a | dict[k] = v |
---|
1342 | n/a | self.assertEqual(dict[k], v) |
---|
1343 | n/a | ddict = copy.copy(dict) |
---|
1344 | n/a | with testcontext() as (k, v): |
---|
1345 | n/a | dict.update(ddict) |
---|
1346 | n/a | self.assertEqual(dict, ddict) |
---|
1347 | n/a | with testcontext() as (k, v): |
---|
1348 | n/a | dict.clear() |
---|
1349 | n/a | self.assertEqual(len(dict), 0) |
---|
1350 | n/a | |
---|
1351 | n/a | def check_weak_del_and_len_while_iterating(self, dict, testcontext): |
---|
1352 | n/a | # Check that len() works when both iterating and removing keys |
---|
1353 | n/a | # explicitly through various means (.pop(), .clear()...), while |
---|
1354 | n/a | # implicit mutation is deferred because an iterator is alive. |
---|
1355 | n/a | # (each call to testcontext() should schedule one item for removal |
---|
1356 | n/a | # for this test to work properly) |
---|
1357 | n/a | o = Object(123456) |
---|
1358 | n/a | with testcontext(): |
---|
1359 | n/a | n = len(dict) |
---|
1360 | n/a | # Since underlaying dict is ordered, first item is popped |
---|
1361 | n/a | dict.pop(next(dict.keys())) |
---|
1362 | n/a | self.assertEqual(len(dict), n - 1) |
---|
1363 | n/a | dict[o] = o |
---|
1364 | n/a | self.assertEqual(len(dict), n) |
---|
1365 | n/a | # last item in objects is removed from dict in context shutdown |
---|
1366 | n/a | with testcontext(): |
---|
1367 | n/a | self.assertEqual(len(dict), n - 1) |
---|
1368 | n/a | # Then, (o, o) is popped |
---|
1369 | n/a | dict.popitem() |
---|
1370 | n/a | self.assertEqual(len(dict), n - 2) |
---|
1371 | n/a | with testcontext(): |
---|
1372 | n/a | self.assertEqual(len(dict), n - 3) |
---|
1373 | n/a | del dict[next(dict.keys())] |
---|
1374 | n/a | self.assertEqual(len(dict), n - 4) |
---|
1375 | n/a | with testcontext(): |
---|
1376 | n/a | self.assertEqual(len(dict), n - 5) |
---|
1377 | n/a | dict.popitem() |
---|
1378 | n/a | self.assertEqual(len(dict), n - 6) |
---|
1379 | n/a | with testcontext(): |
---|
1380 | n/a | dict.clear() |
---|
1381 | n/a | self.assertEqual(len(dict), 0) |
---|
1382 | n/a | self.assertEqual(len(dict), 0) |
---|
1383 | n/a | |
---|
1384 | n/a | def test_weak_keys_destroy_while_iterating(self): |
---|
1385 | n/a | # Issue #7105: iterators shouldn't crash when a key is implicitly removed |
---|
1386 | n/a | dict, objects = self.make_weak_keyed_dict() |
---|
1387 | n/a | self.check_weak_destroy_while_iterating(dict, objects, 'keys') |
---|
1388 | n/a | self.check_weak_destroy_while_iterating(dict, objects, 'items') |
---|
1389 | n/a | self.check_weak_destroy_while_iterating(dict, objects, 'values') |
---|
1390 | n/a | self.check_weak_destroy_while_iterating(dict, objects, 'keyrefs') |
---|
1391 | n/a | dict, objects = self.make_weak_keyed_dict() |
---|
1392 | n/a | @contextlib.contextmanager |
---|
1393 | n/a | def testcontext(): |
---|
1394 | n/a | try: |
---|
1395 | n/a | it = iter(dict.items()) |
---|
1396 | n/a | next(it) |
---|
1397 | n/a | # Schedule a key/value for removal and recreate it |
---|
1398 | n/a | v = objects.pop().arg |
---|
1399 | n/a | gc.collect() # just in case |
---|
1400 | n/a | yield Object(v), v |
---|
1401 | n/a | finally: |
---|
1402 | n/a | it = None # should commit all removals |
---|
1403 | n/a | gc.collect() |
---|
1404 | n/a | self.check_weak_destroy_and_mutate_while_iterating(dict, testcontext) |
---|
1405 | n/a | # Issue #21173: len() fragile when keys are both implicitly and |
---|
1406 | n/a | # explicitly removed. |
---|
1407 | n/a | dict, objects = self.make_weak_keyed_dict() |
---|
1408 | n/a | self.check_weak_del_and_len_while_iterating(dict, testcontext) |
---|
1409 | n/a | |
---|
1410 | n/a | def test_weak_values_destroy_while_iterating(self): |
---|
1411 | n/a | # Issue #7105: iterators shouldn't crash when a key is implicitly removed |
---|
1412 | n/a | dict, objects = self.make_weak_valued_dict() |
---|
1413 | n/a | self.check_weak_destroy_while_iterating(dict, objects, 'keys') |
---|
1414 | n/a | self.check_weak_destroy_while_iterating(dict, objects, 'items') |
---|
1415 | n/a | self.check_weak_destroy_while_iterating(dict, objects, 'values') |
---|
1416 | n/a | self.check_weak_destroy_while_iterating(dict, objects, 'itervaluerefs') |
---|
1417 | n/a | self.check_weak_destroy_while_iterating(dict, objects, 'valuerefs') |
---|
1418 | n/a | dict, objects = self.make_weak_valued_dict() |
---|
1419 | n/a | @contextlib.contextmanager |
---|
1420 | n/a | def testcontext(): |
---|
1421 | n/a | try: |
---|
1422 | n/a | it = iter(dict.items()) |
---|
1423 | n/a | next(it) |
---|
1424 | n/a | # Schedule a key/value for removal and recreate it |
---|
1425 | n/a | k = objects.pop().arg |
---|
1426 | n/a | gc.collect() # just in case |
---|
1427 | n/a | yield k, Object(k) |
---|
1428 | n/a | finally: |
---|
1429 | n/a | it = None # should commit all removals |
---|
1430 | n/a | gc.collect() |
---|
1431 | n/a | self.check_weak_destroy_and_mutate_while_iterating(dict, testcontext) |
---|
1432 | n/a | dict, objects = self.make_weak_valued_dict() |
---|
1433 | n/a | self.check_weak_del_and_len_while_iterating(dict, testcontext) |
---|
1434 | n/a | |
---|
1435 | n/a | def test_make_weak_keyed_dict_from_dict(self): |
---|
1436 | n/a | o = Object(3) |
---|
1437 | n/a | dict = weakref.WeakKeyDictionary({o:364}) |
---|
1438 | n/a | self.assertEqual(dict[o], 364) |
---|
1439 | n/a | |
---|
1440 | n/a | def test_make_weak_keyed_dict_from_weak_keyed_dict(self): |
---|
1441 | n/a | o = Object(3) |
---|
1442 | n/a | dict = weakref.WeakKeyDictionary({o:364}) |
---|
1443 | n/a | dict2 = weakref.WeakKeyDictionary(dict) |
---|
1444 | n/a | self.assertEqual(dict[o], 364) |
---|
1445 | n/a | |
---|
1446 | n/a | def make_weak_keyed_dict(self): |
---|
1447 | n/a | dict = weakref.WeakKeyDictionary() |
---|
1448 | n/a | objects = list(map(Object, range(self.COUNT))) |
---|
1449 | n/a | for o in objects: |
---|
1450 | n/a | dict[o] = o.arg |
---|
1451 | n/a | return dict, objects |
---|
1452 | n/a | |
---|
1453 | n/a | def test_make_weak_valued_dict_from_dict(self): |
---|
1454 | n/a | o = Object(3) |
---|
1455 | n/a | dict = weakref.WeakValueDictionary({364:o}) |
---|
1456 | n/a | self.assertEqual(dict[364], o) |
---|
1457 | n/a | |
---|
1458 | n/a | def test_make_weak_valued_dict_from_weak_valued_dict(self): |
---|
1459 | n/a | o = Object(3) |
---|
1460 | n/a | dict = weakref.WeakValueDictionary({364:o}) |
---|
1461 | n/a | dict2 = weakref.WeakValueDictionary(dict) |
---|
1462 | n/a | self.assertEqual(dict[364], o) |
---|
1463 | n/a | |
---|
1464 | n/a | def test_make_weak_valued_dict_misc(self): |
---|
1465 | n/a | # errors |
---|
1466 | n/a | self.assertRaises(TypeError, weakref.WeakValueDictionary.__init__) |
---|
1467 | n/a | self.assertRaises(TypeError, weakref.WeakValueDictionary, {}, {}) |
---|
1468 | n/a | self.assertRaises(TypeError, weakref.WeakValueDictionary, (), ()) |
---|
1469 | n/a | # special keyword arguments |
---|
1470 | n/a | o = Object(3) |
---|
1471 | n/a | for kw in 'self', 'dict', 'other', 'iterable': |
---|
1472 | n/a | d = weakref.WeakValueDictionary(**{kw: o}) |
---|
1473 | n/a | self.assertEqual(list(d.keys()), [kw]) |
---|
1474 | n/a | self.assertEqual(d[kw], o) |
---|
1475 | n/a | |
---|
1476 | n/a | def make_weak_valued_dict(self): |
---|
1477 | n/a | dict = weakref.WeakValueDictionary() |
---|
1478 | n/a | objects = list(map(Object, range(self.COUNT))) |
---|
1479 | n/a | for o in objects: |
---|
1480 | n/a | dict[o.arg] = o |
---|
1481 | n/a | return dict, objects |
---|
1482 | n/a | |
---|
1483 | n/a | def check_popitem(self, klass, key1, value1, key2, value2): |
---|
1484 | n/a | weakdict = klass() |
---|
1485 | n/a | weakdict[key1] = value1 |
---|
1486 | n/a | weakdict[key2] = value2 |
---|
1487 | n/a | self.assertEqual(len(weakdict), 2) |
---|
1488 | n/a | k, v = weakdict.popitem() |
---|
1489 | n/a | self.assertEqual(len(weakdict), 1) |
---|
1490 | n/a | if k is key1: |
---|
1491 | n/a | self.assertIs(v, value1) |
---|
1492 | n/a | else: |
---|
1493 | n/a | self.assertIs(v, value2) |
---|
1494 | n/a | k, v = weakdict.popitem() |
---|
1495 | n/a | self.assertEqual(len(weakdict), 0) |
---|
1496 | n/a | if k is key1: |
---|
1497 | n/a | self.assertIs(v, value1) |
---|
1498 | n/a | else: |
---|
1499 | n/a | self.assertIs(v, value2) |
---|
1500 | n/a | |
---|
1501 | n/a | def test_weak_valued_dict_popitem(self): |
---|
1502 | n/a | self.check_popitem(weakref.WeakValueDictionary, |
---|
1503 | n/a | "key1", C(), "key2", C()) |
---|
1504 | n/a | |
---|
1505 | n/a | def test_weak_keyed_dict_popitem(self): |
---|
1506 | n/a | self.check_popitem(weakref.WeakKeyDictionary, |
---|
1507 | n/a | C(), "value 1", C(), "value 2") |
---|
1508 | n/a | |
---|
1509 | n/a | def check_setdefault(self, klass, key, value1, value2): |
---|
1510 | n/a | self.assertIsNot(value1, value2, |
---|
1511 | n/a | "invalid test" |
---|
1512 | n/a | " -- value parameters must be distinct objects") |
---|
1513 | n/a | weakdict = klass() |
---|
1514 | n/a | o = weakdict.setdefault(key, value1) |
---|
1515 | n/a | self.assertIs(o, value1) |
---|
1516 | n/a | self.assertIn(key, weakdict) |
---|
1517 | n/a | self.assertIs(weakdict.get(key), value1) |
---|
1518 | n/a | self.assertIs(weakdict[key], value1) |
---|
1519 | n/a | |
---|
1520 | n/a | o = weakdict.setdefault(key, value2) |
---|
1521 | n/a | self.assertIs(o, value1) |
---|
1522 | n/a | self.assertIn(key, weakdict) |
---|
1523 | n/a | self.assertIs(weakdict.get(key), value1) |
---|
1524 | n/a | self.assertIs(weakdict[key], value1) |
---|
1525 | n/a | |
---|
1526 | n/a | def test_weak_valued_dict_setdefault(self): |
---|
1527 | n/a | self.check_setdefault(weakref.WeakValueDictionary, |
---|
1528 | n/a | "key", C(), C()) |
---|
1529 | n/a | |
---|
1530 | n/a | def test_weak_keyed_dict_setdefault(self): |
---|
1531 | n/a | self.check_setdefault(weakref.WeakKeyDictionary, |
---|
1532 | n/a | C(), "value 1", "value 2") |
---|
1533 | n/a | |
---|
1534 | n/a | def check_update(self, klass, dict): |
---|
1535 | n/a | # |
---|
1536 | n/a | # This exercises d.update(), len(d), d.keys(), k in d, |
---|
1537 | n/a | # d.get(), d[]. |
---|
1538 | n/a | # |
---|
1539 | n/a | weakdict = klass() |
---|
1540 | n/a | weakdict.update(dict) |
---|
1541 | n/a | self.assertEqual(len(weakdict), len(dict)) |
---|
1542 | n/a | for k in weakdict.keys(): |
---|
1543 | n/a | self.assertIn(k, dict, "mysterious new key appeared in weak dict") |
---|
1544 | n/a | v = dict.get(k) |
---|
1545 | n/a | self.assertIs(v, weakdict[k]) |
---|
1546 | n/a | self.assertIs(v, weakdict.get(k)) |
---|
1547 | n/a | for k in dict.keys(): |
---|
1548 | n/a | self.assertIn(k, weakdict, "original key disappeared in weak dict") |
---|
1549 | n/a | v = dict[k] |
---|
1550 | n/a | self.assertIs(v, weakdict[k]) |
---|
1551 | n/a | self.assertIs(v, weakdict.get(k)) |
---|
1552 | n/a | |
---|
1553 | n/a | def test_weak_valued_dict_update(self): |
---|
1554 | n/a | self.check_update(weakref.WeakValueDictionary, |
---|
1555 | n/a | {1: C(), 'a': C(), C(): C()}) |
---|
1556 | n/a | # errors |
---|
1557 | n/a | self.assertRaises(TypeError, weakref.WeakValueDictionary.update) |
---|
1558 | n/a | d = weakref.WeakValueDictionary() |
---|
1559 | n/a | self.assertRaises(TypeError, d.update, {}, {}) |
---|
1560 | n/a | self.assertRaises(TypeError, d.update, (), ()) |
---|
1561 | n/a | self.assertEqual(list(d.keys()), []) |
---|
1562 | n/a | # special keyword arguments |
---|
1563 | n/a | o = Object(3) |
---|
1564 | n/a | for kw in 'self', 'dict', 'other', 'iterable': |
---|
1565 | n/a | d = weakref.WeakValueDictionary() |
---|
1566 | n/a | d.update(**{kw: o}) |
---|
1567 | n/a | self.assertEqual(list(d.keys()), [kw]) |
---|
1568 | n/a | self.assertEqual(d[kw], o) |
---|
1569 | n/a | |
---|
1570 | n/a | def test_weak_keyed_dict_update(self): |
---|
1571 | n/a | self.check_update(weakref.WeakKeyDictionary, |
---|
1572 | n/a | {C(): 1, C(): 2, C(): 3}) |
---|
1573 | n/a | |
---|
1574 | n/a | def test_weak_keyed_delitem(self): |
---|
1575 | n/a | d = weakref.WeakKeyDictionary() |
---|
1576 | n/a | o1 = Object('1') |
---|
1577 | n/a | o2 = Object('2') |
---|
1578 | n/a | d[o1] = 'something' |
---|
1579 | n/a | d[o2] = 'something' |
---|
1580 | n/a | self.assertEqual(len(d), 2) |
---|
1581 | n/a | del d[o1] |
---|
1582 | n/a | self.assertEqual(len(d), 1) |
---|
1583 | n/a | self.assertEqual(list(d.keys()), [o2]) |
---|
1584 | n/a | |
---|
1585 | n/a | def test_weak_valued_delitem(self): |
---|
1586 | n/a | d = weakref.WeakValueDictionary() |
---|
1587 | n/a | o1 = Object('1') |
---|
1588 | n/a | o2 = Object('2') |
---|
1589 | n/a | d['something'] = o1 |
---|
1590 | n/a | d['something else'] = o2 |
---|
1591 | n/a | self.assertEqual(len(d), 2) |
---|
1592 | n/a | del d['something'] |
---|
1593 | n/a | self.assertEqual(len(d), 1) |
---|
1594 | n/a | self.assertEqual(list(d.items()), [('something else', o2)]) |
---|
1595 | n/a | |
---|
1596 | n/a | def test_weak_keyed_bad_delitem(self): |
---|
1597 | n/a | d = weakref.WeakKeyDictionary() |
---|
1598 | n/a | o = Object('1') |
---|
1599 | n/a | # An attempt to delete an object that isn't there should raise |
---|
1600 | n/a | # KeyError. It didn't before 2.3. |
---|
1601 | n/a | self.assertRaises(KeyError, d.__delitem__, o) |
---|
1602 | n/a | self.assertRaises(KeyError, d.__getitem__, o) |
---|
1603 | n/a | |
---|
1604 | n/a | # If a key isn't of a weakly referencable type, __getitem__ and |
---|
1605 | n/a | # __setitem__ raise TypeError. __delitem__ should too. |
---|
1606 | n/a | self.assertRaises(TypeError, d.__delitem__, 13) |
---|
1607 | n/a | self.assertRaises(TypeError, d.__getitem__, 13) |
---|
1608 | n/a | self.assertRaises(TypeError, d.__setitem__, 13, 13) |
---|
1609 | n/a | |
---|
1610 | n/a | def test_weak_keyed_cascading_deletes(self): |
---|
1611 | n/a | # SF bug 742860. For some reason, before 2.3 __delitem__ iterated |
---|
1612 | n/a | # over the keys via self.data.iterkeys(). If things vanished from |
---|
1613 | n/a | # the dict during this (or got added), that caused a RuntimeError. |
---|
1614 | n/a | |
---|
1615 | n/a | d = weakref.WeakKeyDictionary() |
---|
1616 | n/a | mutate = False |
---|
1617 | n/a | |
---|
1618 | n/a | class C(object): |
---|
1619 | n/a | def __init__(self, i): |
---|
1620 | n/a | self.value = i |
---|
1621 | n/a | def __hash__(self): |
---|
1622 | n/a | return hash(self.value) |
---|
1623 | n/a | def __eq__(self, other): |
---|
1624 | n/a | if mutate: |
---|
1625 | n/a | # Side effect that mutates the dict, by removing the |
---|
1626 | n/a | # last strong reference to a key. |
---|
1627 | n/a | del objs[-1] |
---|
1628 | n/a | return self.value == other.value |
---|
1629 | n/a | |
---|
1630 | n/a | objs = [C(i) for i in range(4)] |
---|
1631 | n/a | for o in objs: |
---|
1632 | n/a | d[o] = o.value |
---|
1633 | n/a | del o # now the only strong references to keys are in objs |
---|
1634 | n/a | # Find the order in which iterkeys sees the keys. |
---|
1635 | n/a | objs = list(d.keys()) |
---|
1636 | n/a | # Reverse it, so that the iteration implementation of __delitem__ |
---|
1637 | n/a | # has to keep looping to find the first object we delete. |
---|
1638 | n/a | objs.reverse() |
---|
1639 | n/a | |
---|
1640 | n/a | # Turn on mutation in C.__eq__. The first time thru the loop, |
---|
1641 | n/a | # under the iterkeys() business the first comparison will delete |
---|
1642 | n/a | # the last item iterkeys() would see, and that causes a |
---|
1643 | n/a | # RuntimeError: dictionary changed size during iteration |
---|
1644 | n/a | # when the iterkeys() loop goes around to try comparing the next |
---|
1645 | n/a | # key. After this was fixed, it just deletes the last object *our* |
---|
1646 | n/a | # "for o in obj" loop would have gotten to. |
---|
1647 | n/a | mutate = True |
---|
1648 | n/a | count = 0 |
---|
1649 | n/a | for o in objs: |
---|
1650 | n/a | count += 1 |
---|
1651 | n/a | del d[o] |
---|
1652 | n/a | self.assertEqual(len(d), 0) |
---|
1653 | n/a | self.assertEqual(count, 2) |
---|
1654 | n/a | |
---|
1655 | n/a | def test_make_weak_valued_dict_repr(self): |
---|
1656 | n/a | dict = weakref.WeakValueDictionary() |
---|
1657 | n/a | self.assertRegex(repr(dict), '<WeakValueDictionary at 0x.*>') |
---|
1658 | n/a | |
---|
1659 | n/a | def test_make_weak_keyed_dict_repr(self): |
---|
1660 | n/a | dict = weakref.WeakKeyDictionary() |
---|
1661 | n/a | self.assertRegex(repr(dict), '<WeakKeyDictionary at 0x.*>') |
---|
1662 | n/a | |
---|
1663 | n/a | def test_threaded_weak_valued_setdefault(self): |
---|
1664 | n/a | d = weakref.WeakValueDictionary() |
---|
1665 | n/a | with collect_in_thread(): |
---|
1666 | n/a | for i in range(100000): |
---|
1667 | n/a | x = d.setdefault(10, RefCycle()) |
---|
1668 | n/a | self.assertIsNot(x, None) # we never put None in there! |
---|
1669 | n/a | del x |
---|
1670 | n/a | |
---|
1671 | n/a | def test_threaded_weak_valued_pop(self): |
---|
1672 | n/a | d = weakref.WeakValueDictionary() |
---|
1673 | n/a | with collect_in_thread(): |
---|
1674 | n/a | for i in range(100000): |
---|
1675 | n/a | d[10] = RefCycle() |
---|
1676 | n/a | x = d.pop(10, 10) |
---|
1677 | n/a | self.assertIsNot(x, None) # we never put None in there! |
---|
1678 | n/a | |
---|
1679 | n/a | def test_threaded_weak_valued_consistency(self): |
---|
1680 | n/a | # Issue #28427: old keys should not remove new values from |
---|
1681 | n/a | # WeakValueDictionary when collecting from another thread. |
---|
1682 | n/a | d = weakref.WeakValueDictionary() |
---|
1683 | n/a | with collect_in_thread(): |
---|
1684 | n/a | for i in range(200000): |
---|
1685 | n/a | o = RefCycle() |
---|
1686 | n/a | d[10] = o |
---|
1687 | n/a | # o is still alive, so the dict can't be empty |
---|
1688 | n/a | self.assertEqual(len(d), 1) |
---|
1689 | n/a | o = None # lose ref |
---|
1690 | n/a | |
---|
1691 | n/a | |
---|
1692 | n/a | from test import mapping_tests |
---|
1693 | n/a | |
---|
1694 | n/a | class WeakValueDictionaryTestCase(mapping_tests.BasicTestMappingProtocol): |
---|
1695 | n/a | """Check that WeakValueDictionary conforms to the mapping protocol""" |
---|
1696 | n/a | __ref = {"key1":Object(1), "key2":Object(2), "key3":Object(3)} |
---|
1697 | n/a | type2test = weakref.WeakValueDictionary |
---|
1698 | n/a | def _reference(self): |
---|
1699 | n/a | return self.__ref.copy() |
---|
1700 | n/a | |
---|
1701 | n/a | class WeakKeyDictionaryTestCase(mapping_tests.BasicTestMappingProtocol): |
---|
1702 | n/a | """Check that WeakKeyDictionary conforms to the mapping protocol""" |
---|
1703 | n/a | __ref = {Object("key1"):1, Object("key2"):2, Object("key3"):3} |
---|
1704 | n/a | type2test = weakref.WeakKeyDictionary |
---|
1705 | n/a | def _reference(self): |
---|
1706 | n/a | return self.__ref.copy() |
---|
1707 | n/a | |
---|
1708 | n/a | |
---|
1709 | n/a | class FinalizeTestCase(unittest.TestCase): |
---|
1710 | n/a | |
---|
1711 | n/a | class A: |
---|
1712 | n/a | pass |
---|
1713 | n/a | |
---|
1714 | n/a | def _collect_if_necessary(self): |
---|
1715 | n/a | # we create no ref-cycles so in CPython no gc should be needed |
---|
1716 | n/a | if sys.implementation.name != 'cpython': |
---|
1717 | n/a | support.gc_collect() |
---|
1718 | n/a | |
---|
1719 | n/a | def test_finalize(self): |
---|
1720 | n/a | def add(x,y,z): |
---|
1721 | n/a | res.append(x + y + z) |
---|
1722 | n/a | return x + y + z |
---|
1723 | n/a | |
---|
1724 | n/a | a = self.A() |
---|
1725 | n/a | |
---|
1726 | n/a | res = [] |
---|
1727 | n/a | f = weakref.finalize(a, add, 67, 43, z=89) |
---|
1728 | n/a | self.assertEqual(f.alive, True) |
---|
1729 | n/a | self.assertEqual(f.peek(), (a, add, (67,43), {'z':89})) |
---|
1730 | n/a | self.assertEqual(f(), 199) |
---|
1731 | n/a | self.assertEqual(f(), None) |
---|
1732 | n/a | self.assertEqual(f(), None) |
---|
1733 | n/a | self.assertEqual(f.peek(), None) |
---|
1734 | n/a | self.assertEqual(f.detach(), None) |
---|
1735 | n/a | self.assertEqual(f.alive, False) |
---|
1736 | n/a | self.assertEqual(res, [199]) |
---|
1737 | n/a | |
---|
1738 | n/a | res = [] |
---|
1739 | n/a | f = weakref.finalize(a, add, 67, 43, 89) |
---|
1740 | n/a | self.assertEqual(f.peek(), (a, add, (67,43,89), {})) |
---|
1741 | n/a | self.assertEqual(f.detach(), (a, add, (67,43,89), {})) |
---|
1742 | n/a | self.assertEqual(f(), None) |
---|
1743 | n/a | self.assertEqual(f(), None) |
---|
1744 | n/a | self.assertEqual(f.peek(), None) |
---|
1745 | n/a | self.assertEqual(f.detach(), None) |
---|
1746 | n/a | self.assertEqual(f.alive, False) |
---|
1747 | n/a | self.assertEqual(res, []) |
---|
1748 | n/a | |
---|
1749 | n/a | res = [] |
---|
1750 | n/a | f = weakref.finalize(a, add, x=67, y=43, z=89) |
---|
1751 | n/a | del a |
---|
1752 | n/a | self._collect_if_necessary() |
---|
1753 | n/a | self.assertEqual(f(), None) |
---|
1754 | n/a | self.assertEqual(f(), None) |
---|
1755 | n/a | self.assertEqual(f.peek(), None) |
---|
1756 | n/a | self.assertEqual(f.detach(), None) |
---|
1757 | n/a | self.assertEqual(f.alive, False) |
---|
1758 | n/a | self.assertEqual(res, [199]) |
---|
1759 | n/a | |
---|
1760 | n/a | def test_order(self): |
---|
1761 | n/a | a = self.A() |
---|
1762 | n/a | res = [] |
---|
1763 | n/a | |
---|
1764 | n/a | f1 = weakref.finalize(a, res.append, 'f1') |
---|
1765 | n/a | f2 = weakref.finalize(a, res.append, 'f2') |
---|
1766 | n/a | f3 = weakref.finalize(a, res.append, 'f3') |
---|
1767 | n/a | f4 = weakref.finalize(a, res.append, 'f4') |
---|
1768 | n/a | f5 = weakref.finalize(a, res.append, 'f5') |
---|
1769 | n/a | |
---|
1770 | n/a | # make sure finalizers can keep themselves alive |
---|
1771 | n/a | del f1, f4 |
---|
1772 | n/a | |
---|
1773 | n/a | self.assertTrue(f2.alive) |
---|
1774 | n/a | self.assertTrue(f3.alive) |
---|
1775 | n/a | self.assertTrue(f5.alive) |
---|
1776 | n/a | |
---|
1777 | n/a | self.assertTrue(f5.detach()) |
---|
1778 | n/a | self.assertFalse(f5.alive) |
---|
1779 | n/a | |
---|
1780 | n/a | f5() # nothing because previously unregistered |
---|
1781 | n/a | res.append('A') |
---|
1782 | n/a | f3() # => res.append('f3') |
---|
1783 | n/a | self.assertFalse(f3.alive) |
---|
1784 | n/a | res.append('B') |
---|
1785 | n/a | f3() # nothing because previously called |
---|
1786 | n/a | res.append('C') |
---|
1787 | n/a | del a |
---|
1788 | n/a | self._collect_if_necessary() |
---|
1789 | n/a | # => res.append('f4') |
---|
1790 | n/a | # => res.append('f2') |
---|
1791 | n/a | # => res.append('f1') |
---|
1792 | n/a | self.assertFalse(f2.alive) |
---|
1793 | n/a | res.append('D') |
---|
1794 | n/a | f2() # nothing because previously called by gc |
---|
1795 | n/a | |
---|
1796 | n/a | expected = ['A', 'f3', 'B', 'C', 'f4', 'f2', 'f1', 'D'] |
---|
1797 | n/a | self.assertEqual(res, expected) |
---|
1798 | n/a | |
---|
1799 | n/a | def test_all_freed(self): |
---|
1800 | n/a | # we want a weakrefable subclass of weakref.finalize |
---|
1801 | n/a | class MyFinalizer(weakref.finalize): |
---|
1802 | n/a | pass |
---|
1803 | n/a | |
---|
1804 | n/a | a = self.A() |
---|
1805 | n/a | res = [] |
---|
1806 | n/a | def callback(): |
---|
1807 | n/a | res.append(123) |
---|
1808 | n/a | f = MyFinalizer(a, callback) |
---|
1809 | n/a | |
---|
1810 | n/a | wr_callback = weakref.ref(callback) |
---|
1811 | n/a | wr_f = weakref.ref(f) |
---|
1812 | n/a | del callback, f |
---|
1813 | n/a | |
---|
1814 | n/a | self.assertIsNotNone(wr_callback()) |
---|
1815 | n/a | self.assertIsNotNone(wr_f()) |
---|
1816 | n/a | |
---|
1817 | n/a | del a |
---|
1818 | n/a | self._collect_if_necessary() |
---|
1819 | n/a | |
---|
1820 | n/a | self.assertIsNone(wr_callback()) |
---|
1821 | n/a | self.assertIsNone(wr_f()) |
---|
1822 | n/a | self.assertEqual(res, [123]) |
---|
1823 | n/a | |
---|
1824 | n/a | @classmethod |
---|
1825 | n/a | def run_in_child(cls): |
---|
1826 | n/a | def error(): |
---|
1827 | n/a | # Create an atexit finalizer from inside a finalizer called |
---|
1828 | n/a | # at exit. This should be the next to be run. |
---|
1829 | n/a | g1 = weakref.finalize(cls, print, 'g1') |
---|
1830 | n/a | print('f3 error') |
---|
1831 | n/a | 1/0 |
---|
1832 | n/a | |
---|
1833 | n/a | # cls should stay alive till atexit callbacks run |
---|
1834 | n/a | f1 = weakref.finalize(cls, print, 'f1', _global_var) |
---|
1835 | n/a | f2 = weakref.finalize(cls, print, 'f2', _global_var) |
---|
1836 | n/a | f3 = weakref.finalize(cls, error) |
---|
1837 | n/a | f4 = weakref.finalize(cls, print, 'f4', _global_var) |
---|
1838 | n/a | |
---|
1839 | n/a | assert f1.atexit == True |
---|
1840 | n/a | f2.atexit = False |
---|
1841 | n/a | assert f3.atexit == True |
---|
1842 | n/a | assert f4.atexit == True |
---|
1843 | n/a | |
---|
1844 | n/a | def test_atexit(self): |
---|
1845 | n/a | prog = ('from test.test_weakref import FinalizeTestCase;'+ |
---|
1846 | n/a | 'FinalizeTestCase.run_in_child()') |
---|
1847 | n/a | rc, out, err = script_helper.assert_python_ok('-c', prog) |
---|
1848 | n/a | out = out.decode('ascii').splitlines() |
---|
1849 | n/a | self.assertEqual(out, ['f4 foobar', 'f3 error', 'g1', 'f1 foobar']) |
---|
1850 | n/a | self.assertTrue(b'ZeroDivisionError' in err) |
---|
1851 | n/a | |
---|
1852 | n/a | |
---|
1853 | n/a | libreftest = """ Doctest for examples in the library reference: weakref.rst |
---|
1854 | n/a | |
---|
1855 | n/a | >>> import weakref |
---|
1856 | n/a | >>> class Dict(dict): |
---|
1857 | n/a | ... pass |
---|
1858 | n/a | ... |
---|
1859 | n/a | >>> obj = Dict(red=1, green=2, blue=3) # this object is weak referencable |
---|
1860 | n/a | >>> r = weakref.ref(obj) |
---|
1861 | n/a | >>> print(r() is obj) |
---|
1862 | n/a | True |
---|
1863 | n/a | |
---|
1864 | n/a | >>> import weakref |
---|
1865 | n/a | >>> class Object: |
---|
1866 | n/a | ... pass |
---|
1867 | n/a | ... |
---|
1868 | n/a | >>> o = Object() |
---|
1869 | n/a | >>> r = weakref.ref(o) |
---|
1870 | n/a | >>> o2 = r() |
---|
1871 | n/a | >>> o is o2 |
---|
1872 | n/a | True |
---|
1873 | n/a | >>> del o, o2 |
---|
1874 | n/a | >>> print(r()) |
---|
1875 | n/a | None |
---|
1876 | n/a | |
---|
1877 | n/a | >>> import weakref |
---|
1878 | n/a | >>> class ExtendedRef(weakref.ref): |
---|
1879 | n/a | ... def __init__(self, ob, callback=None, **annotations): |
---|
1880 | n/a | ... super().__init__(ob, callback) |
---|
1881 | n/a | ... self.__counter = 0 |
---|
1882 | n/a | ... for k, v in annotations.items(): |
---|
1883 | n/a | ... setattr(self, k, v) |
---|
1884 | n/a | ... def __call__(self): |
---|
1885 | n/a | ... '''Return a pair containing the referent and the number of |
---|
1886 | n/a | ... times the reference has been called. |
---|
1887 | n/a | ... ''' |
---|
1888 | n/a | ... ob = super().__call__() |
---|
1889 | n/a | ... if ob is not None: |
---|
1890 | n/a | ... self.__counter += 1 |
---|
1891 | n/a | ... ob = (ob, self.__counter) |
---|
1892 | n/a | ... return ob |
---|
1893 | n/a | ... |
---|
1894 | n/a | >>> class A: # not in docs from here, just testing the ExtendedRef |
---|
1895 | n/a | ... pass |
---|
1896 | n/a | ... |
---|
1897 | n/a | >>> a = A() |
---|
1898 | n/a | >>> r = ExtendedRef(a, foo=1, bar="baz") |
---|
1899 | n/a | >>> r.foo |
---|
1900 | n/a | 1 |
---|
1901 | n/a | >>> r.bar |
---|
1902 | n/a | 'baz' |
---|
1903 | n/a | >>> r()[1] |
---|
1904 | n/a | 1 |
---|
1905 | n/a | >>> r()[1] |
---|
1906 | n/a | 2 |
---|
1907 | n/a | >>> r()[0] is a |
---|
1908 | n/a | True |
---|
1909 | n/a | |
---|
1910 | n/a | |
---|
1911 | n/a | >>> import weakref |
---|
1912 | n/a | >>> _id2obj_dict = weakref.WeakValueDictionary() |
---|
1913 | n/a | >>> def remember(obj): |
---|
1914 | n/a | ... oid = id(obj) |
---|
1915 | n/a | ... _id2obj_dict[oid] = obj |
---|
1916 | n/a | ... return oid |
---|
1917 | n/a | ... |
---|
1918 | n/a | >>> def id2obj(oid): |
---|
1919 | n/a | ... return _id2obj_dict[oid] |
---|
1920 | n/a | ... |
---|
1921 | n/a | >>> a = A() # from here, just testing |
---|
1922 | n/a | >>> a_id = remember(a) |
---|
1923 | n/a | >>> id2obj(a_id) is a |
---|
1924 | n/a | True |
---|
1925 | n/a | >>> del a |
---|
1926 | n/a | >>> try: |
---|
1927 | n/a | ... id2obj(a_id) |
---|
1928 | n/a | ... except KeyError: |
---|
1929 | n/a | ... print('OK') |
---|
1930 | n/a | ... else: |
---|
1931 | n/a | ... print('WeakValueDictionary error') |
---|
1932 | n/a | OK |
---|
1933 | n/a | |
---|
1934 | n/a | """ |
---|
1935 | n/a | |
---|
1936 | n/a | __test__ = {'libreftest' : libreftest} |
---|
1937 | n/a | |
---|
1938 | n/a | def test_main(): |
---|
1939 | n/a | support.run_unittest( |
---|
1940 | n/a | ReferencesTestCase, |
---|
1941 | n/a | WeakMethodTestCase, |
---|
1942 | n/a | MappingTestCase, |
---|
1943 | n/a | WeakValueDictionaryTestCase, |
---|
1944 | n/a | WeakKeyDictionaryTestCase, |
---|
1945 | n/a | SubclassableWeakrefTestCase, |
---|
1946 | n/a | FinalizeTestCase, |
---|
1947 | n/a | ) |
---|
1948 | n/a | support.run_doctest(sys.modules[__name__]) |
---|
1949 | n/a | |
---|
1950 | n/a | |
---|
1951 | n/a | if __name__ == "__main__": |
---|
1952 | n/a | test_main() |
---|