| """ | |
| Test lldb-dap server integration. | |
| """ | |
| import os | |
| import signal | |
| 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 | |
| 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 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, 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() | |
| @skipIfWindows | |
| 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") | |
| @skipIfWindows | |
| 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") | |
| @skipIfWindows | |
| def test_server_interrupt(self): | |
| """ | |
| Test launching a binary with lldb-dap in server mode and shutting down | |
| 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() | |
| # Interrupt the server which should disconnect all clients. | |
| 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", | |
| ) | |
| @skipIfWindows | |
| def test_connection_timeout_at_server_start(self): | |
| """ | |
| Test launching lldb-dap in server mode with connection timeout and | |
| waiting for it to terminate automatically when no client connects. | |
| """ | |
| self.build() | |
| self.start_server( | |
| connection="listen://localhost:0", | |
| connection_timeout=1, | |
| ) | |
| @skipIfWindows | |
| def test_connection_timeout_long_debug_session(self): | |
| """ | |
| Test launching lldb-dap in server mode with connection timeout and | |
| terminating the server after the a long debug session. | |
| """ | |
| self.build() | |
| (_, connection) = 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) | |
| @skipIfWindows | |
| def test_connection_timeout_multiple_sessions(self): | |
| """ | |
| Test launching lldb-dap in server mode with connection timeout and | |
| terminating the server after the last debug session. | |
| """ | |
| self.build() | |
| (_, connection) = self.start_server( | |
| connection="listen://localhost:0", | |
| connection_timeout=1, | |
| ) | |
| time.sleep(0.5) | |
| # Should be able to connect to the server. | |
| self.run_debug_session(connection, "Alice") | |
| time.sleep(0.5) | |
| # Should be able to connect to the server, because it's still within the connection timeout. | |
| self.run_debug_session(connection, "Bob") |