Skip to content

Commit 5817f3e

Browse files
authored
Merge pull request pyutils#356 from TTsangSC/sys_mon_event_fix
FIX: missing line events for `raise` statements and `finally: ...` bodies in Python 3.12+
2 parents 2cfc693 + ce3e2be commit 5817f3e

2 files changed

Lines changed: 71 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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,3 +1026,65 @@ 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+
@pytest.mark.xfail(condition=sys.version_info[:2] == (3, 9),
1032+
reason='Handling of `finally` bugged in Python 3.9')
1033+
def test_profiling_exception():
1034+
"""
1035+
Test that profiling data is reported for:
1036+
- The line raising an exception
1037+
- The last lines in the `except` and `finally` subblocks of a
1038+
`try`-(`except`-)`finally` statement
1039+
1040+
Notes
1041+
-----
1042+
Seems to be bugged for Python 3.9 only; may be related to CPython
1043+
issue #83295.
1044+
"""
1045+
prof = LineProfiler()
1046+
1047+
class MyException(Exception):
1048+
pass
1049+
1050+
@prof
1051+
def func_raise():
1052+
pass
1053+
raise MyException # Raise: raise
1054+
l.append(0)
1055+
1056+
@prof
1057+
def func_try_finally():
1058+
try:
1059+
raise MyException # Try-finally: try
1060+
finally:
1061+
l.append(1) # Try-finally: finally
1062+
1063+
@prof
1064+
def func_try_except_finally(reraise):
1065+
try:
1066+
raise MyException # Try-except-finally: try
1067+
except MyException:
1068+
l.append(2) # Try-except-finally: except
1069+
if reraise:
1070+
raise
1071+
finally:
1072+
l.append(3) # Try-except-finally: finally
1073+
1074+
l = []
1075+
for func in [func_raise, func_try_finally,
1076+
functools.partial(func_try_except_finally, True),
1077+
functools.partial(func_try_except_finally, False)]:
1078+
try:
1079+
func()
1080+
except MyException:
1081+
pass
1082+
result = get_prof_stats(prof)
1083+
assert l == [1, 2, 3, 2, 3]
1084+
for stmt, nhits in [
1085+
('raise', 1), ('try-finally', 1), ('try-except-finally', 2)]:
1086+
for step in stmt.split('-'):
1087+
comment = '# {}: {}'.format(stmt.capitalize(), step)
1088+
line = next(line for line in result.splitlines()
1089+
if line.endswith(comment))
1090+
assert line.split()[1] == str(nhits)

0 commit comments

Comments
 (0)