diff --git a/src/debugpy/adapter/clients.py b/src/debugpy/adapter/clients.py index cc088f1f..2ba9976e 100644 --- a/src/debugpy/adapter/clients.py +++ b/src/debugpy/adapter/clients.py @@ -30,6 +30,7 @@ class Client(components.Component): "supportsRunInTerminalRequest": False, "supportsMemoryReferences": False, "supportsArgsCanBeInterpretedByShell": False, + "supportsStartDebuggingRequest": False, } class Expectations(components.Capabilities): @@ -688,11 +689,10 @@ class Client(components.Component): self.known_subprocesses.add(conn) self.session.notify_changed() - for key in "processId", "listen", "preLaunchTask", "postDebugTask": + for key in "processId", "listen", "preLaunchTask", "postDebugTask", "request": body.pop(key, None) body["name"] = "Subprocess {0}".format(conn.pid) - body["request"] = "attach" body["subProcessId"] = conn.pid for key in "args", "processName", "pythonArgs": @@ -709,7 +709,14 @@ class Client(components.Component): _, port = listener.getsockname() body["connect"]["port"] = port - self.channel.send_event("debugpyAttach", body) + if self.capabilities["supportsStartDebuggingRequest"]: + self.channel.request("startDebugging", { + "request": "attach", + "configuration": body, + }) + else: + body["request"] = "attach" + self.channel.send_event("debugpyAttach", body) def serve(host, port): diff --git a/tests/debug/session.py b/tests/debug/session.py index 2e58c1a4..7dacc1f9 100644 --- a/tests/debug/session.py +++ b/tests/debug/session.py @@ -84,6 +84,18 @@ class Session(object): self.client_id = "vscode" + self.capabilities = { + "pathFormat": "path", + "clientID": self.client_id, + "adapterID": "test", + "linesStartAt1": True, + "columnsStartAt1": True, + "supportsVariableType": True, + "supportsRunInTerminalRequest": True, + "supportsArgsCanBeInterpretedByShell": True, + "supportsStartDebuggingRequest": False, + } + self.debuggee = None """psutil.Popen instance for the debuggee process.""" @@ -502,6 +514,10 @@ class Session(object): except Exception as exc: log.swallow_exception('"runInTerminal" failed:') raise request.cant_handle(str(exc)) + elif request.command == "startDebugging": + pid = request("configuration", dict)("subProcessId", int) + watchdog.register_spawn(pid, f"{self.debuggee_id}-subprocess-{pid}") + return {} else: raise request.isnt_valid("not supported") @@ -551,19 +567,7 @@ class Session(object): ) ) - self.request( - "initialize", - { - "pathFormat": "path", - "clientID": self.client_id, - "adapterID": "test", - "linesStartAt1": True, - "columnsStartAt1": True, - "supportsVariableType": True, - "supportsRunInTerminalRequest": True, - "supportsArgsCanBeInterpretedByShell": True, - }, - ) + self.request("initialize", self.capabilities) def all_events(self, event, body=some.object): return [ @@ -783,7 +787,15 @@ class Session(object): return StopInfo(stopped, frames, tid, fid) def wait_for_next_subprocess(self): - return Session(self.wait_for_next_event("debugpyAttach")) + message = self.timeline.wait_for_next(timeline.Event("debugpyAttach") | timeline.Request("startDebugging")) + if isinstance(message, timeline.EventOccurrence): + config = message.body + assert "request" in config + elif isinstance(message, timeline.RequestOccurrence): + config = dict(message.body("configuration", dict)) + assert "request" not in config + config["request"] = "attach" + return Session(config) def wait_for_disconnect(self): self.timeline.wait_until_realized(timeline.Mark("disconnect"), freeze=True) diff --git a/tests/debugpy/test_multiproc.py b/tests/debugpy/test_multiproc.py index c53c847a..9a6e4b14 100644 --- a/tests/debugpy/test_multiproc.py +++ b/tests/debugpy/test_multiproc.py @@ -9,7 +9,7 @@ import sys import debugpy import tests -from tests import debug, log +from tests import debug, log, timeline from tests.debug import runners from tests.patterns import some @@ -151,7 +151,8 @@ def test_multiprocessing(pyfile, target, run, start_method): @pytest.mark.parametrize("subProcess", [True, False, None]) -def test_subprocess(pyfile, target, run, subProcess): +@pytest.mark.parametrize("method", ["startDebugging", "debugpyAttach", ""]) +def test_subprocess(pyfile, target, run, subProcess, method): @pyfile def child(): import os @@ -188,6 +189,8 @@ def test_subprocess(pyfile, target, run, subProcess): with debug.Session() as parent_session: backchannel = parent_session.open_backchannel() + if method: + parent_session.capabilities["supportsStartDebuggingRequest"] = (method == "startDebugging") parent_session.config["preLaunchTask"] = "doSomething" parent_session.config["postDebugTask"] = "doSomethingElse" if subProcess is not None: @@ -200,9 +203,19 @@ def test_subprocess(pyfile, target, run, subProcess): return expected_child_config = expected_subprocess_config(parent_session) - child_config = parent_session.wait_for_next_event("debugpyAttach") + + if method == "startDebugging": + subprocess_request = parent_session.timeline.wait_for_next(timeline.Request("startDebugging")) + child_config = subprocess_request.arguments("configuration", dict) + del expected_child_config["request"] + else: + child_config = parent_session.wait_for_next_event("debugpyAttach") + + child_config = dict(child_config) child_config.pop("isOutputRedirected", None) assert child_config == expected_child_config + child_config["request"] = "attach" + parent_session.proceed() with debug.Session(child_config) as child_session: