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