Skip to content

Commit e4c7368

Browse files
committed
Fix for pyutils#355
line_profiler/_line_profiler.pyx Added handling for `sys.monitoring.events.RAISE` and `.RERAISE` tests/test_line_profiler.py::test_profiling_exception() New test for checking whether `raise` statements and `finally: ...` bodies are properly profiled
1 parent 2cfc693 commit e4c7368

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

line_profiler/_line_profiler.pyx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,9 @@ if CAN_USE_SYS_MONITORING:
191191
events = (mon.get_events(mon.PROFILER_ID)
192192
| mon.events.LINE
193193
| mon.events.PY_RETURN
194-
| mon.events.PY_YIELD)
194+
| mon.events.PY_YIELD
195+
| mon.events.RAISE
196+
| mon.events.RERAISE)
195197
mon.set_events(mon.PROFILER_ID, events)
196198
# TODO: store and/or call previous callbacks, see #334
197199
line_callback = functools.partial(
@@ -203,6 +205,10 @@ if CAN_USE_SYS_MONITORING:
203205
mon.PROFILER_ID, mon.events.PY_RETURN, exit_callback)
204206
mon.register_callback(
205207
mon.PROFILER_ID, mon.events.PY_YIELD, exit_callback)
208+
mon.register_callback(
209+
mon.PROFILER_ID, mon.events.RAISE, exit_callback)
210+
mon.register_callback(
211+
mon.PROFILER_ID, mon.events.RERAISE, exit_callback)
206212

207213
def _sys_monitoring_deregister() -> None:
208214
if not _is_main_thread():
@@ -212,6 +218,8 @@ if CAN_USE_SYS_MONITORING:
212218
mon.register_callback(mon.PROFILER_ID, mon.events.LINE, None)
213219
mon.register_callback(mon.PROFILER_ID, mon.events.PY_RETURN, None)
214220
mon.register_callback(mon.PROFILER_ID, mon.events.PY_YIELD, None)
221+
mon.register_callback(mon.PROFILER_ID, mon.events.RAISE, None)
222+
mon.register_callback(mon.PROFILER_ID, mon.events.RERAISE, None)
215223

216224

217225
def label(code):

tests/test_line_profiler.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,3 +1026,58 @@ def func(n):
10261026
loop_body = next(line for line in result.splitlines()
10271027
if line.endswith('x += n'))
10281028
assert loop_body.split()[1] == str(count)
1029+
1030+
1031+
def test_profiling_exception():
1032+
"""
1033+
Test that profiling data is reported for:
1034+
- The line raising an exception
1035+
- The last lines in the `except` and `finally` subblocks of a
1036+
`try`-(`except`-)`finally` statement
1037+
"""
1038+
prof = LineProfiler()
1039+
1040+
class MyException(Exception):
1041+
pass
1042+
1043+
@prof
1044+
def func_raise():
1045+
pass
1046+
raise MyException # Raise: raise
1047+
l.append(0)
1048+
1049+
@prof
1050+
def func_try_finally():
1051+
try:
1052+
raise MyException # Try-finally: try
1053+
finally:
1054+
l.append(1) # Try-finally: finally
1055+
1056+
@prof
1057+
def func_try_except_finally(reraise):
1058+
try:
1059+
raise MyException # Try-except-finally: try
1060+
except MyException:
1061+
l.append(2) # Try-except-finally: except
1062+
if reraise:
1063+
raise
1064+
finally:
1065+
l.append(3) # Try-except-finally: finally
1066+
1067+
l = []
1068+
for func in [func_raise, func_try_finally,
1069+
functools.partial(func_try_except_finally, True),
1070+
functools.partial(func_try_except_finally, False)]:
1071+
try:
1072+
func()
1073+
except MyException:
1074+
pass
1075+
result = get_prof_stats(prof)
1076+
assert l == [1, 2, 3, 2, 3]
1077+
for stmt, nhits in [
1078+
('raise', 1), ('try-finally', 1), ('try-except-finally', 2)]:
1079+
for step in stmt.split('-'):
1080+
comment = '# {}: {}'.format(stmt.capitalize(), step)
1081+
line = next(line for line in result.splitlines()
1082+
if line.endswith(comment))
1083+
assert line.split()[1] == str(nhits)

0 commit comments

Comments
 (0)