1import re 2import sys 3import types 4import 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_does_not_clear_specials(self): 49 class C: 50 pass 51 c = C() 52 exc = self.outer(c=c) 53 del c 54 f = exc.__traceback__.tb_frame 55 f.clear() 56 self.assertIsNot(f.f_code, None) 57 self.assertIsNot(f.f_locals, None) 58 self.assertIsNot(f.f_builtins, None) 59 self.assertIsNot(f.f_globals, None) 60 61 def test_clear_generator(self): 62 endly = False 63 def g(): 64 nonlocal endly 65 try: 66 yield 67 self.inner() 68 finally: 69 endly = True 70 gen = g() 71 next(gen) 72 self.assertFalse(endly) 73 # Clearing the frame closes the generator 74 gen.gi_frame.clear() 75 self.assertTrue(endly) 76 77 def test_clear_executing(self): 78 # Attempting to clear an executing frame is forbidden. 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 88 def test_clear_executing_generator(self): 89 # Attempting to clear an executing generator frame is forbidden. 90 endly = False 91 def g(): 92 nonlocal endly 93 try: 94 1/0 95 except ZeroDivisionError as e: 96 f = e.__traceback__.tb_frame 97 with self.assertRaises(RuntimeError): 98 f.clear() 99 with self.assertRaises(RuntimeError): 100 f.f_back.clear() 101 yield f 102 finally: 103 endly = True 104 gen = g() 105 f = next(gen) 106 self.assertFalse(endly) 107 # Clearing the frame closes the generator 108 f.clear() 109 self.assertTrue(endly) 110 111 def test_lineno_with_tracing(self): 112 def record_line(): 113 f = sys._getframe(1) 114 lines.append(f.f_lineno-f.f_code.co_firstlineno) 115 116 def test(trace): 117 record_line() 118 if trace: 119 sys._getframe(0).f_trace = True 120 record_line() 121 record_line() 122 123 expected_lines = [1, 4, 5] 124 lines = [] 125 test(False) 126 self.assertEqual(lines, expected_lines) 127 lines = [] 128 test(True) 129 self.assertEqual(lines, expected_lines) 130 131 @support.cpython_only 132 def test_clear_refcycles(self): 133 # .clear() doesn't leave any refcycle behind 134 with support.disable_gc(): 135 class C: 136 pass 137 c = C() 138 wr = weakref.ref(c) 139 exc = self.outer(c=c) 140 del c 141 self.assertIsNot(None, wr()) 142 self.clear_traceback_frames(exc.__traceback__) 143 self.assertIs(None, wr()) 144 145 146class FrameAttrsTest(unittest.TestCase): 147 148 def make_frames(self): 149 def outer(): 150 x = 5 151 y = 6 152 def inner(): 153 z = x + 2 154 1/0 155 t = 9 156 return inner() 157 try: 158 outer() 159 except ZeroDivisionError as e: 160 tb = e.__traceback__ 161 frames = [] 162 while tb: 163 frames.append(tb.tb_frame) 164 tb = tb.tb_next 165 return frames 166 167 def test_locals(self): 168 f, outer, inner = self.make_frames() 169 outer_locals = outer.f_locals 170 self.assertIsInstance(outer_locals.pop('inner'), types.FunctionType) 171 self.assertEqual(outer_locals, {'x': 5, 'y': 6}) 172 inner_locals = inner.f_locals 173 self.assertEqual(inner_locals, {'x': 5, 'z': 7}) 174 175 def test_clear_locals(self): 176 # Test f_locals after clear() (issue #21897) 177 f, outer, inner = self.make_frames() 178 outer.clear() 179 inner.clear() 180 self.assertEqual(outer.f_locals, {}) 181 self.assertEqual(inner.f_locals, {}) 182 183 def test_locals_clear_locals(self): 184 # Test f_locals before and after clear() (to exercise caching) 185 f, outer, inner = self.make_frames() 186 outer.f_locals 187 inner.f_locals 188 outer.clear() 189 inner.clear() 190 self.assertEqual(outer.f_locals, {}) 191 self.assertEqual(inner.f_locals, {}) 192 193 def test_f_lineno_del_segfault(self): 194 f, _, _ = self.make_frames() 195 with self.assertRaises(AttributeError): 196 del f.f_lineno 197 198 199class ReprTest(unittest.TestCase): 200 """ 201 Tests for repr(frame). 202 """ 203 204 def test_repr(self): 205 def outer(): 206 x = 5 207 y = 6 208 def inner(): 209 z = x + 2 210 1/0 211 t = 9 212 return inner() 213 214 offset = outer.__code__.co_firstlineno 215 try: 216 outer() 217 except ZeroDivisionError as e: 218 tb = e.__traceback__ 219 frames = [] 220 while tb: 221 frames.append(tb.tb_frame) 222 tb = tb.tb_next 223 else: 224 self.fail("should have raised") 225 226 f_this, f_outer, f_inner = frames 227 file_repr = re.escape(repr(__file__)) 228 self.assertRegex(repr(f_this), 229 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code test_repr>$" 230 % (file_repr, offset + 23)) 231 self.assertRegex(repr(f_outer), 232 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code outer>$" 233 % (file_repr, offset + 7)) 234 self.assertRegex(repr(f_inner), 235 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code inner>$" 236 % (file_repr, offset + 5)) 237 238 239if __name__ == "__main__": 240 unittest.main() 241