Fix #1074: Use "startDebugging" request for subprocesses

Use the request if client advertises the "supportsStartDebuggingRequest" capability.
This commit is contained in:
Pavel Minaev 2023-02-07 02:24:18 -08:00 committed by Pavel Minaev
parent 9fd3e72235
commit e9dc3e8844
3 changed files with 52 additions and 20 deletions

View file

@ -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):

View file

@ -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)

View file

@ -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: