Skip to content

Commit

Permalink
Fix #1074: Use "startDebugging" request for subprocesses
Browse files Browse the repository at this point in the history
Use the request if client advertises the "supportsStartDebuggingRequest" capability.
  • Loading branch information
Pavel Minaev authored and int19h committed Feb 7, 2023
1 parent 9fd3e72 commit e9dc3e8
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 20 deletions.
13 changes: 10 additions & 3 deletions src/debugpy/adapter/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Capabilities(components.Capabilities):
"supportsRunInTerminalRequest": False,
"supportsMemoryReferences": False,
"supportsArgsCanBeInterpretedByShell": False,
"supportsStartDebuggingRequest": False,
}

class Expectations(components.Capabilities):
Expand Down Expand Up @@ -688,11 +689,10 @@ def notify_of_subprocess(self, conn):
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":
Expand All @@ -709,7 +709,14 @@ def notify_of_subprocess(self, conn):
_, 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):
Expand Down
40 changes: 26 additions & 14 deletions tests/debug/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ def __init__(self, debug_config=None):

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."""

Expand Down Expand Up @@ -502,6 +514,10 @@ def _process_request(self, request):
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")

Expand Down Expand Up @@ -551,19 +567,7 @@ def _start_channel(self, stream):
)
)

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 [
Expand Down Expand Up @@ -783,7 +787,15 @@ def wait_for_stop(
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)
Expand Down
19 changes: 16 additions & 3 deletions tests/debugpy/test_multiproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

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

Expand Down Expand Up @@ -151,7 +151,8 @@ def grandchild(q, a):


@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
Expand Down Expand Up @@ -188,6 +189,8 @@ def parent():
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:
Expand All @@ -200,9 +203,19 @@ def parent():
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:
Expand Down

0 comments on commit e9dc3e8

Please sign in to comment.