1import re 2import types 3import 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 self.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) 93 # Clearing the frame closes the generator 94 f.clear() 95 self.assertTrue(endly) 96 97 @support.cpython_only 98 def test_clear_refcycles(self): 99 # .clear() doesn't leave any refcycle behind 100 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 112class FrameAttrsTest(unittest.TestCase): 113 114 def make_frames(self): 115 def outer(): 116 x = 5 117 y = 6 118 def inner(): 119 z = x + 2 120 1/0 121 t = 9 122 return inner() 123 try: 124 outer() 125 except ZeroDivisionError as e: 126 tb = e.__traceback__ 127 frames = [] 128 while tb: 129 frames.append(tb.tb_frame) 130 tb = tb.tb_next 131 return frames 132 133 def test_locals(self): 134 f, outer, inner = self.make_frames() 135 outer_locals = outer.f_locals 136 self.assertIsInstance(outer_locals.pop('inner'), types.FunctionType) 137 self.assertEqual(outer_locals, {'x': 5, 'y': 6}) 138 inner_locals = inner.f_locals 139 self.assertEqual(inner_locals, {'x': 5, 'z': 7}) 140 141 def test_clear_locals(self): 142 # Test f_locals after clear() (issue #21897) 143 f, outer, inner = self.make_frames() 144 outer.clear() 145 inner.clear() 146 self.assertEqual(outer.f_locals, {}) 147 self.assertEqual(inner.f_locals, {}) 148 149 def test_locals_clear_locals(self): 150 # Test f_locals before and after clear() (to exercise caching) 151 f, outer, inner = self.make_frames() 152 outer.f_locals 153 inner.f_locals 154 outer.clear() 155 inner.clear() 156 self.assertEqual(outer.f_locals, {}) 157 self.assertEqual(inner.f_locals, {}) 158 159 def test_f_lineno_del_segfault(self): 160 f, _, _ = self.make_frames() 161 with self.assertRaises(AttributeError): 162 del f.f_lineno 163 164 165class ReprTest(unittest.TestCase): 166 """ 167 Tests for repr(frame). 168 """ 169 170 def test_repr(self): 171 def outer(): 172 x = 5 173 y = 6 174 def inner(): 175 z = x + 2 176 1/0 177 t = 9 178 return inner() 179 180 offset = outer.__code__.co_firstlineno 181 try: 182 outer() 183 except ZeroDivisionError as e: 184 tb = e.__traceback__ 185 frames = [] 186 while tb: 187 frames.append(tb.tb_frame) 188 tb = tb.tb_next 189 else: 190 self.fail("should have raised") 191 192 f_this, f_outer, f_inner = frames 193 file_repr = re.escape(repr(__file__)) 194 self.assertRegex(repr(f_this), 195 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code test_repr>$" 196 % (file_repr, offset + 23)) 197 self.assertRegex(repr(f_outer), 198 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code outer>$" 199 % (file_repr, offset + 7)) 200 self.assertRegex(repr(f_inner), 201 r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code inner>$" 202 % (file_repr, offset + 5)) 203 204 205if __name__ == "__main__": 206 unittest.main() 207