blob: 7ac37b6937502e94eeac275c3808c8ff274f7bdc [file] [log] [blame]
Antoine Pitrou14709142017-12-31 22:35:22 +01001import re
Mark Shannonee9f98d2021-01-05 12:04:10 +00002import sys
Antoine Pitrouacc8cf22014-07-04 20:24:13 -04003import types
Antoine Pitrou9e3d27b2013-08-05 23:35:43 +02004import unittest
5import weakref
6
7from test import support
8
9
10class ClearTest(unittest.TestCase):
11 """
12 Tests for frame.clear().
13 """
14
15 def inner(self, x=5, **kwargs):
16 1/0
17
18 def outer(self, **kwargs):
19 try:
20 self.inner(**kwargs)
21 except ZeroDivisionError as e:
22 exc = e
23 return exc
24
25 def clear_traceback_frames(self, tb):
26 """
27 Clear all frames in a traceback.
28 """
29 while tb is not None:
30 tb.tb_frame.clear()
31 tb = tb.tb_next
32
33 def test_clear_locals(self):
34 class C:
35 pass
36 c = C()
37 wr = weakref.ref(c)
38 exc = self.outer(c=c)
39 del c
40 support.gc_collect()
41 # A reference to c is held through the frames
42 self.assertIsNot(None, wr())
43 self.clear_traceback_frames(exc.__traceback__)
44 support.gc_collect()
45 # The reference was released by .clear()
46 self.assertIs(None, wr())
47
48 def test_clear_generator(self):
49 endly = False
50 def g():
51 nonlocal endly
52 try:
53 yield
Furkan Önder719e14d2020-05-01 15:49:35 +030054 self.inner()
Antoine Pitrou9e3d27b2013-08-05 23:35:43 +020055 finally:
56 endly = True
57 gen = g()
58 next(gen)
59 self.assertFalse(endly)
60 # Clearing the frame closes the generator
61 gen.gi_frame.clear()
62 self.assertTrue(endly)
63
64 def test_clear_executing(self):
65 # Attempting to clear an executing frame is forbidden.
66 try:
67 1/0
68 except ZeroDivisionError as e:
69 f = e.__traceback__.tb_frame
70 with self.assertRaises(RuntimeError):
71 f.clear()
72 with self.assertRaises(RuntimeError):
73 f.f_back.clear()
74
75 def test_clear_executing_generator(self):
76 # Attempting to clear an executing generator frame is forbidden.
77 endly = False
78 def g():
79 nonlocal endly
80 try:
81 1/0
82 except ZeroDivisionError as e:
83 f = e.__traceback__.tb_frame
84 with self.assertRaises(RuntimeError):
85 f.clear()
86 with self.assertRaises(RuntimeError):
87 f.f_back.clear()
88 yield f
89 finally:
90 endly = True
91 gen = g()
92 f = next(gen)
93 self.assertFalse(endly)
Antoine Pitroudbfc1292013-08-06 23:05:23 +020094 # Clearing the frame closes the generator
95 f.clear()
96 self.assertTrue(endly)
Antoine Pitrou9e3d27b2013-08-05 23:35:43 +020097
Mark Shannonee9f98d2021-01-05 12:04:10 +000098 def test_lineno_with_tracing(self):
99 def record_line():
100 f = sys._getframe(1)
101 lines.append(f.f_lineno-f.f_code.co_firstlineno)
102
103 def test(trace):
104 record_line()
105 if trace:
106 sys._getframe(0).f_trace = True
107 record_line()
108 record_line()
109
110 expected_lines = [1, 4, 5]
111 lines = []
112 test(False)
113 self.assertEqual(lines, expected_lines)
114 lines = []
115 test(True)
116 self.assertEqual(lines, expected_lines)
117
Antoine Pitrou9e3d27b2013-08-05 23:35:43 +0200118 @support.cpython_only
119 def test_clear_refcycles(self):
Antoine Pitrou236a5472013-08-06 23:06:59 +0200120 # .clear() doesn't leave any refcycle behind
Antoine Pitrou9e3d27b2013-08-05 23:35:43 +0200121 with support.disable_gc():
122 class C:
123 pass
124 c = C()
125 wr = weakref.ref(c)
126 exc = self.outer(c=c)
127 del c
128 self.assertIsNot(None, wr())
129 self.clear_traceback_frames(exc.__traceback__)
130 self.assertIs(None, wr())
131
132
Zackery Spytz842acaa2018-12-17 07:52:45 -0700133class FrameAttrsTest(unittest.TestCase):
Antoine Pitrouacc8cf22014-07-04 20:24:13 -0400134
135 def make_frames(self):
136 def outer():
137 x = 5
138 y = 6
139 def inner():
140 z = x + 2
141 1/0
142 t = 9
143 return inner()
144 try:
145 outer()
146 except ZeroDivisionError as e:
147 tb = e.__traceback__
148 frames = []
149 while tb:
150 frames.append(tb.tb_frame)
151 tb = tb.tb_next
152 return frames
153
154 def test_locals(self):
155 f, outer, inner = self.make_frames()
156 outer_locals = outer.f_locals
157 self.assertIsInstance(outer_locals.pop('inner'), types.FunctionType)
158 self.assertEqual(outer_locals, {'x': 5, 'y': 6})
159 inner_locals = inner.f_locals
160 self.assertEqual(inner_locals, {'x': 5, 'z': 7})
161
162 def test_clear_locals(self):
163 # Test f_locals after clear() (issue #21897)
164 f, outer, inner = self.make_frames()
165 outer.clear()
166 inner.clear()
167 self.assertEqual(outer.f_locals, {})
168 self.assertEqual(inner.f_locals, {})
169
170 def test_locals_clear_locals(self):
171 # Test f_locals before and after clear() (to exercise caching)
172 f, outer, inner = self.make_frames()
173 outer.f_locals
174 inner.f_locals
175 outer.clear()
176 inner.clear()
177 self.assertEqual(outer.f_locals, {})
178 self.assertEqual(inner.f_locals, {})
179
Zackery Spytz842acaa2018-12-17 07:52:45 -0700180 def test_f_lineno_del_segfault(self):
181 f, _, _ = self.make_frames()
182 with self.assertRaises(AttributeError):
183 del f.f_lineno
184
Antoine Pitrouacc8cf22014-07-04 20:24:13 -0400185
Antoine Pitrou14709142017-12-31 22:35:22 +0100186class ReprTest(unittest.TestCase):
187 """
188 Tests for repr(frame).
189 """
190
191 def test_repr(self):
192 def outer():
193 x = 5
194 y = 6
195 def inner():
196 z = x + 2
197 1/0
198 t = 9
199 return inner()
200
201 offset = outer.__code__.co_firstlineno
202 try:
203 outer()
204 except ZeroDivisionError as e:
205 tb = e.__traceback__
206 frames = []
207 while tb:
208 frames.append(tb.tb_frame)
209 tb = tb.tb_next
210 else:
211 self.fail("should have raised")
212
213 f_this, f_outer, f_inner = frames
214 file_repr = re.escape(repr(__file__))
215 self.assertRegex(repr(f_this),
216 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code test_repr>$"
217 % (file_repr, offset + 23))
218 self.assertRegex(repr(f_outer),
219 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code outer>$"
220 % (file_repr, offset + 7))
221 self.assertRegex(repr(f_inner),
222 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code inner>$"
223 % (file_repr, offset + 5))
224
225
Antoine Pitrou9e3d27b2013-08-05 23:35:43 +0200226if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500227 unittest.main()