blob: fd795085a5cd400e9fda91facd355166904e0053 [file] [log] [blame]
Antoine Pitrou14709142017-12-31 22:35:22 +01001import re
Antoine Pitrouacc8cf22014-07-04 20:24:13 -04002import types
Antoine Pitrou9e3d27b2013-08-05 23:35:43 +02003import unittest
4import weakref
5
6from test import support
7
8
9class ClearTest(unittest.TestCase):
10 """
11 Tests for frame.clear().
12 """
13
14 def inner(self, x=5, **kwargs):
15 1/0
16
17 def outer(self, **kwargs):
18 try:
19 self.inner(**kwargs)
20 except ZeroDivisionError as e:
21 exc = e
22 return exc
23
24 def clear_traceback_frames(self, tb):
25 """
26 Clear all frames in a traceback.
27 """
28 while tb is not None:
29 tb.tb_frame.clear()
30 tb = tb.tb_next
31
32 def test_clear_locals(self):
33 class C:
34 pass
35 c = C()
36 wr = weakref.ref(c)
37 exc = self.outer(c=c)
38 del c
39 support.gc_collect()
40 # A reference to c is held through the frames
41 self.assertIsNot(None, wr())
42 self.clear_traceback_frames(exc.__traceback__)
43 support.gc_collect()
44 # The reference was released by .clear()
45 self.assertIs(None, wr())
46
47 def test_clear_generator(self):
48 endly = False
49 def g():
50 nonlocal endly
51 try:
52 yield
53 inner()
54 finally:
55 endly = True
56 gen = g()
57 next(gen)
58 self.assertFalse(endly)
59 # Clearing the frame closes the generator
60 gen.gi_frame.clear()
61 self.assertTrue(endly)
62
63 def test_clear_executing(self):
64 # Attempting to clear an executing frame is forbidden.
65 try:
66 1/0
67 except ZeroDivisionError as e:
68 f = e.__traceback__.tb_frame
69 with self.assertRaises(RuntimeError):
70 f.clear()
71 with self.assertRaises(RuntimeError):
72 f.f_back.clear()
73
74 def test_clear_executing_generator(self):
75 # Attempting to clear an executing generator frame is forbidden.
76 endly = False
77 def g():
78 nonlocal endly
79 try:
80 1/0
81 except ZeroDivisionError as e:
82 f = e.__traceback__.tb_frame
83 with self.assertRaises(RuntimeError):
84 f.clear()
85 with self.assertRaises(RuntimeError):
86 f.f_back.clear()
87 yield f
88 finally:
89 endly = True
90 gen = g()
91 f = next(gen)
92 self.assertFalse(endly)
Antoine Pitroudbfc1292013-08-06 23:05:23 +020093 # Clearing the frame closes the generator
94 f.clear()
95 self.assertTrue(endly)
Antoine Pitrou9e3d27b2013-08-05 23:35:43 +020096
97 @support.cpython_only
98 def test_clear_refcycles(self):
Antoine Pitrou236a5472013-08-06 23:06:59 +020099 # .clear() doesn't leave any refcycle behind
Antoine Pitrou9e3d27b2013-08-05 23:35:43 +0200100 with support.disable_gc():
101 class C:
102 pass
103 c = C()
104 wr = weakref.ref(c)
105 exc = self.outer(c=c)
106 del c
107 self.assertIsNot(None, wr())
108 self.clear_traceback_frames(exc.__traceback__)
109 self.assertIs(None, wr())
110
111
Antoine Pitrouacc8cf22014-07-04 20:24:13 -0400112class FrameLocalsTest(unittest.TestCase):
113 """
114 Tests for the .f_locals attribute.
115 """
116
117 def make_frames(self):
118 def outer():
119 x = 5
120 y = 6
121 def inner():
122 z = x + 2
123 1/0
124 t = 9
125 return inner()
126 try:
127 outer()
128 except ZeroDivisionError as e:
129 tb = e.__traceback__
130 frames = []
131 while tb:
132 frames.append(tb.tb_frame)
133 tb = tb.tb_next
134 return frames
135
136 def test_locals(self):
137 f, outer, inner = self.make_frames()
138 outer_locals = outer.f_locals
139 self.assertIsInstance(outer_locals.pop('inner'), types.FunctionType)
140 self.assertEqual(outer_locals, {'x': 5, 'y': 6})
141 inner_locals = inner.f_locals
142 self.assertEqual(inner_locals, {'x': 5, 'z': 7})
143
144 def test_clear_locals(self):
145 # Test f_locals after clear() (issue #21897)
146 f, outer, inner = self.make_frames()
147 outer.clear()
148 inner.clear()
149 self.assertEqual(outer.f_locals, {})
150 self.assertEqual(inner.f_locals, {})
151
152 def test_locals_clear_locals(self):
153 # Test f_locals before and after clear() (to exercise caching)
154 f, outer, inner = self.make_frames()
155 outer.f_locals
156 inner.f_locals
157 outer.clear()
158 inner.clear()
159 self.assertEqual(outer.f_locals, {})
160 self.assertEqual(inner.f_locals, {})
161
162
Antoine Pitrou14709142017-12-31 22:35:22 +0100163class ReprTest(unittest.TestCase):
164 """
165 Tests for repr(frame).
166 """
167
168 def test_repr(self):
169 def outer():
170 x = 5
171 y = 6
172 def inner():
173 z = x + 2
174 1/0
175 t = 9
176 return inner()
177
178 offset = outer.__code__.co_firstlineno
179 try:
180 outer()
181 except ZeroDivisionError as e:
182 tb = e.__traceback__
183 frames = []
184 while tb:
185 frames.append(tb.tb_frame)
186 tb = tb.tb_next
187 else:
188 self.fail("should have raised")
189
190 f_this, f_outer, f_inner = frames
191 file_repr = re.escape(repr(__file__))
192 self.assertRegex(repr(f_this),
193 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code test_repr>$"
194 % (file_repr, offset + 23))
195 self.assertRegex(repr(f_outer),
196 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code outer>$"
197 % (file_repr, offset + 7))
198 self.assertRegex(repr(f_inner),
199 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code inner>$"
200 % (file_repr, offset + 5))
201
202
Antoine Pitrou9e3d27b2013-08-05 23:35:43 +0200203if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500204 unittest.main()