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_generator(self): 49 endly = False 50 def g(): 51 nonlocal endly 52 try: 53 yield 54 self.inner() 55 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) 94 # Clearing the frame closes the generator 95 f.clear() 96 self.assertTrue(endly) 97 98 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 118 @support.cpython_only 119 def test_clear_refcycles(self): 120 # .clear() doesn't leave any refcycle behind 121 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 133class FrameAttrsTest(unittest.TestCase): 134 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 180 def test_f_lineno_del_segfault(self): 181 f, _, _ = self.make_frames() 182 with self.assertRaises(AttributeError): 183 del f.f_lineno 184 185 186class 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 226if __name__ == "__main__": 227 unittest.main() 228