[Lldb-commits] [lldb] fb45017 - [lldb-dap][test] Migrate server mode tests (#227804)
via lldb-commits
lldb-commits at lists.llvm.org
Fri Oct 2 06:09:01 PDT 2026
Author: Ebuka Ezike
Date: 2026-10-02T14:08:53+01:00
New Revision: fb45017cd60cf012c85ce4f87498c26e49ca9e5e
URL: https://github.com/llvm/llvm-project/commit/fb45017cd60cf012c85ce4f87498c26e49ca9e5e
DIFF: https://github.com/llvm/llvm-project/commit/fb45017cd60cf012c85ce4f87498c26e49ca9e5e.diff
LOG: [lldb-dap][test] Migrate server mode tests (#227804)
Migrate TestDAP_server.py to the new lldb-dap test infrastructure.
test_server_port and test_server_unix_socket now run two sessions
concurrently.
Add test_breakpoints_in_multiple_sessions to check that a breakpoint set
in one session must not stop another.
Fix test_connection_timeout_at_server_start. It now waits for lldb-dap
to exit on its own before the teardown kills it.
The test now takes significantly less time.
Added:
Modified:
lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
Removed:
################################################################################
diff --git a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
index d5225fd6270224..34a09e80eec548 100644
--- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -7,65 +7,15 @@
import tempfile
import time
-import dap_server
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
-import lldbdap_testcase
-from subprocess import Popen
-from typing import Tuple
+from lldbsuite.test.tools.lldb_dap import *
+from lldbsuite.test.tools.lldb_dap.types import *
@requireSocketPermission # every test starts lldb-dap in listening server mode
-class TestDAP_server(lldbdap_testcase.DAPTestCaseBase):
- def start_server(
- self, connection, connection_timeout=30
- ) -> Tuple[Popen[bytes], str]:
- self.create_debug_adapter(
- connection=connection, connection_timeout=connection_timeout
- )
- assert self.dap_server.process
- assert self.dap_server.connection
-
- # Save the process instance for the cleanup in case a new server is
- # created.
- process: Popen[bytes] = self.dap_server.process
-
- def cleanup():
- try:
- process.stdin.close()
- process.wait(timeout=5)
- except subprocess.TimeoutExpired:
- process.kill()
-
- self.addTearDownHook(cleanup)
-
- return (process, self.dap_server.connection)
-
- def run_debug_session(
- self, connection: str, name: str, *, sleep_seconds_in_middle: float = 0
- ):
- self.dap_server = dap_server.DebugAdapterServer(
- connection=connection,
- init_commands=self.setUpCommands(),
- spawn_helper=self.spawnSubprocess,
- )
- program = self.getBuildArtifact("a.out")
- source = "main.c"
- breakpoint_line = line_number(source, "// breakpoint")
-
- self.launch(
- program,
- args=[name],
- disconnectAutomatically=False,
- )
- if sleep_seconds_in_middle:
- time.sleep(sleep_seconds_in_middle)
- self.set_source_breakpoints(source, [breakpoint_line])
- self.continue_to_next_stop()
- self.continue_to_exit()
- output = self.get_stdout()
- self.assertEqual(output, f"Hello {name}!\r\n")
- self.dap_server.request_disconnect()
+class TestDAP_server(DAPTestCaseBase):
+ USE_DEFAULT_DEBUG_ADAPTER = False
@skipIfWindows
def test_server_port(self):
@@ -73,27 +23,35 @@ def test_server_port(self):
Test launching a binary with a lldb-dap in server mode on a specific port.
"""
self.build()
- (_, connection) = self.start_server(connection="listen://localhost:0")
- self.run_debug_session(connection, "Alice")
- self.run_debug_session(connection, "Bob")
+ adapter = self.start_server(connection="listen://localhost:0")
+ # TODO: Ideally the sessions should run concurrently. But parsing the same module's
+ # debug info from multiple SBDebuggers simultaneously is currently not thread-safe.
+ for name in ["Alice", "Bob"]:
+ session = self.create_session(adapter, disconnect_automatically=False)
+ self.run_debug_session(session, name)
@requirePOSIX
def test_server_unix_socket(self):
"""
Test launching a binary with a lldb-dap in server mode on a unix socket.
"""
- dir = tempfile.gettempdir()
- name = dir + "/dap-connection-" + str(os.getpid())
-
- def cleanup():
- os.unlink(name)
-
- self.addTearDownHook(cleanup)
-
self.build()
- (_, connection) = self.start_server(connection="accept://" + name)
- self.run_debug_session(connection, "Alice")
- self.run_debug_session(connection, "Bob")
+ # Unix socket paths are limited to about 104 bytes, and the default temp
+ # directory on macOS can be long, so use /tmp when there is one.
+ # The temp directory makes it unique "/tmp/tmp-dir9909ilskj32ewr/dap.sock"
+ base_dir = "/tmp" if os.path.isdir("/tmp") else None
+ temp_dir = tempfile.TemporaryDirectory(dir=base_dir)
+ socket_path = os.path.join(temp_dir.name, "dap.sock")
+
+ self.logger.info("adapter server socket path: %s", socket_path)
+ self.addTearDownHook(temp_dir.cleanup)
+
+ adapter = self.start_server(connection="accept://" + socket_path)
+ # TODO: Ideally the sessions should run concurrently. But parsing the same module's
+ # debug info from multiple SBDebuggers simultaneously is currently not thread-safe.
+ for name in ["Alice", "Bob"]:
+ session = self.create_session(adapter, disconnect_automatically=False)
+ self.run_debug_session(session, name)
@skipIfWindows
def test_server_interrupt(self):
@@ -102,29 +60,23 @@ def test_server_interrupt(self):
the server while the debug session is still active.
"""
self.build()
- (process, _) = self.start_server(connection="listen://localhost:0")
- program = self.getBuildArtifact("a.out")
- source = "main.c"
- breakpoint_line = line_number(source, "// breakpoint")
-
- self.launch(
- program,
- args=["Alice"],
- disconnectAutomatically=False,
- )
- self.set_source_breakpoints(source, [breakpoint_line])
- self.continue_to_next_stop()
+ adapter = self.start_server(connection="listen://localhost:0")
+ session = self.create_session(adapter, disconnect_automatically=False)
+ stop_event = self.launch_to_breakpoint(session, "Alice")
# Interrupt the server which should disconnect all clients.
- process.send_signal(signal.SIGINT)
+ adapter.process.send_signal(signal.SIGINT)
# Wait for both events since they can happen in any order.
- self.dap_server.wait_for_event(["terminated", "exited"])
- self.dap_server.wait_for_event(["terminated", "exited"])
- self.assertIsNotNone(
- self.dap_server.exit_status,
- "Process exited before interrupting lldb-dap server",
- )
+ ending_events = (TerminatedEvent, ExitedEvent)
+ first = session.wait_for_any_event(ending_events, after=stop_event)
+ session.wait_for_any_event(ending_events, after=first)
+
+ exit_code = int(signal.SIGKILL)
+ session.verify_process_exited(exitCode=exit_code, after=stop_event)
+
+ # The server shuts down cleanly once its clients are disconnected.
+ self.assertEqual(adapter.process.wait(timeout=self.DEFAULT_TIMEOUT), 0)
@skipIfWindows
def test_connection_timeout_at_server_start(self):
@@ -133,10 +85,12 @@ def test_connection_timeout_at_server_start(self):
waiting for it to terminate automatically when no client connects.
"""
self.build()
- self.start_server(
+ adapter = self.start_server(
connection="listen://localhost:0",
connection_timeout=1,
)
+ # The server must exit on its own, before the teardown hook kills it.
+ self.assertEqual(adapter.process.wait(timeout=self.DEFAULT_TIMEOUT), 0)
@skipIfWindows
def test_connection_timeout_long_debug_session(self):
@@ -145,12 +99,14 @@ def test_connection_timeout_long_debug_session(self):
terminating the server after the a long debug session.
"""
self.build()
- (_, connection) = self.start_server(
+ adapter = self.start_server(
connection="listen://localhost:0",
connection_timeout=1,
)
- # The connection timeout should not cut off the debug session
- self.run_debug_session(connection, "Alice", sleep_seconds_in_middle=1.5)
+ # The connection timeout should not cut off the debug session.
+ session = self.create_session(adapter, disconnect_automatically=False)
+ self.run_debug_session(session, "Alice", sleep_seconds_in_middle=1.2)
+ self.assertTrue(adapter.is_alive, "expected the server to be running")
@skipIfWindows
def test_connection_timeout_multiple_sessions(self):
@@ -159,13 +115,94 @@ def test_connection_timeout_multiple_sessions(self):
terminating the server after the last debug session.
"""
self.build()
- (_, connection) = self.start_server(
+ adapter = self.start_server(
connection="listen://localhost:0",
connection_timeout=1,
)
- time.sleep(0.5)
+ time.sleep(0.25)
# Should be able to connect to the server.
- self.run_debug_session(connection, "Alice")
- time.sleep(0.5)
+ session1 = self.create_session(adapter, disconnect_automatically=False)
+ self.run_debug_session(session1, "Alice")
+ time.sleep(0.25)
# Should be able to connect to the server, because it's still within the connection timeout.
- self.run_debug_session(connection, "Bob")
+ session2 = self.create_session(adapter, disconnect_automatically=False)
+ self.run_debug_session(session2, "Bob")
+
+ # The server shuts down once the connection timeout passes after the
+ # last session ends.
+ self.assertEqual(adapter.process.wait(timeout=self.DEFAULT_TIMEOUT), 0)
+
+ @skipIfWindows
+ def test_breakpoints_in_multiple_sessions(self):
+ """
+ Test in server mode setting a breakpoint in one session does not activate
+ in another session.
+ """
+ self.build()
+ program = self.getBuildArtifact("a.out")
+ adapter = self.start_server(connection="listen://localhost:0")
+ # With first breakpoint.
+ session1 = self.create_session(adapter, disconnect_automatically=False)
+ # With second breakpoint.
+ session2 = self.create_session(adapter, disconnect_automatically=False)
+
+ source = "main.c"
+ bp1_line = line_number(source, "// breakpoint 1")
+ launch_args = LaunchArgs(program)
+
+ # Start the first session and stop at breakpoint 1.
+ with session1.configure(launch_args) as ctx1:
+ [breakpoint1] = session1.resolve_source_breakpoints(source, [bp1_line])
+
+ session1.verify_stopped_on_breakpoint(breakpoint1, after=ctx1.process_event)
+
+ # Start the second session and stop at breakpoint 2.
+ bp2_line = line_number(source, "// breakpoint 2")
+ with session2.configure(launch_args) as ctx2:
+ [breakpoint2] = session2.resolve_source_breakpoints(source, [bp2_line])
+
+ session2.verify_stopped_on_breakpoint(breakpoint2, after=ctx2.process_event)
+
+ # Start and finish the third session with no breakpoint.
+ session3 = self.create_session(adapter, disconnect_automatically=False)
+ process_event3 = session3.launch(launch_args)
+ session3.verify_process_exited(after=process_event3)
+
+ # Finishing session1 and session2 should not hit any breakpoint.
+ session1.continue_to_exit()
+ session2.continue_to_exit()
+
+ def start_server(self, connection: str, connection_timeout: int = 30):
+ options = DebugAdapterOptions(
+ connection=connection, connection_timeout=connection_timeout
+ )
+ return self.create_server_debug_adapter(options)
+
+ def launch_to_breakpoint(self, session: DAPTestSession, name: str):
+ program = self.getBuildArtifact("a.out")
+ source = "main.c"
+ breakpoint_line = line_number(source, "// breakpoint 1")
+
+ with session.configure(LaunchArgs(program, args=[name])) as ctx:
+ session.resolve_source_breakpoints(source, [breakpoint_line])
+
+ return session.verify_stopped_on_breakpoint(after=ctx.process_event)
+
+ def continue_to_exit_and_disconnect(self, session: DAPTestSession, name: str):
+ session.continue_to_exit()
+ output = session.get_stdout()
+ self.assertEqual(output, f"Hello {name}!\r\n")
+ disconnect_resp = session.disconnect()
+
+ with self.assertRaises(DAPError):
+ # We should not receive a second terminated event. The check doesn't
+ # stall, since the event history is closed.
+ session.wait_for_terminated_event(after=disconnect_resp)
+
+ def run_debug_session(
+ self, session: DAPTestSession, name: str, *, sleep_seconds_in_middle: float = 0
+ ):
+ self.launch_to_breakpoint(session, name)
+ if sleep_seconds_in_middle:
+ time.sleep(sleep_seconds_in_middle)
+ self.continue_to_exit_and_disconnect(session, name)
More information about the lldb-commits
mailing list