def test_module_events(pyfile, target, run): @pyfile def module2(): def do_more_things(): print("done") # @bp @pyfile def module1(): import module2 def do_something(): module2.do_more_things() @pyfile def test_code(): import debuggee debuggee.setup() from module1 import do_something do_something() with debug.Session() as session: with run(session, target(test_code)): session.set_breakpoints(module2, all) session.wait_for_stop( "breakpoint", expected_frames=[some.dap.frame(module2, line="bp")] ) # Stack trace after the stop will trigger module events, but they are only # sent after the trace response, so we need to wait for them separately. # The order isn't guaranteed, either, so just wait for any 3 modules. session.timeline.wait_until_realized( Event("module") >> Event("module") >> Event("module") ) modules = { event.body["module"]["name"]: event.body["module"]["path"] for event in session.all_occurrences_of(Event("module")) } assert modules == some.dict.containing( { "__main__": some.path(test_code), "module1": some.path(module1), "module2": some.path(module2), } ) session.request_continue()
def source(path, **kwargs): """Matches DAP Source objects. """ if isinstance(path, py.path.local): path = some.path(path) d = {"path": path} d.update(kwargs) return some.dict.containing(d)
def test_exception_stack(pyfile, target, run, max_frames): @pyfile def code_to_debug(): import debuggee debuggee.setup() def do_something(n): if n <= 0: raise ArithmeticError("bad code") # @unhandled do_something2(n - 1) def do_something2(n): do_something(n - 1) do_something(100) with debug.Session() as session: session.expected_exit_code = some.int max_frames, (min_expected_lines, max_expected_lines) = { "all": (0, (100, 221)), "default": (None, (100, 221)), 10: (10, (10, 21)), }[max_frames] if max_frames is not None: session.config["maxExceptionStackFrames"] = max_frames with run(session, target(code_to_debug)): session.request("setExceptionBreakpoints", {"filters": ["uncaught"]}) stop = session.wait_for_stop( "exception", expected_frames=[some.dap.frame(code_to_debug, line="unhandled")], ) exc_info = session.request("exceptionInfo", {"threadId": stop.thread_id}) expected_exc_info = some.dict.containing({ "exceptionId": str_matching_ArithmeticError, "description": "bad code", "breakMode": "unhandled", "details": some.dict.containing({ "typeName": str_matching_ArithmeticError, "message": "bad code", "source": some.path(code_to_debug), }), }) assert expected_exc_info == exc_info stack_str = exc_info["details"]["stackTrace"] stack_line_count = len(stack_str.split("\n")) assert min_expected_lines <= stack_line_count <= max_expected_lines session.request_continue()
def test_vsc_exception_options_raise_with_except(pyfile, target, run, raised, uncaught): @pyfile def code_to_debug(): import debuggee debuggee.setup() def raise_with_except(): try: raise ArithmeticError("bad code") # @exc except Exception: pass raise_with_except() with debug.Session() as session: session.expected_exit_code = some.int with run(session, target(code_to_debug)): session.request("setExceptionBreakpoints", {"filters": list({raised, uncaught} - {""})}) expected = some.dict.containing({ "exceptionId": str_matching_ArithmeticError, "description": "bad code", "breakMode": "always" if raised else "unhandled", "details": some.dict.containing({ "typeName": str_matching_ArithmeticError, "message": "bad code", "source": some.path(code_to_debug), }), }) if raised: stop = session.wait_for_stop( "exception", expected_text=str_matching_ArithmeticError, expected_description="bad code", expected_frames=[some.dap.frame(code_to_debug, line="exc")], ) exc_info = session.request("exceptionInfo", {"threadId": stop.thread_id}) assert exc_info == expected session.request_continue() if uncaught: # Exception is caught by try..except, so there should be no stop. pass
def test_flask_exception_no_multiproc(start_flask, exc_type): exc_line = lines.app_py["exc_" + exc_type] with debug.Session() as session: with start_flask(session): session.request( "setExceptionBreakpoints", {"filters": ["raised", "uncaught"]} ) with flask_server: flask_server.get("/" + exc_type) stopped = session.wait_for_stop( "exception", expected_frames=[ some.dap.frame( some.dap.source(paths.app_py), line=exc_line, name="bad_route_" + exc_type, ) ], ).body assert stopped == some.dict.containing( { "reason": "exception", "text": some.str.ending_with("ArithmeticError"), "description": "Hello", } ) exception_info = session.request( "exceptionInfo", {"threadId": stopped["threadId"]} ) assert exception_info == { "exceptionId": some.str.ending_with("ArithmeticError"), "breakMode": "always", "description": "Hello", "details": { "message": "Hello", "typeName": some.str.ending_with("ArithmeticError"), "source": some.path(paths.app_py), "stackTrace": some.str, }, } session.request_continue()
def test_breakpoint_in_nonexistent_file(pyfile, target, run): @pyfile def code_to_debug(): import debug_me # noqa with debug.Session() as session: with run(session, target(code_to_debug)): breakpoints = session.set_breakpoints("nonexistent_file.py", [1]) assert breakpoints == [{ "verified": False, "message": "Breakpoint in file that does not exist.", "source": some.dict.containing( {"path": some.path("nonexistent_file.py")}), "line": 1, }]
def test_with_dot_remote_root(pyfile, long_tmpdir, target, run): @pyfile def code_to_debug(): import os import debuggee from debuggee import backchannel debuggee.setup() backchannel.send(os.path.abspath(__file__)) print("done") # @bp dir_local = long_tmpdir.mkdir("local") dir_remote = long_tmpdir.mkdir("remote") path_local = dir_local / "code_to_debug.py" path_remote = dir_remote / "code_to_debug.py" code_to_debug.copy(path_local) code_to_debug.copy(path_remote) with debug.Session() as session: session.config["pathMappings"] = [{ "localRoot": dir_local, "remoteRoot": "." }] backchannel = session.open_backchannel() with run(session, target(path_remote), cwd=dir_remote): # Set breakpoints using local path. This tests that local paths are # mapped to remote paths. session.set_breakpoints(path_local, all) actual_path_remote = backchannel.receive() assert some.path(actual_path_remote) == path_remote session.wait_for_stop( "breakpoint", expected_frames=[ some.dap.frame(some.dap.source(path_local), line="bp") ], ) session.request_continue()
def test_justmycode_frames(pyfile, target, run, jmc): @pyfile def code_to_debug(): import debuggee debuggee.setup() import this # @bp assert this with debug.Session() as session: session.config["justMyCode"] = bool(jmc) with run(session, target(code_to_debug)): session.set_breakpoints(code_to_debug, all) stop = session.wait_for_stop( "breakpoint", expected_frames=[some.dap.frame(code_to_debug, "bp")]) if jmc: assert len(stop.frames) == 1 else: assert len(stop.frames) >= 1 session.request("stepIn", {"threadId": stop.thread_id}) # With JMC, it should step out of the function, remaining in the same file. # Without JMC, it should step into stdlib. expected_path = some.path(code_to_debug) if not jmc: expected_path = ~expected_path session.wait_for_stop( "step", expected_frames=[ some.dap.frame(some.dap.source(expected_path), some.int) ], ) session.request_continue()
def test_with_path_mappings(pyfile, long_tmpdir, target, run): @pyfile def code_to_debug(): import debuggee import os import sys from debuggee import backchannel debuggee.setup() backchannel.send(os.path.abspath(__file__)) call_me_back_dir = backchannel.receive() sys.path.insert(0, call_me_back_dir) import call_me_back def call_func(): print("break here") # @bp call_me_back.call_me_back(call_func) # @call_me_back print("done") dir_local = long_tmpdir.mkdir("local") dir_remote = long_tmpdir.mkdir("remote") path_local = dir_local / "code_to_debug.py" path_remote = dir_remote / "code_to_debug.py" code_to_debug.copy(path_local) code_to_debug.copy(path_remote) call_me_back_dir = test_data / "call_me_back" call_me_back_py = call_me_back_dir / "call_me_back.py" with debug.Session() as session: session.config["pathMappings"] = [{ "localRoot": dir_local, "remoteRoot": dir_remote }] backchannel = session.open_backchannel() with run(session, target(path_remote)): # Set breakpoints using local path. This tests that local paths are # mapped to remote paths. session.set_breakpoints(path_local, ["bp"]) actual_path_remote = backchannel.receive() assert some.path(actual_path_remote) == path_remote backchannel.send(call_me_back_dir) stop = session.wait_for_stop( "breakpoint", expected_frames=[ some.dap.frame( # Mapped files should not have a sourceReference, so that the IDE # doesn't try to fetch them instead of opening the local file. some.dap.source(path_local, sourceReference=0), line="bp", ), some.dap.frame( # Unmapped files should have a sourceReference, since there's no # local file for the IDE to open. some.dap.source(call_me_back_py, sourceReference=some.int.not_equal_to(0)), line="callback", ), some.dap.frame( # Mapped files should not have a sourceReference, so that the IDE # doesn't try to fetch them instead of opening the local file. some.dap.source(path_local, sourceReference=0), line="call_me_back", ), ], ) srcref = stop.frames[1]["source"]["sourceReference"] try: session.request("source", {"sourceReference": 0}) except Exception as exc: assert "Source unavailable" in str(exc) else: pytest.fail("sourceReference=0 should not be valid") source = session.request("source", {"sourceReference": srcref}) assert "def call_me_back(callback):" in source["content"] session.request_continue()
def test_vsc_exception_options_raise_without_except(pyfile, target, run, raised, uncaught): @pyfile def code_to_debug(): import debuggee debuggee.setup() def raise_without_except(): raise ArithmeticError("bad code") # @exc raise_without_except() with debug.Session() as session: session.ignore_unobserved.append(Event("stopped")) session.expected_exit_code = some.int with run(session, target(code_to_debug)): session.request("setExceptionBreakpoints", {"filters": list({raised, uncaught} - {""})}) expected_exc_info = some.dict.containing({ "exceptionId": str_matching_ArithmeticError, "description": "bad code", "breakMode": "always" if raised else "unhandled", "details": some.dict.containing({ "typeName": str_matching_ArithmeticError, "message": "bad code", "source": some.path(code_to_debug), }), }) if raised: stop = session.wait_for_stop( "exception", expected_frames=[some.dap.frame(code_to_debug, line="exc")]) exc_info = session.request("exceptionInfo", {"threadId": stop.thread_id}) assert expected_exc_info == exc_info session.request_continue() # NOTE: debugger stops at each frame if raised and is uncaught # This behavior can be changed by updating 'notify_on_handled_exceptions' # setting we send to pydevd to notify only once. In our test code, we have # two frames, hence two stops. session.wait_for_stop("exception") session.request_continue() if uncaught: stop = session.wait_for_stop( "exception", expected_frames=[some.dap.frame(code_to_debug, line="exc")]) expected_exc_info = some.dict.containing({ "exceptionId": str_matching_ArithmeticError, "description": "bad code", "breakMode": "unhandled", # Only difference from previous expected is breakMode. "details": some.dict.containing({ "typeName": str_matching_ArithmeticError, "message": "bad code", "source": some.path(code_to_debug), }), }) exc_info = session.request("exceptionInfo", {"threadId": stop.thread_id}) assert expected_exc_info == exc_info session.request_continue()