• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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