Author: Ebuka Ezike Date: 2026-08-13T16:46:16+01:00 New Revision: bd7af6744d4e604582841f512bc5886d21509ae1
URL: https://github.com/llvm/llvm-project/commit/bd7af6744d4e604582841f512bc5886d21509ae1 DIFF: https://github.com/llvm/llvm-project/commit/bd7af6744d4e604582841f512bc5886d21509ae1.diff LOG: [lldb-dap] Migrate memory and disconnect tests (#215821) In Disconnect `test_attach` test, use a breakpoint to stop the process to ensure we are in the `handle_attach` frame to change the `wait_for_attach` variable. Added: Modified: lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py lldb/test/API/tools/lldb-dap/disconnect/main.cpp lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py lldb/test/API/tools/lldb-dap/server/main.c Removed: ################################################################################ diff --git a/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py b/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py index c6ebef2e584a1..4c82f34b9232f 100644 --- a/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py +++ b/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py @@ -2,28 +2,23 @@ Test lldb-dap disconnect request """ +import os +import subprocess +import uuid +from lldbsuite.test import lldbutil from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * -from lldbsuite.test import lldbutil -import lldbdap_testcase -import time -import os +from lldbsuite.test.tools.lldb_dap import DAPTestCaseBase +from lldbsuite.test.tools.lldb_dap.types import * @requireNotWasm("no attach support") -class TestDAP_disconnect(lldbdap_testcase.DAPTestCaseBase): +class TestDAP_disconnect(DAPTestCaseBase): SHARED_BUILD_TESTCASE = False source = "main.cpp" - def disconnect_and_assert_no_output_printed(self): - self.dap_server.request_disconnect() - # verify we didn't get any input after disconnect - time.sleep(2) - output = self.get_stdout() - self.assertTrue(output is None or len(output) == 0) - @skipIfWindows def test_launch(self): """ @@ -32,22 +27,23 @@ def test_launch(self): created. """ program = self.getBuildArtifact("a.out") - self.build_and_launch(program, stopOnEntry=True, disconnectAutomatically=False) - - # We set a breakpoint right before the side effect file is created - self.set_source_breakpoints( - self.source, [line_number(self.source, "// breakpoint")] - ) - self.continue_to_next_stop() + side_effect = f"{program}.side_effect" + session = self.build_and_create_session(disconnect_automatically=False) + with session.configure(LaunchArgs(program, stopOnEntry=True)) as ctx: + # We set a breakpoint right before the side effect file is created + session.resolve_source_breakpoints( + self.source, [line_number(self.source, "// breakpoint")] + ) + stop_event = session.verify_stopped_on_entry(after=ctx.process_event) - # verify we haven't produced the side effect file yet - self.assertFalse(os.path.exists(program + ".side_effect")) + # Verify we haven't produced the side effect file yet. + self.assertFalse(os.path.exists(side_effect)) - self.dap_server.request_disconnect() + session.disconnect(terminateDebuggee=True) + session.wait_for_event(TerminatedEvent, after=stop_event) - # verify we didn't produce the side effect file - time.sleep(1) - self.assertFalse(os.path.exists(program + ".side_effect")) + # Verify we didn't produce the side effect file. + self.assertFalse(os.path.exists(side_effect)) @skipIfWindows @expectedFailureNetBSD @@ -57,29 +53,40 @@ def test_attach(self): before the file is created, and as the process is not terminated upon disconnection, the file is created anyway. """ - self.build_and_create_debug_adapter() + session = self.build_and_create_session(disconnect_automatically=False) program = self.getBuildArtifact("a.out") + side_effect = program + ".side_effect" # Use a file as a synchronization point between test and inferior. sync_file_path = lldbutil.append_to_process_working_directory( - self, "sync_file_%d" % (int(time.time())) + self, f"sync_file_{uuid.uuid4().hex}" ) - self.addTearDownHook( - lambda: self.run_platform_command("rm %s" % (sync_file_path)) + + proc = self.spawnSubprocess( + program, [sync_file_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) + self.addTearDownHook(lambda: proc.kill()) - proc = self.spawnSubprocess(program, [sync_file_path]) lldbutil.wait_for_file_on_target(self, sync_file_path) - self.attach(pid=proc.pid, disconnectAutomatically=False, stopOnEntry=True) - self.continue_to_next_stop() - response = self.dap_server.request_evaluate("wait_for_attach = false;") - self.assertTrue(response["success"]) + with session.configure(AttachArgs(pid=proc.pid)) as cm: + line = line_number(self.source, "// attach breakpoint") + [bp_id] = session.resolve_source_breakpoints(self.source, [line]) + stop_event = session.verify_stopped_on_breakpoint(bp_id, after=cm.process_event) + + self.assertFalse(os.path.exists(side_effect)) + + top_frame = session.top_frame_from(stop_event) + self.logger.info("frame name: %s", top_frame.name) + top_frame.evaluate("`expr wait_for_attach = false;", context="repl") + + # Verify the variable changed. + wait_for_attach_var = top_frame.evaluate("wait_for_attach", context="hover") + self.assertEqual(wait_for_attach_var.result, "false") + + session.disconnect() - # verify we haven't produced the side effect file yet - self.assertFalse(os.path.exists(program + ".side_effect")) + # Wait for the process to run to completion. + proc.wait(timeout=10) - self.dap_server.request_disconnect() - time.sleep(2) - # verify we produced the side effect file, as the program continued after disconnecting - self.assertTrue(os.path.exists(program + ".side_effect")) + self.assertTrue(os.path.exists(side_effect)) diff --git a/lldb/test/API/tools/lldb-dap/disconnect/main.cpp b/lldb/test/API/tools/lldb-dap/disconnect/main.cpp index ca9610da4566a..92effee48023b 100644 --- a/lldb/test/API/tools/lldb-dap/disconnect/main.cpp +++ b/lldb/test/API/tools/lldb-dap/disconnect/main.cpp @@ -16,8 +16,10 @@ void handle_attach(char *sync_file_path) { sync_file.open(sync_file_path); } - while (wait_for_attach) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + while (wait_for_attach) { + const auto sleep_time = std::chrono::milliseconds(10); + std::this_thread::sleep_for(sleep_time); // attach breakpoint + } } int main(int argc, char **args) { diff --git a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py index 7c32ae4fb8c38..e9adf6921b373 100644 --- a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py +++ b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py @@ -3,209 +3,180 @@ """ from base64 import b64decode -import dap_server + from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * -from lldbsuite.test import lldbutil -import lldbdap_testcase -import os +from lldbsuite.test.tools.lldb_dap import DAPTestCaseBase, DAPTestSession +from lldbsuite.test.tools.lldb_dap.types import * -class TestDAP_memory(lldbdap_testcase.DAPTestCaseBase): - @skipIfWindows - def test_memory_refs_variables(self): - """ - Tests memory references for evaluate - """ +class TestDAP_memory(DAPTestCaseBase): + def stop_at_breakpoint(self, session: DAPTestSession): + """Build, launch, and stop at the `// Breakpoint` line. Returns the + top frame at that stop.""" program = self.getBuildArtifact("a.out") - self.build_and_launch(program) - source = "main.cpp" - self.source_path = os.path.join(os.getcwd(), source) - self.set_source_breakpoints( - source, - [line_number(source, "// Breakpoint")], - ) - self.continue_to_next_stop() + source = self.getSourcePath("main.cpp") + bp_line = line_number(source, "// Breakpoint") + with session.configure(LaunchArgs(program)) as ctx: + session.resolve_source_breakpoints(source, [bp_line]) + stop_event = session.verify_stopped_on_breakpoint(after=ctx.process_event) + thread_ctx = session.thread_context_from(stop_event) + return thread_ctx.top_frame() - locals = {l["name"]: l for l in self.dap_server.get_local_variables()} + @skipIfWindows + def test_memory_refs_variables(self): + """Tests memory references on local variables.""" + session = self.build_and_create_session() + top_frame = self.stop_at_breakpoint(session) + locals = top_frame.locals - # Pointers should have memory-references - self.assertIn("memoryReference", locals["rawptr"].keys()) - # Non-pointers should also have memory-references - self.assertIn("memoryReference", locals["not_a_ptr"].keys()) + # Pointers should have memory references. + self.assertIsNotNone(locals["rawptr"].memoryReference) + # Non-pointers should also have memory references. + self.assertIsNotNone(locals["not_a_ptr"].memoryReference) @skipIfWindows def test_memory_refs_evaluate(self): - """ - Tests memory references for evaluate - """ - program = self.getBuildArtifact("a.out") - self.build_and_launch(program) - source = "main.cpp" - self.source_path = os.path.join(os.getcwd(), source) - self.set_source_breakpoints( - source, - [line_number(source, "// Breakpoint")], - ) - self.continue_to_next_stop() + """Tests memory references on `evaluate` responses.""" + session = self.build_and_create_session() + top_frame = self.stop_at_breakpoint(session) - self.assertIn( - "memoryReference", - self.dap_server.request_evaluate("rawptr")["body"].keys(), - ) + eval_body = session.evaluate("rawptr", frameId=top_frame.id) + self.assertIsNotNone(eval_body.memoryReference) @skipIfWindows def test_memory_refs_set_variable(self): - """ - Tests memory references for `setVariable` - """ - program = self.getBuildArtifact("a.out") - self.build_and_launch(program) - source = "main.cpp" - self.source_path = os.path.join(os.getcwd(), source) - self.set_source_breakpoints( - source, - [line_number(source, "// Breakpoint")], - ) - self.continue_to_next_stop() + """Tests memory references on `setVariable` responses.""" + session = self.build_and_create_session() + top_frame = self.stop_at_breakpoint(session) + locals = top_frame.locals - ptr_value = self.get_local_as_int("rawptr") - self.assertIn( - "memoryReference", - self.set_local("rawptr", ptr_value + 2)["body"].keys(), - ) + ptr_value = locals["rawptr"].variable.value_as_int + response = locals.set("rawptr", ptr_value + 2) + response = self.expect_success(response) + self.assertIsNotNone(response.body.memoryReference) @skipIfWindows - @skipIfWasm # the test finds the memory to read by evaluating an expression + @requireExpressionEvaluation def test_readMemory(self): - """ - Tests the 'readMemory' request - """ - program = self.getBuildArtifact("a.out") - self.build_and_launch(program) - source = "main.cpp" - self.source_path = os.path.join(os.getcwd(), source) - self.set_source_breakpoints( - source, - [line_number(source, "// Breakpoint")], + """Tests the `readMemory` request.""" + session = self.build_and_create_session() + top_frame = self.stop_at_breakpoint(session) + + eval_body = session.evaluate("*rawptr", frameId=top_frame.id) + memref = self.expect_not_none(eval_body.memoryReference) + + # We can read the complete string. + response = session.read_memory(memref, count=5, offset=0).result() + data = self.expect_not_none(response.body.data) + self.assertEqual(b64decode(data), b"dead\0") + + # Large reads return partial results. + response = session.read_memory(memref, count=4096, offset=0).result() + data = self.expect_not_none(response.body.data) + self.assertEqual(b64decode(data)[0:5], b"dead\0") + + # Offsets work. + response = session.read_memory(memref, count=3, offset=2).result() + data = self.expect_not_none(response.body.data) + self.assertEqual(b64decode(data), b"ad\0") + + # Reads of size 0 are successful. + # VSCode uses these to probe whether a memoryReference can actually be dereferenced. + response = session.read_memory(memref, count=0, offset=0).result() + self.assertIsNone( + response.body.data, f"expects no data in response: {response!r}" ) - self.continue_to_next_stop() - ptr_deref = self.dap_server.request_evaluate("*rawptr")["body"] - memref = ptr_deref["memoryReference"] - - # We can read the complete string - mem = self.dap_server.request_readMemory(memref, 0, 5)["body"] - self.assertEqual(b64decode(mem["data"]), b"dead\0") - - # We can read large chunks, potentially returning partial results - mem = self.dap_server.request_readMemory(memref, 0, 4096)["body"] - self.assertEqual(b64decode(mem["data"])[0:5], b"dead\0") - - # Use an offset - mem = self.dap_server.request_readMemory(memref, 2, 3)["body"] - self.assertEqual(b64decode(mem["data"]), b"ad\0") - - # Reads of size 0 are successful - # VS Code sends those in order to check if a `memoryReference` can actually be dereferenced. - mem = self.dap_server.request_readMemory(memref, 0, 0) - self.assertEqual(mem["success"], True) - self.assertNotIn( - "data", mem["body"], f"expects no data key in response: {mem!r}" - ) - - # Reads at offset 0x0 return unreadable bytes + # Reads at offset 0x0 return unreadable bytes. bytes_to_read = 6 - mem = self.dap_server.request_readMemory("0x0", 0, bytes_to_read) - self.assertEqual(mem["body"]["unreadableBytes"], bytes_to_read) + response = session.read_memory("0x0", count=bytes_to_read, offset=0).result() + self.assertEqual(response.body.unreadableBytes, bytes_to_read) - # Reads with invalid address fails. - mem = self.dap_server.request_readMemory("-3204", 0, 10) - self.assertFalse(mem["success"], "expect fail on reading memory.") + # Reads with an invalid address fail. + session.read_memory("-3204", count=10, offset=0).error( + "expect fail on reading memory." + ) - self.continue_to_exit() + session.continue_to_exit() # Flakey on 32-bit Arm Linux. @skipIf(oslist=["linux"], archs=["arm$"]) - @skipIfWasm # the test finds the memory to write by evaluating an expression + @requireExpressionEvaluation def test_writeMemory(self): - """ - Tests the 'writeMemory' request - """ - program = self.getBuildArtifact("a.out") - self.build_and_launch(program) - source = "main.cpp" - self.source_path = os.path.join(os.getcwd(), source) - self.set_source_breakpoints( - source, - [line_number(source, "// Breakpoint")], - ) - self.continue_to_next_stop() + """Tests the `writeMemory` request.""" + session = self.build_and_create_session() + top_frame = self.stop_at_breakpoint(session) - # Get the 'not_a_ptr' writable variable reference address. - ptr_deref = self.dap_server.request_evaluate("not_a_ptr")["body"] - memref = ptr_deref["memoryReference"] + # Get `not_a_ptr`'s writable variable's memory reference. + eval_body = session.evaluate("not_a_ptr", frameId=top_frame.id) + memref = self.expect_not_none(eval_body.memoryReference) # Write the decimal value 50 (0x32 in hexadecimal) to memory. - # This corresponds to the ASCII character '2' and encodes to Base64 as "Mg==". - mem_response = self.writeMemory(memref, 50, 0, True) - self.assertEqual(mem_response["success"], True) - self.assertEqual(mem_response["body"]["bytesWritten"], 1) - - # Read back the modified memory and verify that the written data matches - # the expected result. - mem_response = self.dap_server.request_readMemory(memref, 0, 1) - self.assertEqual(mem_response["success"], True) - self.assertEqual(mem_response["body"]["data"], "Mg==") - - # Write the decimal value 100 (0x64 in hexadecimal) to memory. - # This corresponds to the ASCII character 'd' and encodes to Base64 as "ZA==". - # allowPartial=False - mem_response = self.writeMemory(memref, 100, 0, False) - self.assertEqual(mem_response["success"], True) - self.assertEqual(mem_response["body"]["bytesWritten"], 1) - - # Read back the modified memory and verify that the written data matches - # the expected result. - mem_response = self.dap_server.request_readMemory(memref, 0, 1) - self.assertEqual(mem_response["success"], True) - self.assertEqual(mem_response["body"]["data"], "ZA==") - - # Memory write failed for 0x0. - mem_response = self.writeMemory("0x0", 50, 0, True) - self.assertEqual(mem_response["success"], False) - - # Malformed memory reference. - mem_response = self.writeMemory("12345", 50, 0, True) - self.assertEqual(mem_response["success"], False) - - ptr_deref = self.dap_server.request_evaluate("nonWritable")["body"] - memref = ptr_deref["memoryReference"] - - # Writing to non-writable region should return an appropriate error. - mem_response = self.writeMemory(memref, 50, 0, False) - self.assertEqual(mem_response["success"], False) + # This corresponds to the ASCII character '2' and encodes to base64 + # as "Mg==". + response = session.write_memory(memref, value=50, offset=0, allowPartial=True) + response = self.expect_success(response) + self.assertEqual(response.body.bytesWritten, 1) + + # Read back and verify. + read_response = session.read_memory(memref, count=1, offset=0).result() + self.assertEqual(read_response.body.data, "Mg==") + + # Write the decimal value 100 (0x64 in hexadecimal) to memory with + # allowPartial=False. This corresponds to the ASCII character 'd' and + # encodes to base64 as "ZA==". + response = session.write_memory(memref, value=100, offset=0, allowPartial=False) + response = self.expect_success(response) + self.assertEqual(response.body.bytesWritten, 1) + + # Read back and verify. + read_response = session.read_memory(memref, count=1, offset=0).result() + self.assertEqual(read_response.body.data, "ZA==") + + # Writing to 0x0 fails. + response = session.write_memory("0x0", value=50, offset=0, allowPartial=True) + self.expect_error(response) + + # Writing to a malformed memory reference fails. + response = session.write_memory("12345", value=50, offset=0, allowPartial=True) + self.expect_error(response) + + # Writing to a non-writable region returns a not-writable error. + eval_body = session.evaluate("nonWritable", frameId=top_frame.id) + nonwritable_ref = self.expect_not_none(eval_body.memoryReference) + response = session.write_memory( + nonwritable_ref, value=50, offset=0, allowPartial=False + ) + err = self.expect_error(response) + err_msg = self.expect_not_none(err.body and err.body.error) self.assertRegex( - mem_response["body"]["error"]["format"], - r"Memory " + memref + " region is not writable", + err_msg.format, + rf"Memory {nonwritable_ref} region is not writable", ) - # Trying to write empty value; data="" - mem_response = self.writeMemory(memref) - self.assertEqual(mem_response["success"], False) + # Writing an empty value (no data) fails. + response = session.write_memory(nonwritable_ref, value="") + err = self.expect_error(response) + err_msg = self.expect_not_none(err.body and err.body.error) self.assertRegex( - mem_response["body"]["error"]["format"], + err_msg.format, r"Data cannot be empty value. Provide valid data", ) - # Verify that large memory writes fail if the range spans non-writable - # or non -contiguous regions. + # Large writes spanning non-writable regions fail. data = bytes([0xFF] * 8192) - mem_response = self.writeMemory( - memref, int.from_bytes(data, byteorder="little"), 0, False + + response = session.write_memory( + nonwritable_ref, + value=data, + offset=0, + allowPartial=False, ) - self.assertEqual(mem_response["success"], False) + err = self.expect_error(response) + err_msg = self.expect_not_none(err.body and err.body.error) self.assertRegex( - mem_response["body"]["error"]["format"], - r"Memory " + memref + " region is not writable", + err_msg.format, rf"Memory {nonwritable_ref} region is not writable" ) + + session.continue_to_exit() diff --git a/lldb/test/API/tools/lldb-dap/server/main.c b/lldb/test/API/tools/lldb-dap/server/main.c index 446ae82532af5..6f69c9568b037 100644 --- a/lldb/test/API/tools/lldb-dap/server/main.c +++ b/lldb/test/API/tools/lldb-dap/server/main.c @@ -6,5 +6,5 @@ int main(int argc, char const *argv[]) { } else { printf("Hello World!\n"); } - return 0; + return 0; // breakpoint 2 } _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
