Skip to content

Commit c91d09a

Browse files
committed
tests/basics: Add tests for weakref.ref and weakref.finalize.
Signed-off-by: Damien George <damien@micropython.org>
1 parent 74e9457 commit c91d09a

7 files changed

Lines changed: 257 additions & 0 deletions
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Test weakref.finalize() functionality that doesn't require gc.collect().
2+
3+
try:
4+
import weakref
5+
except ImportError:
6+
print("SKIP")
7+
raise SystemExit
8+
9+
# Cannot reference non-heap objects.
10+
for value in (None, False, True, Ellipsis, 0, "", ()):
11+
try:
12+
weakref.finalize(value, lambda: None)
13+
except TypeError:
14+
print(value, "TypeError")
15+
16+
17+
# Convert (obj, func, args, kwargs) so CPython and MicroPython have a chance to match.
18+
def convert_4_tuple(values):
19+
if values is None:
20+
return None
21+
return (type(values[0]).__name__, type(values[1]), values[2], values[3])
22+
23+
24+
class A:
25+
def __str__(self):
26+
return "<A object>"
27+
28+
29+
print("test alive, peek, detach")
30+
a = A()
31+
f = weakref.finalize(a, lambda: None, 1, 2, kwarg=3)
32+
print("alive", f.alive)
33+
print("peek", convert_4_tuple(f.peek()))
34+
print("detach", convert_4_tuple(f.detach()))
35+
print("alive", f.alive)
36+
print("peek", convert_4_tuple(f.peek()))
37+
print("detach", convert_4_tuple(f.detach()))
38+
print("call", f())
39+
a = None
40+
41+
print("test alive, peek, call")
42+
a = A()
43+
f = weakref.finalize(a, lambda *args, **kwargs: (args, kwargs), 1, 2, kwarg=3)
44+
print("alive", f.alive)
45+
print("peek", convert_4_tuple(f.peek()))
46+
print("call", f())
47+
print("alive", f.alive)
48+
print("peek", convert_4_tuple(f.peek()))
49+
print("call", f())
50+
print("detach", convert_4_tuple(f.detach()))
51+
52+
print("test call which raises exception")
53+
a = A()
54+
f = weakref.finalize(a, lambda: 1 / 0)
55+
try:
56+
f()
57+
except ZeroDivisionError as er:
58+
print("call ZeroDivisionError")
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Test weakref.finalize() functionality requiring gc.collect().
2+
3+
try:
4+
import weakref
5+
except ImportError:
6+
print("SKIP")
7+
raise SystemExit
8+
9+
# gc module must be available if weakref is.
10+
import gc
11+
12+
13+
class A:
14+
def __str__(self):
15+
return "<A object>"
16+
17+
18+
def callback(*args, **kwargs):
19+
print("callback({}, {})".format(args, kwargs))
20+
return 42
21+
22+
23+
def test():
24+
print("test basic use of finalize() with a simple callback")
25+
a = A()
26+
f = weakref.finalize(a, callback)
27+
a = None
28+
clean_the_stack = [0, 0, 0, 0]
29+
gc.collect()
30+
print("alive", f.alive)
31+
print("peek", f.peek())
32+
print("detach", f.detach())
33+
print("call", f())
34+
35+
print("test that a callback is passed the correct values")
36+
a = A()
37+
f = weakref.finalize(a, callback, 1, 2, kwarg=3)
38+
a = None
39+
clean_the_stack = [0, 0, 0, 0]
40+
gc.collect()
41+
print("alive", f.alive)
42+
print("peek", f.peek())
43+
print("detach", f.detach())
44+
print("call", f())
45+
46+
print("test that calling the finalizer cancels the finalizer")
47+
a = A()
48+
f = weakref.finalize(a, callback)
49+
print(f())
50+
print(a)
51+
a = None
52+
clean_the_stack = [0, 0, 0, 0]
53+
gc.collect()
54+
55+
print("test that calling detach cancels the finalizer")
56+
a = A()
57+
f = weakref.finalize(a, callback)
58+
print(len(f.detach()))
59+
print(a)
60+
a = None
61+
clean_the_stack = [0, 0, 0, 0]
62+
gc.collect()
63+
64+
print("test that finalize does not get collected before its ref does")
65+
a = A()
66+
weakref.finalize(a, callback)
67+
clean_the_stack = [0, 0, 0, 0]
68+
gc.collect()
69+
print("free a")
70+
a = None
71+
clean_the_stack = [0, 0, 0, 0]
72+
gc.collect()
73+
74+
75+
test()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Test weakref when multiple weak references are active.
2+
#
3+
# This test has different output to CPython due to the order that MicroPython
4+
# executes weak reference callbacks.
5+
6+
try:
7+
import weakref
8+
except ImportError:
9+
print("SKIP")
10+
raise SystemExit
11+
12+
# gc module must be available if weakref is.
13+
import gc
14+
15+
16+
class A:
17+
def __str__(self):
18+
return "<A object>"
19+
20+
21+
def test():
22+
print("test having multiple ref and finalize objects referencing the same thing")
23+
a = A()
24+
r1 = weakref.ref(a, lambda r: print("ref1", r()))
25+
f1 = weakref.finalize(a, lambda: print("finalize1"))
26+
r2 = weakref.ref(a, lambda r: print("ref2", r()))
27+
f2 = weakref.finalize(a, lambda: print("finalize2"))
28+
print(r1(), f1.alive, r2(), f2.alive)
29+
a = None
30+
clean_the_stack = [0, 0, 0, 0]
31+
gc.collect()
32+
33+
34+
test()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
test having multiple ref and finalize objects referencing the same thing
2+
<A object> True <A object> True
3+
finalize2
4+
finalize1
5+
ref2 None
6+
ref1 None

