def test_args(pyfile, target, run): @pyfile def code_to_debug(): from debug_me import backchannel import sys backchannel.send(sys.argv) args = ["--arg1", "arg2", "-arg3", "--", "arg4", "-a"] with debug.Session() as session: backchannel = session.open_backchannel() with run(session, target(code_to_debug, args=args)): pass argv = backchannel.receive() assert argv == [some.str] + args
def test_log_dir(pyfile, tmpdir, run, target): @pyfile def code_to_debug(): import debuggee debuggee.setup() # Depending on the method, the runner will use either `debugpy --log-dir ...` # or `debugpy.log_to() ...`. run = run.with_options(log_dir=tmpdir.strpath) with check_logs(tmpdir, run, pydevd_log=False): with debug.Session() as session: session.log_dir = None with run(session, target(code_to_debug)): pass
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 debug_me # noqa 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 test_evaluate(pyfile, target, run): @pyfile def code_to_debug(): import debuggee debuggee.setup() a = 1 b = {"one": 1, 2: "two"} print(a, b) # @bp with debug.Session() as session: with run(session, target(code_to_debug)): session.set_breakpoints(code_to_debug, all) stop = session.wait_for_stop() # Evaluate a variable name. evaluate1 = session.request("evaluate", { "expression": "a", "frameId": stop.frame_id }) assert evaluate1 == some.dict.containing({ "type": "int", "result": "1" }) # Evaluate dict indexing. evaluate2 = session.request("evaluate", { "expression": "b[2]", "frameId": stop.frame_id }) assert evaluate2 == some.dict.containing({ "type": "str", "result": "'two'" }) # Evaluate an expression with a binary operator. evaluate3 = session.request("evaluate", { "expression": 'a + b["one"]', "frameId": stop.frame_id }) assert evaluate3 == some.dict.containing({ "type": "int", "result": "2" }) session.request_continue()
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_continue_on_disconnect_for_attach(pyfile, target, run): @pyfile def code_to_debug(): from debug_me import backchannel backchannel.send("continued") # @bp with debug.Session() as session: backchannel = session.open_backchannel() with run(session, target(code_to_debug)): session.set_breakpoints(code_to_debug, all) session.wait_for_stop( "breakpoint", expected_frames=[some.dap.frame(code_to_debug, line="bp")]) session.disconnect() assert "continued" == backchannel.receive()
def test_with_tab_in_output(pyfile, target, run): @pyfile def code_to_debug(): import debug_me # noqa a = "\t".join(("Hello", "World")) print(a) () # @wait_for_output with debug.Session() as session: with run(session, target(code_to_debug)): session.set_breakpoints(code_to_debug, all) session.wait_for_stop() session.request_continue() assert session.output("stdout").startswith("Hello\tWorld")
def test_completions_cases(pyfile, target, run): @pyfile def code_to_debug(): import debug_me # noqa a = 1 b = {"one": 1, "two": 2} c = 3 print([a, b, c]) # @break with debug.Session() as session: with run(session, target(code_to_debug)): session.set_breakpoints(code_to_debug, [code_to_debug.lines["break"]]) stop = session.wait_for_stop() completions = session.request( "completions", {"frameId": stop.frame_id, "text": "b.", "column": 3} ) labels = set(target["label"] for target in completions["targets"]) assert labels >= {"get", "items", "keys", "setdefault", "update", "values"} completions = session.request( "completions", {"frameId": stop.frame_id, "text": "x = b.setdefault", "column": 13}, ) assert completions["targets"] == [ {"label": "setdefault", "length": 6, "start": 6, "type": "function"} ] completions = session.request( "completions", {"frameId": stop.frame_id, "text": "not_there", "column": 10} ) assert not completions["targets"] with pytest.raises(messaging.MessageHandlingError): completions = session.request( "completions", { "frameId": 9999999, # nonexistent frameId "text": "not_there", "column": 10, }, ) session.request_continue()
def test_set_variable(pyfile, target, run): @pyfile def code_to_debug(): import debuggee import debugpy from debuggee import backchannel debuggee.setup() a = 1 debugpy.breakpoint() backchannel.send(a) with debug.Session() as session: backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass stop = session.wait_for_stop() scopes = session.request("scopes", {"frameId": stop.frame_id})["scopes"] globals_ref = scopes[0]["variablesReference"] vars = session.request( "variables", {"variablesReference": globals_ref})["variables"] (a, ) = (v for v in vars if v["name"] == "a") assert a == some.dict.containing({ "type": "int", "value": "1", "name": "a", "evaluateName": "a", "variablesReference": 0, }) set_a = session.request( "setVariable", { "variablesReference": globals_ref, "name": "a", "value": "1000" }, ) assert set_a == some.dict.containing({"type": "int", "value": "1000"}) session.request_continue() assert backchannel.receive() == 1000
def test_with_no_output(pyfile, target, run): @pyfile def code_to_debug(): import debug_me # noqa () # @wait_for_output with debug.Session() as session: with run(session, target(code_to_debug)): session.set_breakpoints(code_to_debug, all) session.wait_for_stop("breakpoint") session.request_continue() assert not session.output("stdout") assert not session.output("stderr") assert not session.captured_stdout() assert not session.captured_stderr()
def test_wait_on_normal_exit_enabled(pyfile, target, run): @pyfile def code_to_debug(): from debug_me import ptvsd ptvsd.break_into_debugger() print() # line on which it'll actually break with debug.Session() as session: session.config["waitOnNormalExit"] = True with run(session, target(code_to_debug)): pass session.wait_for_stop() session.request_continue() wait_and_press_key(session)
def test_attach_by_pid(pyfile, target, pid_type): @pyfile def code_to_debug(): import debug_me # noqa import time def do_something(i): time.sleep(0.1) proceed = True print(i) # @bp return proceed for i in range(100): if not do_something(i): break with debug.Session() as session: def before_request(command, arguments): if command == "attach": assert isinstance(arguments["processId"], int) if pid_type == "str": arguments["processId"] = str(arguments["processId"]) session.before_request = before_request session.config["redirectOutput"] = True with session.attach_by_pid(target(code_to_debug), wait=False): session.set_breakpoints(code_to_debug, all) stop = session.wait_for_stop( expected_frames=[some.dap.frame(code_to_debug, "bp")] ) # Remove breakpoint and continue. session.request( "setExpression", {"frameId": stop.frame_id, "expression": "proceed", "value": "False"}, ) session.set_breakpoints(code_to_debug, []) session.request_continue() session.wait_for_next_event( "output", some.dict.containing({"category": "stdout"}) )
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_log_dir_env(pyfile, tmpdir, run, target): @pyfile def code_to_debug(): from debug_me import backchannel # noqa assert backchannel.receive() == "proceed" with check_logs(tmpdir, run): with debug.Session() as session: session.log_dir = None session.spawn_adapter.env["PTVSD_LOG_DIR"] = tmpdir if run.request != "launch": session.spawn_debuggee.env["PTVSD_LOG_DIR"] = tmpdir backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass backchannel.send("proceed")
def test_django_template_exception_no_multiproc(start_django): with debug.Session() as session: with start_django(session): session.request("setExceptionBreakpoints", {"filters": ["raised", "uncaught"]}) with django_server: django_server.get("/badtemplate", log_errors=False) stop = session.wait_for_stop( "exception", expected_frames=[ some.dap.frame( some.dap.source(paths.bad_html), line=8, name="Django TemplateSyntaxError", ) ], ) # Will stop once in the plugin exception_info = session.request("exceptionInfo", {"threadId": stop.thread_id}) assert exception_info == some.dict.containing({ "exceptionId": some.str.ending_with("TemplateSyntaxError"), "breakMode": "always", "description": some.str.containing("doesnotexist"), "details": some.dict.containing({ "message": some.str.containing("doesnotexist"), "typeName": some.str.ending_with("TemplateSyntaxError"), }), }) session.request_continue() log.info("Exception will be reported again in {0}", paths.app_py) session.wait_for_stop("exception") session.request_continue()
def test_run_relative_path(pyfile, run): @pyfile def code_to_debug(): import debuggee from debuggee import backchannel from _pydev_bundle.pydev_log import list_log_files debuggee.setup() from _pydevd_bundle import pydevd_constants # @ bp1 backchannel.send( list_log_files(pydevd_constants.DebugInfoHolder.PYDEVD_DEBUG_FILE) ) assert backchannel.receive() == "continue" with debug.Session() as session: backchannel = session.open_backchannel() code_to_debug = str(code_to_debug) cwd = os.path.dirname(os.path.dirname(code_to_debug)) program = targets.Program(code_to_debug) program.make_relative(cwd) with run(session, program): session.set_breakpoints(code_to_debug, all) session.wait_for_stop() session.request_continue() pydevd_debug_files = backchannel.receive() backchannel.send("continue") session.wait_for_next_event("terminated") session.proceed() # Check if we don't have errors in the pydevd log (the # particular error this test is covering: # https://github.com/microsoft/debugpy/issues/620 # is handled by pydevd but produces a Traceback in the logs). for pydevd_debug_file in pydevd_debug_files: with open(pydevd_debug_file, "r") as stream: contents = stream.read() assert "critical" not in contents assert "Traceback" not in contents
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_ptvsd_systemInfo(pyfile, target, run, expected_system_info): @pyfile def code_to_debug(): from debug_me import ptvsd ptvsd.break_into_debugger() print() with debug.Session() as session: with run(session, target(code_to_debug)): pass session.wait_for_stop() system_info = session.request("ptvsd_systemInfo") log.info("Expected system info: {0}", expected_system_info) assert system_info == expected_system_info session.request_continue()
def test_sudo(pyfile, tmpdir, run, target): # Since the test can't rely on sudo being allowed for the user, create a dummy # sudo script that doesn't actually elevate, but sets an environment variable # that can be checked in the debuggee. sudo = tmpdir / "sudo" sudo.write( """#!/bin/sh if [ "$1" = "-E" ]; then shift; fi exec env DEBUGPY_SUDO=1 "$@" """ ) os.chmod(sudo.strpath, 0o777) @pyfile def code_to_debug(): import os import debuggee from debuggee import backchannel debuggee.setup() backchannel.send(os.getenv("DEBUGPY_SUDO", "0")) with debug.Session() as session: session.config["sudo"] = True session.spawn_adapter.env["PATH"] = session.spawn_debuggee.env["PATH"] = ( tmpdir.strpath + ":" + os.environ["PATH"] ) backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass # The "runInTerminal" request sent by the adapter to spawn the launcher, # if any, shouldn't be using sudo. assert all( "sudo" not in req.arguments["args"] for req in session.all_occurrences_of(timeline.Request("runInTerminal")) ) # The launcher, however, should use our dummy sudo to spawn the debuggee, # and the debuggee should report the environment variable accordingly. assert backchannel.receive() == "1"
def test_exit_normally_with_wait_on_abnormal_exit_enabled(pyfile, target, run): @pyfile def code_to_debug(): from debug_me import ptvsd ptvsd.break_into_debugger() print() with debug.Session() as session: session.config["waitOnAbnormalExit"] = True with run(session, target(code_to_debug)): pass session.wait_for_stop() session.request_continue() session.wait_for_next_event("exited") assert not has_waited(session)
def test_exceptions_and_exclude_rules(pyfile, target, run, scenario, exc_type): if exc_type == "RuntimeError": @pyfile def code_to_debug(): import debuggee debuggee.setup() raise RuntimeError("unhandled error") # @raise_line elif exc_type == "SystemExit": @pyfile def code_to_debug(): import debuggee import sys debuggee.setup() sys.exit(1) # @raise_line else: pytest.fail(exc_type) if scenario == "exclude_by_name": rules = [{"path": "**/" + code_to_debug.basename, "include": False}] elif scenario == "exclude_by_dir": rules = [{"path": code_to_debug.dirname, "include": False}] else: pytest.fail(scenario) log.info("Rules: {0!j}", rules) with debug.Session() as session: session.expected_exit_code = some.int session.config["rules"] = rules with run(session, target(code_to_debug)): session.request("setExceptionBreakpoints", {"filters": ["raised", "uncaught"]}) # No exceptions should be seen. session.wait_for_next_event("terminated") session.proceed()
def test_flask_breakpoint_multiproc(start_flask): bp_line = lines.app_py["bphome"] bp_var_content = compat.force_str("Flask-Jinja-Test") with debug.Session() as parent_session: with start_flask(parent_session, multiprocess=True): parent_session.set_breakpoints(paths.app_py, [bp_line]) with parent_session.wait_for_next_subprocess() as child_session: with child_session.start(): child_session.set_breakpoints(paths.app_py, [bp_line]) with flask_server: home_request = flask_server.get("/") child_session.wait_for_stop( "breakpoint", expected_frames=[ some.dap.frame(some.dap.source(paths.app_py), line=bp_line, name="home") ], ) var_content = child_session.get_variable("content") assert var_content == some.dict.containing({ "name": "content", "type": "str", "value": repr(bp_var_content), "presentationHint": { "attributes": ["rawString"] }, "evaluateName": "content", "variablesReference": 0, }) child_session.request_continue() assert bp_var_content in home_request.response_text()
def test_django_breakpoint_no_multiproc(start_django, bp_target): bp_file, bp_line, bp_name = { "code": (paths.app_py, lines.app_py["bphome"], "home"), "template": (paths.hello_html, 8, "Django Template"), }[bp_target] bp_var_content = compat.force_str("Django-Django-Test") with debug.Session() as session: with start_django(session): session.set_breakpoints(bp_file, [bp_line]) with django_server: home_request = django_server.get("/home") session.wait_for_stop( "breakpoint", expected_frames=[ some.dap.frame(some.dap.source(bp_file), line=bp_line, name=bp_name) ], ) var_content = session.get_variable("content") assert var_content == some.dict.containing({ "name": "content", "type": "str", "value": compat.unicode_repr(bp_var_content), "presentationHint": { "attributes": ["rawString"] }, "evaluateName": "content", "variablesReference": 0, }) session.request_continue() assert bp_var_content in home_request.response_text()
def test_set_expression(pyfile, target, run): @pyfile def code_to_debug(): from debug_me import backchannel a = 1 backchannel.send(a) # @bp with debug.Session() as session: backchannel = session.open_backchannel() with run(session, target(code_to_debug)): session.set_breakpoints(code_to_debug, all) stop = session.wait_for_stop() scopes = session.request("scopes", {"frameId": stop.frame_id})["scopes"] globals_ref = scopes[0]["variablesReference"] vars = session.request( "variables", {"variablesReference": globals_ref})["variables"] a, = (v for v in vars if v["name"] == "a") assert a == some.dict.containing({ "type": "int", "value": "1", "name": "a", "evaluateName": "a", "variablesReference": 0, }) set_a = session.request( "setExpression", { "frameId": stop.frame_id, "expression": "a", "value": "1000" }, ) assert set_a == some.dict.containing({"type": "int", "value": "1000"}) session.request_continue() assert backchannel.receive() == 1000
def test_debugpySystemInfo(pyfile, target, run, expected_system_info): @pyfile def code_to_debug(): import debuggee import debugpy debuggee.setup() debugpy.breakpoint() print() with debug.Session() as session: with run(session, target(code_to_debug)): pass session.wait_for_stop() system_info = session.request("debugpySystemInfo") log.info("Expected system info: {0}", expected_system_info) assert system_info == expected_system_info session.request_continue()
def test_conditional_breakpoint(pyfile, target, run, condition_kind, condition): hit = conditions[condition_kind, condition] @pyfile def code_to_debug(): import debuggee debuggee.setup() for i in range(1, 10): print(i) # @bp with debug.Session() as session: with run(session, target(code_to_debug)): session.request( "setBreakpoints", { "source": { "path": code_to_debug }, "breakpoints": [{ "line": code_to_debug.lines["bp"], condition_kind: condition }], }, ) for i in range(1, 10): if not hit(i): continue session.wait_for_stop( expected_frames=[some.dap.frame(code_to_debug, line="bp")]) var_i = session.get_variable("i") assert var_i == some.dict.containing({ "name": "i", "evaluateName": "i", "type": "int", "value": str(i) }) 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_autokill_nodebug(daemon, pyfile, target, run): @pyfile def child(): import os from debuggee import backchannel backchannel.send(os.getpid()) while True: pass @pyfile def parent(): import os import subprocess import sys argv = [sys.executable, sys.argv[1]] env = os.environ.copy() subprocess.Popen( argv, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).wait() with debug.Session() as session: session.expected_exit_code = some.int session.config["noDebug"] = True backchannel = session.open_backchannel() run(session, target(parent, args=[child])) child_pid = backchannel.receive() child_process = psutil.Process(child_pid) session.request("terminate") log.info("Waiting for child process...") child_process.wait()
def test_break_api(pyfile, target, run, func): if func == "breakpoint" and sys.version_info < (3, 7): pytest.skip("breakpoint() was introduced in Python 3.7") @pyfile def code_to_debug(): from debug_me import ptvsd # noqa import sys func = eval(sys.argv[1]) func() print("break here") # @break with debug.Session() as session: target = target(code_to_debug, args=[func]) with run(session, target): pass session.wait_for_stop(expected_frames=[ some.dap.frame(target.source, target.lines["break"]) ]) session.request_continue()
def test_with_no_output(pyfile, target, run): @pyfile def code_to_debug(): import debuggee debuggee.setup() () # @wait_for_output with debug.Session() as session: session.config["redirectOutput"] = True with run(session, target(code_to_debug)): session.set_breakpoints(code_to_debug, all) session.wait_for_stop("breakpoint") session.request_continue() assert not session.output("stdout") assert not session.output("stderr") if session.debuggee is not None: assert not session.captured_stdout() assert not session.captured_stderr()