| import gc |
| import sys |
| import types |
| import unittest |
| import weakref |
| |
| from test import support |
| |
| |
| class ClearTest(unittest.TestCase): |
| """ |
| Tests for frame.clear(). |
| """ |
| |
| def inner(self, x=5, **kwargs): |
| 1/0 |
| |
| def outer(self, **kwargs): |
| try: |
| self.inner(**kwargs) |
| except ZeroDivisionError as e: |
| exc = e |
| return exc |
| |
| def clear_traceback_frames(self, tb): |
| """ |
| Clear all frames in a traceback. |
| """ |
| while tb is not None: |
| tb.tb_frame.clear() |
| tb = tb.tb_next |
| |
| def test_clear_locals(self): |
| class C: |
| pass |
| c = C() |
| wr = weakref.ref(c) |
| exc = self.outer(c=c) |
| del c |
| support.gc_collect() |
| # A reference to c is held through the frames |
| self.assertIsNot(None, wr()) |
| self.clear_traceback_frames(exc.__traceback__) |
| support.gc_collect() |
| # The reference was released by .clear() |
| self.assertIs(None, wr()) |
| |
| def test_clear_generator(self): |
| endly = False |
| def g(): |
| nonlocal endly |
| try: |
| yield |
| inner() |
| finally: |
| endly = True |
| gen = g() |
| next(gen) |
| self.assertFalse(endly) |
| # Clearing the frame closes the generator |
| gen.gi_frame.clear() |
| self.assertTrue(endly) |
| |
| def test_clear_executing(self): |
| # Attempting to clear an executing frame is forbidden. |
| try: |
| 1/0 |
| except ZeroDivisionError as e: |
| f = e.__traceback__.tb_frame |
| with self.assertRaises(RuntimeError): |
| f.clear() |
| with self.assertRaises(RuntimeError): |
| f.f_back.clear() |
| |
| def test_clear_executing_generator(self): |
| # Attempting to clear an executing generator frame is forbidden. |
| endly = False |
| def g(): |
| nonlocal endly |
| try: |
| 1/0 |
| except ZeroDivisionError as e: |
| f = e.__traceback__.tb_frame |
| with self.assertRaises(RuntimeError): |
| f.clear() |
| with self.assertRaises(RuntimeError): |
| f.f_back.clear() |
| yield f |
| finally: |
| endly = True |
| gen = g() |
| f = next(gen) |
| self.assertFalse(endly) |
| # Clearing the frame closes the generator |
| f.clear() |
| self.assertTrue(endly) |
| |
| @support.cpython_only |
| def test_clear_refcycles(self): |
| # .clear() doesn't leave any refcycle behind |
| with support.disable_gc(): |
| class C: |
| pass |
| c = C() |
| wr = weakref.ref(c) |
| exc = self.outer(c=c) |
| del c |
| self.assertIsNot(None, wr()) |
| self.clear_traceback_frames(exc.__traceback__) |
| self.assertIs(None, wr()) |
| |
| |
| class FrameLocalsTest(unittest.TestCase): |
| """ |
| Tests for the .f_locals attribute. |
| """ |
| |
| def make_frames(self): |
| def outer(): |
| x = 5 |
| y = 6 |
| def inner(): |
| z = x + 2 |
| 1/0 |
| t = 9 |
| return inner() |
| try: |
| outer() |
| except ZeroDivisionError as e: |
| tb = e.__traceback__ |
| frames = [] |
| while tb: |
| frames.append(tb.tb_frame) |
| tb = tb.tb_next |
| return frames |
| |
| def test_locals(self): |
| f, outer, inner = self.make_frames() |
| outer_locals = outer.f_locals |
| self.assertIsInstance(outer_locals.pop('inner'), types.FunctionType) |
| self.assertEqual(outer_locals, {'x': 5, 'y': 6}) |
| inner_locals = inner.f_locals |
| self.assertEqual(inner_locals, {'x': 5, 'z': 7}) |
| |
| def test_clear_locals(self): |
| # Test f_locals after clear() (issue #21897) |
| f, outer, inner = self.make_frames() |
| outer.clear() |
| inner.clear() |
| self.assertEqual(outer.f_locals, {}) |
| self.assertEqual(inner.f_locals, {}) |
| |
| def test_locals_clear_locals(self): |
| # Test f_locals before and after clear() (to exercise caching) |
| f, outer, inner = self.make_frames() |
| outer.f_locals |
| inner.f_locals |
| outer.clear() |
| inner.clear() |
| self.assertEqual(outer.f_locals, {}) |
| self.assertEqual(inner.f_locals, {}) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |