summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_frame.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_frame.py')
-rw-r--r--Lib/test/test_frame.py168
1 files changed, 168 insertions, 0 deletions
diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py
new file mode 100644
index 0000000..c402ec3
--- /dev/null
+++ b/Lib/test/test_frame.py
@@ -0,0 +1,168 @@
+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, {})
+
+
+def test_main():
+ support.run_unittest(__name__)
+
+if __name__ == "__main__":
+ test_main()