You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Support post_mortem mode via entering specific frames #1103
This would be a continuation from #722. I would need this feature to implement post-mortem debugging for unit tests (nvim-neotest/neotest-python#23).
#722 discusses how the debugger could stop at an exception that would be thrown from an user code to a library code (e.g., pytest) using the "user uncaught" exception breakpoints (#111). However, I don't think it is enough to implement post-mortem debugging, because it will pause too aggressively at exceptions that regularly crosses the boundaries between user and library code as pointed out in #1102.
One quick example would be like this:
deftest_foo():
withpytest.raises(SomeUserExpectedError):
_an_user_code_raises_exception() # <-- (A) The debugger will always stop here even if it's "expected"assert"something has failed"# <---- (B) The debugger should stop here only
Because an exception that is thrown from a user code to the pytest framework will be considered as an "user uncaught" exception, it will hit a breakpoint on the line (A).
New feature: entering specific frames
In another issue #1102, I suggest implementing some filters or condition logics to control exception breakpoints in a more fine-grained manner, but we could also think of a feature to let the debugger enter specific stack frames in a post-mortem mode.
but it does not work as pydevd.settrace does not support stopping at or entering a frame that is not relevant with the current stacktrace (it just says Frame skipped from debugging during step-in.).
Note: PuDB has such a feature!
PuDB allows to enter some specific stack frames obtained from a thrown exception.
Closing this as this is a duplicate of: #723 (note that there's some code on that issue on how to do that with the current version of the debugger -- it uses some internal APIs, and we'd like to have better public APIs for this, but that can probably be used in the meanwhile... that code has been stable for quite a while and there are no plans to change it).
Thanks for the pointer to #723 (comment) --- which I didn't find while searching the issues with the keywords I was using!
Based on the code example you provided and a single function extracted from that (#723 (comment)), I was able to make the debugger enter and stop at the exception raised out of a pytest method.
Below is a proof-of-concept, but a single-file script to demonstrate the behavior:
"""A script to run pytest tests with debugger stopping at exceptions."""fromtypingimportUnionimportpytestdeftest_foo():
x="some local variable"withpytest.raises(ValueError):
raiseValueError("The debugger should not stop here")
assertFalse, "A debugger should stop here"deftest_bar():
assertFalse, "A debugger should stop here AS WELL"defmaybe_debugpy_postmortem(excinfo):
"""Make the debugpy debugger enter and stop at a raised exception. excinfo: A (type(e), e, e.__traceback__) tuple. See sys.exc_info() """importdebugpyifnotdebugpy.is_client_connected():
return# Do not thing if not running with a DAP debuggerimportthreadingimportpydevdpy_db=pydevd.get_global_debugger()
thread=threading.current_thread()
additional_info=py_db.set_additional_thread_info(thread)
additional_info.is_tracing+=1try:
py_db.stop_on_unhandled_exception(py_db, thread, additional_info, excinfo)
finally:
additional_info.is_tracing-=1classPytestDebuggerIntegration:
@staticmethoddefpytest_exception_interact(
node: Union['pytest.Item', 'pytest.Collector'],
call: 'pytest.CallInfo',
report: Union['pytest.CollectReport', 'pytest.TestReport'],
):
# https://docs.pytest.org/en/latest/reference/reference.html#pytest.hookspec.pytest_exception_interactmaybe_debugpy_postmortem(call.excinfo._excinfo)
if__name__=='__main__':
# yapf: disable# pytest.main([__file__], plugins=[]) # <-- Does not hit breakpointspytest.main([__file__], plugins=[PytestDebuggerIntegration]) # <-- The debugger should stop twice # yapf: enable
I might write and publish this as a separate pytest plugin soon, if you don't mind. What do you think?
I might write and publish this as a separate pytest plugin soon, if you don't mind. What do you think?
I think that'd be ok, I just have 1 note:
Don't check for debugpy.is_client_connected():... just check if pydevd.get_global_debugger() returns None or if you can't import pydevd (then it'd work with both debugpy as well as pydevd).
Don't check for debugpy.is_client_connected():... just check if pydevd.get_global_debugger() returns None or if you can't import pydevd (then it'd work with both debugpy as well as pydevd).
Activity
fabioz commentedon Oct 27, 2022
Closing this as this is a duplicate of: #723 (note that there's some code on that issue on how to do that with the current version of the debugger -- it uses some internal APIs, and we'd like to have better public APIs for this, but that can probably be used in the meanwhile... that code has been stable for quite a while and there are no plans to change it).
wookayin commentedon Oct 27, 2022
Thanks for the pointer to #723 (comment) --- which I didn't find while searching the issues with the keywords I was using!
Based on the code example you provided and a single function extracted from that (#723 (comment)), I was able to make the debugger enter and stop at the exception raised out of a pytest method.
Below is a proof-of-concept, but a single-file script to demonstrate the behavior:
I might write and publish this as a separate pytest plugin soon, if you don't mind. What do you think?
fabioz commentedon Oct 27, 2022
I think that'd be ok, I just have 1 note:
Don't check for
debugpy.is_client_connected():... just check ifpydevd.get_global_debugger()returnsNoneor if you can't importpydevd(then it'd work with bothdebugpyas well aspydevd).wookayin commentedon Oct 27, 2022
That's a great point. I have updated the code snippet in #723. Thanks so much!