tests/basics/weakref_ref_basic.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Test weakref.ref() functionality that doesn't require gc.collect().
2+
3+
try:
4+
import weakref
5+
except ImportError:
6+
print("SKIP")
7+
raise SystemExit
8+
9+
# Cannot reference non-heap objects.
10+
for value in (None, False, True, Ellipsis, 0, "", ()):
11+
try:
12+
weakref.ref(value)
13+
except TypeError:
14+
print(value, "TypeError")
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Test weakref.ref() functionality requiring gc.collect().
2+
3+
try:
4+
import weakref
5+
except ImportError:
6+
print("SKIP")
7+
raise SystemExit
8+
9+
# gc module must be available if weakref is.
10+
import gc
11+
12+
# Cannot reference non-heap objects.
13+
for value in (None, False, True, Ellipsis, 0, "", ()):
14+
try:
15+
weakref.ref(value)
16+
except TypeError:
17+
print(value, "TypeError")
18+
19+
20+
class A:
21+
def __str__(self):
22+
return "<A object>"
23+
24+
25+
def callback(r):
26+
print("callback", r())
27+
28+
29+
def test():
30+
print("test basic use of ref() with only one argument")
31+
a = A()
32+
r = weakref.ref(a)
33+
print(r())
34+
a = None
35+
clean_the_stack = [0, 0, 0, 0]
36+
gc.collect()
37+
print(r())
38+
39+
print("test use of ref() with a callback")
40+
a = A()
41+
r = weakref.ref(a, callback)
42+
print(r())
43+
a = None
44+
clean_the_stack = [0, 0, 0, 0]
45+
gc.collect()
46+
print(r())
47+
48+
print("test when weakref gets collected before the object it refs")
49+
a = A()
50+
r = weakref.ref(a, callback)
51+
print(r())
52+
r = None
53+
clean_the_stack = [0, 0, 0, 0]
54+
gc.collect()
55+
a = None
56+
57+
print("test a double reference")
58+
a = A()
59+
r1 = weakref.ref(a, callback)
60+
r2 = weakref.ref(a, callback)
61+
print(r1(), r2())
62+
a = None
63+
clean_the_stack = [0, 0, 0, 0]
64+
gc.collect()
65+
print(r1(), r2())
66+
67+
68+
test()

tests/run-tests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@
158158
"webassembly": (
159159
"basics/string_format_modulo.py", # can't print nulls to stdout
160160
"basics/string_strip.py", # can't print nulls to stdout
161+
"basics/weakref_ref_collect.py", # requires custom test due to GC behaviour
162+
"basics/weakref_finalize_collect.py", # requires custom test due to GC behaviour
161163
"extmod/asyncio_basic2.py",
162164
"extmod/asyncio_cancel_self.py",
163165
"extmod/asyncio_current_task.py",

0 commit comments

Comments
 (0)