llvm-project/lldb/test/API/tools/lldb-dap/startDebugging/TestDAP_startDebugging.py
Janet Yang 5ab3375b2c
[lldb-dap] Add multi-session support with shared debugger instances (#163653)
## Summary:
This change introduces a `DAPSessionManager` to enable multiple DAP
sessions to share debugger instances when needed, for things like child
process debugging and some scripting hooks that create dynamically new
targets.

Changes include:
- Add `DAPSessionManager` singleton to track and coordinate all active DAP
sessions
- Support attaching to an existing target via its globally unique target
ID (targetId parameter)
- Share debugger instances across sessions when new targets are created
dynamically
- Refactor event thread management to allow sharing event threads
between sessions and move event thread and event thread handlers to `EventHelpers`
- Add `eBroadcastBitNewTargetCreated` event to notify when new targets are
created
- Extract session names from target creation events
- Defer debugger initialization from 'initialize' request to
'launch'/'attach' requests. The only time the debugger is used currently
in between its creation in `InitializeRequestHandler` and the `Launch`
or `Attach` requests is during the `TelemetryDispatcher` destruction
call at the end of the `DAP::HandleObject` call, so this is safe.

This enables scenarios when new targets are created dynamically so that
the debug adapter can automatically start a new debug session for the
spawned target while sharing the debugger instance.

## Tests:
The refactoring maintains backward compatibility. All existing DAP test
cases pass.

Also added a few basic unit tests for DAPSessionManager
```
>> ninja DAPTests
>> ./tools/lldb/unittests/DAP/DAPTests
>>./bin/llvm-lit -v ../llvm-project/lldb/test/API/tools/lldb-dap/
```
2025-11-26 10:32:25 -08:00

90 lines
3.1 KiB
Python

"""
Test lldb-dap start-debugging reverse requests.
"""
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbdap_testcase
class TestDAP_startDebugging(lldbdap_testcase.DAPTestCaseBase):
def test_startDebugging(self):
"""
Tests the "startDebugging" reverse request. It makes sure that the IDE can
start a child debug session.
"""
program = self.getBuildArtifact("a.out")
source = "main.c"
self.build_and_launch(program)
breakpoint_line = line_number(source, "// breakpoint")
self.set_source_breakpoints(source, [breakpoint_line])
self.continue_to_next_stop()
self.dap_server.request_evaluate(
"`lldb-dap start-debugging attach '{\"pid\":321}'", context="repl"
)
self.continue_to_exit()
self.assertEqual(
len(self.dap_server.reverse_requests),
1,
"make sure we got a reverse request",
)
request = self.dap_server.reverse_requests[0]
self.assertEqual(request["arguments"]["configuration"]["pid"], 321)
self.assertEqual(request["arguments"]["request"], "attach")
def test_startDebugging_debugger_reuse(self):
"""
Tests that debugger and target IDs can be passed through startDebugging
for debugger reuse. This verifies the infrastructure for child DAP
sessions to reuse the parent's debugger and attach to an existing target.
"""
program = self.getBuildArtifact("a.out")
source = "main.c"
self.build_and_launch(program)
breakpoint_line = line_number(source, "// breakpoint")
self.set_source_breakpoints(source, [breakpoint_line])
self.continue_to_next_stop()
# Use mock IDs to test the infrastructure
# In a real scenario, these would come from the parent session
test_debugger_id = 1
test_target_id = 100
# Send a startDebugging request with debuggerId and targetId
# This simulates creating a child DAP session that reuses the debugger
self.dap_server.request_evaluate(
f'`lldb-dap start-debugging attach \'{{"debuggerId":{test_debugger_id},"targetId":{test_target_id}}}\'',
context="repl",
)
self.continue_to_exit()
# Verify the reverse request was sent with the correct IDs
self.assertEqual(
len(self.dap_server.reverse_requests),
1,
"Should have received one startDebugging reverse request",
)
request = self.dap_server.reverse_requests[0]
self.assertEqual(request["command"], "startDebugging")
self.assertEqual(request["arguments"]["request"], "attach")
config = request["arguments"]["configuration"]
self.assertEqual(
config["debuggerId"],
test_debugger_id,
"Reverse request should include debugger ID",
)
self.assertEqual(
config["targetId"],
test_target_id,
"Reverse request should include target ID",
)