-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathref_cycle_test.py
153 lines (131 loc) · 5.59 KB
/
ref_cycle_test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""Unit test for ref_cycle<>() annotation.
See also ref_cycle_test_util_py.cc for the bindings used in the tests.
"""
import gc
import sys
import unittest
import weakref
from pydrake.common.ref_cycle_test_util import (
NotDynamic, IsDynamic, invalid_arg_index, free_function, ouroboros)
class TestRefCycle(unittest.TestCase):
def check_is_collectable_cycle(self, p0, p1):
# The edges of the cycle are:
# p0 -> p0.__dict__ -> p0._pydrake_ref_cycle_peers \
# -> p1 -> p1.__dict__ -> p1._pydrake_ref_cycle_peers -> p0
# where the object at each _pydrake_ref_cycle_peers is a set.
#
# It is impractical to check the counts of p0 and p1 here because
# callers may hold an arbitrary number of references.
for x in [p0, p1]:
# Check the counts of the internal parts of the cycle.
# Recall that sys.getrefcount() adds 1 to the actual count.
self.assertEqual(sys.getrefcount(x.__dict__), 2)
self.assertEqual(sys.getrefcount(x._pydrake_ref_cycle_peers), 2)
# Check that all parts are tracked by gc.
self.assertTrue(gc.is_tracked(x))
self.assertTrue(gc.is_tracked(x.__dict__))
self.assertTrue(gc.is_tracked(x._pydrake_ref_cycle_peers))
# Check that the peers refer to each other.
self.assertTrue(p1 in p0._pydrake_ref_cycle_peers)
self.assertTrue(p0 in p1._pydrake_ref_cycle_peers)
def check_no_cycle(self, p0, p1):
for x in [p0, p1]:
self.assertFalse(hasattr(x, '_pydrake_ref_cycle_peers'))
def test_invalid_index(self):
with self.assertRaisesRegex(RuntimeError,
"Could not activate ref_cycle.*"):
invalid_arg_index()
def test_ouroboros(self):
# The self-cycle edges are:
# dut -> dut.__dict__ -> dut._pydrake_ref_cycle_peers -> dut
#
# This still passes check_is_collectable_cycle() -- the function just
# does redundant work.
dut = IsDynamic()
returned = ouroboros(dut)
self.assertEqual(returned, dut)
self.assertEqual(len(dut._pydrake_ref_cycle_peers), 1)
self.check_is_collectable_cycle(returned, dut)
def test_free_function(self):
p0 = IsDynamic()
p1 = IsDynamic()
free_function(p0, p1)
self.check_is_collectable_cycle(p0, p1)
def test_not_dynamic_add(self):
dut = NotDynamic()
peer = IsDynamic()
# Un-annotated call is fine.
dut.AddIs(peer)
self.check_no_cycle(dut, peer)
# Annotated call dies because dut is not py::dynamic_attr().
with self.assertRaisesRegex(SystemExit, ".*PyType_IS_GC.*"):
dut.AddIsCycle(peer)
def test_not_dynamic_return(self):
dut = NotDynamic()
# Un-annotated call is fine.
returned = dut.ReturnIs()
self.check_no_cycle(dut, returned)
# Annotated call dies because dut is not py::dynamic_attr().
with self.assertRaisesRegex(SystemExit, ".*PyType_IS_GC.*"):
dut.ReturnIsCycle()
def test_not_dynamic_null(self):
dut = NotDynamic()
# Un-annotated call is fine.
self.assertIsNone(dut.ReturnNullIs())
# Annotated call does not die because one peer is missing.
self.assertIsNone(dut.ReturnNullIsCycle())
def test_is_dynamic_add_not(self):
dut = IsDynamic()
notpeer = NotDynamic()
dut.AddNot(notpeer)
self.check_no_cycle(dut, notpeer)
# Annotated call dies because notpeer is not py::dynamic_attr().
with self.assertRaisesRegex(SystemExit, ".*PyType_IS_GC.*"):
dut.AddNotCycle(notpeer)
def test_is_dynamic_return_not(self):
dut = IsDynamic()
# Un-annotated call is fine.
returned = dut.ReturnNot()
self.check_no_cycle(dut, returned)
# Annotated call dies because return is not py::dynamic_attr().
with self.assertRaisesRegex(SystemExit, ".*PyType_IS_GC.*"):
dut.ReturnNotCycle()
def test_is_dynamic_return_null(self):
dut = IsDynamic()
# Un-annotated call is fine.
self.assertIsNone(dut.ReturnNullNot())
self.assertIsNone(dut.ReturnNullIs())
# Annotated call does not die because one peer is missing.
self.assertIsNone(dut.ReturnNullNotCycle())
self.assertIsNone(dut.ReturnNullIsCycle())
def test_is_dynamic_add_is(self):
dut = IsDynamic()
peer = IsDynamic()
# Un-annotated call does not implement a cycle.
dut.AddIs(peer)
self.check_no_cycle(dut, peer)
# Annotated call produces a collectable cycle.
dut.AddIsCycle(peer)
self.check_is_collectable_cycle(dut, peer)
def test_is_dynamic_return_is(self):
dut = IsDynamic()
# Un-annotated call does not implement a cycle.
returned = dut.ReturnIs()
self.check_no_cycle(dut, returned)
# Annotated call produces a collectable cycle.
returned = dut.ReturnIsCycle()
self.check_is_collectable_cycle(dut, returned)
def test_actual_collection(self):
def make_a_cycle():
dut = IsDynamic()
return dut.ReturnIsCycle()
cycle = make_a_cycle()
finalizer = weakref.finalize(cycle, lambda: None)
# Cycle is alive while we refer to it.
self.assertTrue(finalizer.alive)
del cycle
# Cycle is alive because of the ref_cycle.
self.assertTrue(finalizer.alive)
gc.collect()
# Cycle does not survive garbage collection.
self.assertFalse(finalizer.alive)