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