[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