diff options
author | Mark Shannon <mark@hotpy.org> | 2023-07-27 12:32:30 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-27 12:32:30 (GMT) |
commit | 766d2518ae8384c6bd7f82727defeb86847ccf64 (patch) | |
tree | 1831ee92ae897c8236517ede9e860ab26656df3f /Lib | |
parent | f84d77b4e07aeb6241c1ff9932627d3ba059efa8 (diff) | |
download | cpython-766d2518ae8384c6bd7f82727defeb86847ccf64.zip cpython-766d2518ae8384c6bd7f82727defeb86847ccf64.tar.gz cpython-766d2518ae8384c6bd7f82727defeb86847ccf64.tar.bz2 |
GH-106897: Add `RERAISE` event to `sys.monitoring`. (GH-107291)
* Ensures that exception handling events are balanced. Each [re]raise event has a matching unwind/handled event.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_monitoring.py | 230 |
1 files changed, 193 insertions, 37 deletions
diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index f854c58..7538786 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -8,7 +8,7 @@ import sys import textwrap import types import unittest - +import asyncio PAIR = (0,1) @@ -243,7 +243,6 @@ class MonitoringEventsBase(MonitoringTestBase): expected = func.events self.assertEqual(events, expected) - class MonitoringEventsTest(MonitoringEventsBase, unittest.TestCase): def test_just_pass(self): @@ -632,7 +631,7 @@ class ExceptionRecorder: class CheckEvents(MonitoringTestBase, unittest.TestCase): - def check_events(self, func, expected, tool=TEST_TOOL, recorders=(ExceptionRecorder,)): + def get_events(self, func, tool, recorders): try: self.assertEqual(sys.monitoring._all_events(), {}) event_list = [] @@ -646,19 +645,63 @@ class CheckEvents(MonitoringTestBase, unittest.TestCase): sys.monitoring.set_events(tool, 0) for recorder in recorders: sys.monitoring.register_callback(tool, recorder.event_type, None) - self.assertEqual(event_list, expected) + return event_list finally: sys.monitoring.set_events(tool, 0) for recorder in recorders: sys.monitoring.register_callback(tool, recorder.event_type, None) + def check_events(self, func, expected, tool=TEST_TOOL, recorders=(ExceptionRecorder,)): + events = self.get_events(func, tool, recorders) + if events != expected: + print(events, file = sys.stderr) + self.assertEqual(events, expected) + + def check_balanced(self, func, recorders): + events = self.get_events(func, TEST_TOOL, recorders) + self.assertEqual(len(events)%2, 0) + for r, h in zip(events[::2],events[1::2]): + r0 = r[0] + self.assertIn(r0, ("raise", "reraise")) + h0 = h[0] + self.assertIn(h0, ("handled", "unwind")) + + + class StopiterationRecorder(ExceptionRecorder): event_type = E.STOP_ITERATION -class ExceptionMontoringTest(CheckEvents): +class ReraiseRecorder(ExceptionRecorder): + + event_type = E.RERAISE + + def __call__(self, code, offset, exc): + self.events.append(("reraise", type(exc))) + +class UnwindRecorder(ExceptionRecorder): + + event_type = E.PY_UNWIND + + def __call__(self, *args): + self.events.append(("unwind", None)) + +class ExceptionHandledRecorder(ExceptionRecorder): + + event_type = E.EXCEPTION_HANDLED + + def __call__(self, code, offset, exc): + self.events.append(("handled", type(exc))) - recorder = ExceptionRecorder +class ExceptionMonitoringTest(CheckEvents): + + + exception_recorders = ( + ExceptionRecorder, + ReraiseRecorder, + ExceptionHandledRecorder, + UnwindRecorder + ) def test_simple_try_except(self): @@ -672,6 +715,8 @@ class ExceptionMontoringTest(CheckEvents): self.check_events(func1, [("raise", KeyError)]) + def test_implicit_stop_iteration(self): + def gen(): yield 1 return 2 @@ -682,6 +727,117 @@ class ExceptionMontoringTest(CheckEvents): self.check_events(implicit_stop_iteration, [("raise", StopIteration)], recorders=(StopiterationRecorder,)) + initial = [ + ("raise", ZeroDivisionError), + ("handled", ZeroDivisionError) + ] + + reraise = [ + ("reraise", ZeroDivisionError), + ("handled", ZeroDivisionError) + ] + + def test_explicit_reraise(self): + + def func(): + try: + try: + 1/0 + except: + raise + except: + pass + + self.check_balanced( + func, + recorders = self.exception_recorders) + + def test_explicit_reraise_named(self): + + def func(): + try: + try: + 1/0 + except Exception as ex: + raise + except: + pass + + self.check_balanced( + func, + recorders = self.exception_recorders) + + def test_implicit_reraise(self): + + def func(): + try: + try: + 1/0 + except ValueError: + pass + except: + pass + + self.check_balanced( + func, + recorders = self.exception_recorders) + + + def test_implicit_reraise_named(self): + + def func(): + try: + try: + 1/0 + except ValueError as ex: + pass + except: + pass + + self.check_balanced( + func, + recorders = self.exception_recorders) + + def test_try_finally(self): + + def func(): + try: + try: + 1/0 + finally: + pass + except: + pass + + self.check_balanced( + func, + recorders = self.exception_recorders) + + def test_async_for(self): + + def func(): + + async def async_generator(): + for i in range(1): + raise ZeroDivisionError + yield i + + async def async_loop(): + try: + async for item in async_generator(): + pass + except Exception: + pass + + try: + async_loop().send(None) + except StopIteration: + pass + + self.check_balanced( + func, + recorders = self.exception_recorders) + class LineRecorder: event_type = E.LINE @@ -733,12 +889,12 @@ class TestManyEvents(CheckEvents): line3 = 3 self.check_events(func1, recorders = MANY_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('call', 'func1', sys.monitoring.MISSING), ('line', 'func1', 1), ('line', 'func1', 2), ('line', 'func1', 3), - ('line', 'check_events', 11), + ('line', 'get_events', 11), ('call', 'set_events', 2)]) def test_c_call(self): @@ -749,14 +905,14 @@ class TestManyEvents(CheckEvents): line3 = 3 self.check_events(func2, recorders = MANY_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('call', 'func2', sys.monitoring.MISSING), ('line', 'func2', 1), ('line', 'func2', 2), ('call', 'append', [2]), ('C return', 'append', [2]), ('line', 'func2', 3), - ('line', 'check_events', 11), + ('line', 'get_events', 11), ('call', 'set_events', 2)]) def test_try_except(self): @@ -770,7 +926,7 @@ class TestManyEvents(CheckEvents): line = 6 self.check_events(func3, recorders = MANY_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('call', 'func3', sys.monitoring.MISSING), ('line', 'func3', 1), ('line', 'func3', 2), @@ -779,7 +935,7 @@ class TestManyEvents(CheckEvents): ('line', 'func3', 4), ('line', 'func3', 5), ('line', 'func3', 6), - ('line', 'check_events', 11), + ('line', 'get_events', 11), ('call', 'set_events', 2)]) class InstructionRecorder: @@ -791,7 +947,7 @@ class InstructionRecorder: def __call__(self, code, offset): # Filter out instructions in check_events to lower noise - if code.co_name != "check_events": + if code.co_name != "get_events": self.events.append(("instruction", code.co_name, offset)) @@ -808,7 +964,7 @@ class TestLineAndInstructionEvents(CheckEvents): line3 = 3 self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('line', 'func1', 1), ('instruction', 'func1', 2), ('instruction', 'func1', 4), @@ -819,7 +975,7 @@ class TestLineAndInstructionEvents(CheckEvents): ('instruction', 'func1', 10), ('instruction', 'func1', 12), ('instruction', 'func1', 14), - ('line', 'check_events', 11)]) + ('line', 'get_events', 11)]) def test_c_call(self): @@ -829,7 +985,7 @@ class TestLineAndInstructionEvents(CheckEvents): line3 = 3 self.check_events(func2, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('line', 'func2', 1), ('instruction', 'func2', 2), ('instruction', 'func2', 4), @@ -843,7 +999,7 @@ class TestLineAndInstructionEvents(CheckEvents): ('instruction', 'func2', 40), ('instruction', 'func2', 42), ('instruction', 'func2', 44), - ('line', 'check_events', 11)]) + ('line', 'get_events', 11)]) def test_try_except(self): @@ -856,7 +1012,7 @@ class TestLineAndInstructionEvents(CheckEvents): line = 6 self.check_events(func3, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('line', 'func3', 1), ('instruction', 'func3', 2), ('line', 'func3', 2), @@ -876,7 +1032,7 @@ class TestLineAndInstructionEvents(CheckEvents): ('instruction', 'func3', 30), ('instruction', 'func3', 32), ('instruction', 'func3', 34), - ('line', 'check_events', 11)]) + ('line', 'get_events', 11)]) def test_with_restart(self): def func1(): @@ -885,7 +1041,7 @@ class TestLineAndInstructionEvents(CheckEvents): line3 = 3 self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('line', 'func1', 1), ('instruction', 'func1', 2), ('instruction', 'func1', 4), @@ -896,12 +1052,12 @@ class TestLineAndInstructionEvents(CheckEvents): ('instruction', 'func1', 10), ('instruction', 'func1', 12), ('instruction', 'func1', 14), - ('line', 'check_events', 11)]) + ('line', 'get_events', 11)]) sys.monitoring.restart_events() self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('line', 'func1', 1), ('instruction', 'func1', 2), ('instruction', 'func1', 4), @@ -912,7 +1068,7 @@ class TestLineAndInstructionEvents(CheckEvents): ('instruction', 'func1', 10), ('instruction', 'func1', 12), ('instruction', 'func1', 14), - ('line', 'check_events', 11)]) + ('line', 'get_events', 11)]) class TestInstallIncrementallly(MonitoringTestBase, unittest.TestCase): @@ -1114,7 +1270,7 @@ class TestBranchAndJumpEvents(CheckEvents): ('branch', 'func', 2, 2)]) self.check_events(func, recorders = JUMP_BRANCH_AND_LINE_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('line', 'func', 1), ('line', 'func', 2), ('branch', 'func', 2, 2), @@ -1130,7 +1286,7 @@ class TestBranchAndJumpEvents(CheckEvents): ('jump', 'func', 4, 2), ('line', 'func', 2), ('branch', 'func', 2, 2), - ('line', 'check_events', 11)]) + ('line', 'get_events', 11)]) def test_except_star(self): @@ -1149,7 +1305,7 @@ class TestBranchAndJumpEvents(CheckEvents): self.check_events(func, recorders = JUMP_BRANCH_AND_LINE_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('line', 'func', 1), ('line', 'func', 2), ('line', 'func', 3), @@ -1160,10 +1316,10 @@ class TestBranchAndJumpEvents(CheckEvents): ('jump', 'func', 5, 5), ('jump', 'func', 5, '[offset=112]'), ('branch', 'func', '[offset=118]', '[offset=120]'), - ('line', 'check_events', 11)]) + ('line', 'get_events', 11)]) self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('line', 'func', 1), ('line', 'func', 2), ('line', 'func', 3), @@ -1177,7 +1333,7 @@ class TestBranchAndJumpEvents(CheckEvents): ('jump', 'func', 5, '[offset=112]'), ('branch', 'func', '[offset=118]', '[offset=120]'), ('return', None), - ('line', 'check_events', 11)]) + ('line', 'get_events', 11)]) class TestLoadSuperAttr(CheckEvents): RECORDERS = CallRecorder, LineRecorder, CRaiseRecorder, CReturnRecorder @@ -1229,7 +1385,7 @@ class TestLoadSuperAttr(CheckEvents): """ d = self._exec_super(codestr, optimized) expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('call', 'f', sys.monitoring.MISSING), ('line', 'f', 1), ('call', 'method', d["b"]), @@ -1242,7 +1398,7 @@ class TestLoadSuperAttr(CheckEvents): ('call', 'method', 1), ('line', 'method', 1), ('line', 'method', 1), - ('line', 'check_events', 11), + ('line', 'get_events', 11), ('call', 'set_events', 2), ] return d["f"], expected @@ -1280,7 +1436,7 @@ class TestLoadSuperAttr(CheckEvents): """ d = self._exec_super(codestr, optimized) expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('call', 'f', sys.monitoring.MISSING), ('line', 'f', 1), ('line', 'f', 2), @@ -1293,7 +1449,7 @@ class TestLoadSuperAttr(CheckEvents): ('C raise', 'super', 1), ('line', 'f', 3), ('line', 'f', 4), - ('line', 'check_events', 11), + ('line', 'get_events', 11), ('call', 'set_events', 2), ] return d["f"], expected @@ -1321,7 +1477,7 @@ class TestLoadSuperAttr(CheckEvents): """ d = self._exec_super(codestr, optimized) expected = [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('call', 'f', sys.monitoring.MISSING), ('line', 'f', 1), ('call', 'method', d["b"]), @@ -1330,7 +1486,7 @@ class TestLoadSuperAttr(CheckEvents): ('C return', 'super', sys.monitoring.MISSING), ('line', 'method', 2), ('line', 'method', 1), - ('line', 'check_events', 11), + ('line', 'get_events', 11), ('call', 'set_events', 2) ] return d["f"], expected @@ -1355,7 +1511,7 @@ class TestLoadSuperAttr(CheckEvents): def get_expected(name, call_method, ns): repr_arg = 0 if name == "int" else sys.monitoring.MISSING return [ - ('line', 'check_events', 10), + ('line', 'get_events', 10), ('call', 'f', sys.monitoring.MISSING), ('line', 'f', 1), ('call', 'method', ns["c"]), @@ -1368,7 +1524,7 @@ class TestLoadSuperAttr(CheckEvents): ('C return', '__repr__', repr_arg), ] if call_method else [] ), - ('line', 'check_events', 11), + ('line', 'get_events', 11), ('call', 'set_events', 2), ] |