https://github.com/da-viper created https://github.com/llvm/llvm-project/pull/217403
- TestDAP_save_core.py - TestDAP_coreFile.py There is a bug in the number of modules reported from the core file in TestDAP_save_core.py. The test previously assertsTrue instead of assertsEqual. >From 7a3aee8c0637722024ae2d5b1bfc079a6285c772 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Fri, 24 Jul 2026 21:24:19 +0100 Subject: [PATCH] [lldb-dap] Migrate core file test - TestDAP_save_core.py - TestDAP_coreFile.py There is a bug in the number of modules reported from the core file in TestDAP_save_core.py. The test previously assertsTrue instead of assertsEqual. --- .../lldbsuite/test/tools/lldb_dap/types.py | 8 +- .../lldb-dap/coreFile/TestDAP_coreFile.py | 283 +++++++++--------- .../lldb-dap/save-core/TestDAP_save_core.py | 117 ++++---- 3 files changed, 199 insertions(+), 209 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb_dap/types.py b/lldb/packages/Python/lldbsuite/test/tools/lldb_dap/types.py index d75a1ea8fb3b9..94980cb081f73 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb_dap/types.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb_dap/types.py @@ -1080,7 +1080,7 @@ class InitializeArgs: supportsStartDebuggingRequest: Optional[bool] = None supportsANSIStyling: Optional[bool] = None sourceInitFile: bool = field( - metadata={"alias": "$__lldbSourceInitFile"}, default=False + metadata={"alias": "$__lldb_sourceInitFile"}, default=False ) command_ = "initialize" @@ -1118,9 +1118,9 @@ class LaunchArgs: env: Optional[Union[Dict[str, str], List[str]]] = None detachOnError: Optional[bool] = None disableASLR: bool = False - disableSTDIO: bool = False + disableSTDIO: Optional[bool] = None shellExpandArguments: bool = False - console: Console = Console.INTERNAL + console: Optional[Console] = None stdio: Optional[List[Optional[str]]] = None # Configurations. @@ -1128,7 +1128,7 @@ class LaunchArgs: enableAutoVariableSummaries: bool = False enableSyntheticChildDebugging: bool = False displayExtendedBacktrace: bool = False - stopOnEntry: bool = False + stopOnEntry: Optional[bool] = None timeout: Optional[float] = None commandEscapePrefix: Optional[str] = None customFrameFormat: Optional[str] = None diff --git a/lldb/test/API/tools/lldb-dap/coreFile/TestDAP_coreFile.py b/lldb/test/API/tools/lldb-dap/coreFile/TestDAP_coreFile.py index 7c1bbad6afaba..94bf30956e7a8 100644 --- a/lldb/test/API/tools/lldb-dap/coreFile/TestDAP_coreFile.py +++ b/lldb/test/API/tools/lldb-dap/coreFile/TestDAP_coreFile.py @@ -2,197 +2,184 @@ Test lldb-dap coreFile attaching """ -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 +from lldbsuite.test.tools.lldb_dap.types import ( + AttachArgs, + ContinueArgs, + NextArgs, + Source, + StackFrame, +) # The expected backtrace when loading the bundled linux-x86_64.core. Shared by # the tests that load this core through different mechanisms (the "coreFile" # attach key and "attachCommands") so we can assert they behave identically. EXPECTED_CORE_FRAMES = [ - { - "column": 0, - "id": 524288, - "line": 4, - "moduleId": "01DF54A6-045E-657D-3F8F-FB9CE1118789-14F8BD6D", - "name": "bar", - "source": { - "name": "main.c", - "path": "/home/labath/test/main.c", - "presentationHint": "deemphasize", - }, - "instructionPointerReference": "0x40011C", - }, - { - "column": 0, - "id": 524289, - "line": 10, - "moduleId": "01DF54A6-045E-657D-3F8F-FB9CE1118789-14F8BD6D", - "name": "foo", - "source": { - "name": "main.c", - "path": "/home/labath/test/main.c", - "presentationHint": "deemphasize", - }, - "instructionPointerReference": "0x400142", - }, - { - "column": 0, - "id": 524290, - "line": 16, - "moduleId": "01DF54A6-045E-657D-3F8F-FB9CE1118789-14F8BD6D", - "name": "_start", - "source": { - "name": "main.c", - "path": "/home/labath/test/main.c", - "presentationHint": "deemphasize", - }, - "instructionPointerReference": "0x40015F", - }, + StackFrame( + column=0, + id=524288, + line=4, + moduleId="01DF54A6-045E-657D-3F8F-FB9CE1118789-14F8BD6D", + name="bar", + source=Source( + name="main.c", + path="/home/labath/test/main.c", + presentationHint="deemphasize", + ), + instructionPointerReference="0x40011C", + ), + StackFrame( + column=0, + id=524289, + line=10, + moduleId="01DF54A6-045E-657D-3F8F-FB9CE1118789-14F8BD6D", + name="foo", + source=Source( + name="main.c", + path="/home/labath/test/main.c", + presentationHint="deemphasize", + ), + instructionPointerReference="0x400142", + ), + StackFrame( + column=0, + id=524290, + line=16, + moduleId="01DF54A6-045E-657D-3F8F-FB9CE1118789-14F8BD6D", + name="_start", + source=Source( + name="main.c", + path="/home/labath/test/main.c", + presentationHint="deemphasize", + ), + instructionPointerReference="0x40015F", + ), ] -class TestDAP_coreFile(lldbdap_testcase.DAPTestCaseBase): - @skipIfLLVMTargetMissing("X86") +@skipIfLLVMTargetMissing("X86") +class TestDAP_coreFile(DAPTestCaseBase): def test_core_file(self): - current_dir = os.path.dirname(__file__) - exe_file = os.path.join(current_dir, "linux-x86_64.out") - core_file = os.path.join(current_dir, "linux-x86_64.core") - - self.create_debug_adapter() - self.attach(program=exe_file, coreFile=core_file) - self.dap_server.request_configurationDone() + exe_file = self.getSourcePath("linux-x86_64.out") + core_file = self.getSourcePath("linux-x86_64.core") - expected_frames = EXPECTED_CORE_FRAMES + session = self.create_session() + process_event = session.attach(AttachArgs(program=exe_file, coreFile=core_file)) + stop_event = session.wait_for_stopped_event(after=process_event) + thread_id = self.expect_not_none(stop_event.body.threadId) - self.assertEqual(self.get_stackFrames(), expected_frames) + frames = session.stack_trace(thread_id).body.stackFrames + self.assertEqual(frames, EXPECTED_CORE_FRAMES) - # Resuming should have no effect and keep the process stopped - resp = self.dap_server.request_continue() - self.assertFalse(resp["success"]) - self.assertEqual(self.get_stackFrames(), expected_frames) + # Resuming a core process should fail. the process stays stopped + # with the same backtrace. + session.send_request(ContinueArgs(thread_id)).error() + frames = session.stack_trace(thread_id).body.stackFrames + self.assertEqual(frames, EXPECTED_CORE_FRAMES) - self.dap_server.request_next(threadId=32259) - self.assertEqual(self.get_stackFrames(), expected_frames) + # Same for step-over. + session.send_request(NextArgs(threadId=thread_id)).error() + frames = session.stack_trace(thread_id).body.stackFrames + self.assertEqual(frames, EXPECTED_CORE_FRAMES) - @skipIfLLVMTargetMissing("X86") def test_core_file_attach_commands(self): """Loading a core through "attachCommands" (e.g. `target create --core`) should behave identically to using the "coreFile" attach key: the session stops with the real crash reason and cannot be resumed.""" - current_dir = os.path.dirname(__file__) - exe_file = os.path.join(current_dir, "linux-x86_64.out") - core_file = os.path.join(current_dir, "linux-x86_64.core") + exe_file = self.getSourcePath("linux-x86_64.out") + core_file = self.getSourcePath("linux-x86_64.core") - self.create_debug_adapter() + session = self.create_session() # Bootstrap the core target purely through a custom attach command, # mirroring how the "coreFile" key passes the same program. - self.attach( - program=exe_file, - attachCommands=['target create --core "%s" "%s"' % (core_file, exe_file)], - ) - # configurationDone must succeed: a core is a non-live session, so the # adapter must not try to resume it (resuming a core fails). - resp = self.dap_server.request_configurationDone() - self.assertTrue( - resp["success"], - "configurationDone should succeed for a core loaded via attachCommands", + process_event = session.attach( + AttachArgs( + program=exe_file, + attachCommands=[f"target create --core '{core_file}' '{exe_file}"], + ) ) - # The backtrace must match the "coreFile" attach key exactly. - self.assertEqual(self.get_stackFrames(), EXPECTED_CORE_FRAMES) - # The stop must be reported with the real crash reason, not "entry". - self.dap_server.wait_for_stopped() - found_exception = any( - body.get("reason") == "exception" - for body in self.dap_server.thread_stop_reasons.values() - ) - self.assertTrue( - found_exception, - f"Expected a thread stopped with reason 'exception', got: " - f"{self.dap_server.thread_stop_reasons}", - ) + stop_event = session.verify_stopped_on_exception(after=process_event) + thread_id = self.expect_not_none(stop_event.body.threadId) - # Resuming should have no effect and keep the process stopped. - resp = self.dap_server.request_continue() - self.assertFalse(resp["success"]) - self.assertEqual(self.get_stackFrames(), EXPECTED_CORE_FRAMES) + # The backtrace must match the "coreFile" attach key exactly. + frames = session.stack_trace(thread_id).body.stackFrames + self.assertEqual(frames, EXPECTED_CORE_FRAMES) + + # Resuming should fail. + session.send_request(ContinueArgs(thread_id)).error() + frames = session.stack_trace(thread_id).body.stackFrames + self.assertEqual(frames, EXPECTED_CORE_FRAMES) def test_wrong_core_file(self): + """Attaching with a file that isn't a real core should fail cleanly + during configurationDone rather than crashing the adapter.""" exe_file = self.getSourcePath("linux-x86_64.out") wrong_core_file = self.getSourcePath("main.c") - self.create_debug_adapter() - resp = self.attach_and_configurationDone( - program=exe_file, coreFile=wrong_core_file + session = self.create_session() + session.initialize_sequence(session.initialize_args) + pending_attach = session.send_request( + AttachArgs(program=exe_file, coreFile=wrong_core_file) ) - self.assertIsNotNone(resp) - self.assertFalse(resp["success"], "Expected failure in response {resp!r}") - error_msg = resp["body"]["error"]["format"] + session.verify_configuration_done(expected_success=False) - # attach may fail for mutilple reasons. - self.assertEqual(error_msg, "Failed to create the process") + resp = pending_attach.error() + resp_error = self.expect_not_none(resp.body and resp.body.error) + self.assertEqual(resp_error.format, "Failed to create the process") - @skipIfLLVMTargetMissing("X86") def test_core_file_stopped_reason(self): - """Test that the stopped event for a core file reports the actual crash + """The stopped event for a core file should report the actual crash reason (e.g. 'exception') rather than 'entry'.""" - current_dir = os.path.dirname(__file__) - exe_file = os.path.join(current_dir, "linux-x86_64.out") - core_file = os.path.join(current_dir, "linux-x86_64.core") - - self.create_debug_adapter() - self.attach(program=exe_file, coreFile=core_file) - self.dap_server.request_configurationDone() - self.dap_server.wait_for_stopped() - - # Core files should report the actual crash reason, not 'entry'. - stop_reasons = self.dap_server.thread_stop_reasons - self.assertGreater(len(stop_reasons), 0, "Expected at least one stopped thread") - - # Find any thread with a stop reason — the crashing thread should - # report 'exception' with a description about the signal. - found_exception = False - for tid, body in stop_reasons.items(): - if body.get("reason") == "exception": - found_exception = True - self.assertIn("description", body) - break - self.assertTrue( - found_exception, - f"Expected at least one thread with stop reason 'exception', " - f"got: {stop_reasons}", - ) + exe_file = self.getSourcePath("linux-x86_64.out") + core_file = self.getSourcePath("linux-x86_64.core") - @skipIfLLVMTargetMissing("X86") - def test_core_file_source_mapping_array(self): - """Test that sourceMap property is correctly applied when loading a core""" - current_dir = os.path.dirname(__file__) - exe_file = os.path.join(current_dir, "linux-x86_64.out") - core_file = os.path.join(current_dir, "linux-x86_64.core") + session = self.create_session() + process_event = session.attach(AttachArgs(program=exe_file, coreFile=core_file)) - self.create_debug_adapter() + stop_event = session.verify_stopped_on_exception(after=process_event) + self.assertIsNotNone(stop_event.body.description, "expect a stop description.") - source_map = [["/home/labath/test", current_dir]] - self.attach(program=exe_file, coreFile=core_file, sourceMap=source_map) + def test_core_file_source_mapping_array(self): + """Test that sourceMap property is correctly applied when loading a core""" + exe_file = self.getSourcePath("linux-x86_64.out") + core_file = self.getSourcePath("linux-x86_64.core") + current_dir = self.getSourceDir() + + session = self.create_session() + process_event = session.attach( + AttachArgs( + program=exe_file, + coreFile=core_file, + sourceMap=[("/home/labath/test", current_dir)], + ) + ) - self.assertIn(current_dir, self.get_stackFrames()[0]["source"]["path"]) + stop_event = session.verify_stopped_on_exception(after=process_event) + top_frame = session.top_frame_from(stop_event).frame + top_source = self.expect_not_none(top_frame.source and top_frame.source.path) + self.assertIn(current_dir, top_source) - @skipIfLLVMTargetMissing("X86") def test_core_file_source_mapping_object(self): """Test that sourceMap property is correctly applied when loading a core""" - current_dir = os.path.dirname(__file__) - exe_file = os.path.join(current_dir, "linux-x86_64.out") - core_file = os.path.join(current_dir, "linux-x86_64.core") - - self.create_debug_adapter() - - source_map = {"/home/labath/test": current_dir} - self.attach(program=exe_file, coreFile=core_file, sourceMap=source_map) + exe_file = self.getSourcePath("linux-x86_64.out") + core_file = self.getSourcePath("linux-x86_64.core") + current_dir = self.getSourceDir() + + session = self.create_session() + process_event = session.attach( + AttachArgs( + program=exe_file, + coreFile=core_file, + sourceMap={"/home/labath/test": current_dir}, + ) + ) - self.assertIn(current_dir, self.get_stackFrames()[0]["source"]["path"]) + stop_event = session.verify_stopped_on_exception(after=process_event) + top_frame = session.top_frame_from(stop_event).frame + top_source = self.expect_not_none(top_frame.source and top_frame.source.path) + self.assertIn(current_dir, top_source) diff --git a/lldb/test/API/tools/lldb-dap/save-core/TestDAP_save_core.py b/lldb/test/API/tools/lldb-dap/save-core/TestDAP_save_core.py index 77c1e47914a39..a8239ff4a082f 100644 --- a/lldb/test/API/tools/lldb-dap/save-core/TestDAP_save_core.py +++ b/lldb/test/API/tools/lldb-dap/save-core/TestDAP_save_core.py @@ -1,73 +1,76 @@ """ -Test saving core minidump from lldb-dap +Test lldb-dap saving a core file and attaching to it. """ -import dap_server +from pathlib import Path + from lldbsuite.test.decorators import * -from lldbsuite.test.lldbtest import * -import lldbdap_testcase -from lldbsuite.test import lldbutil +from lldbsuite.test.lldbtest import line_number +from lldbsuite.test.tools.lldb_dap import DAPTestCaseBase +from lldbsuite.test.tools.lldb_dap.types import AttachArgs, LaunchArgs + +class TestDAP_save_core(DAPTestCaseBase): + SHARED_BUILD_TESTCASE = False -class TestDAP_save_core(lldbdap_testcase.DAPTestCaseBase): @skipUnlessArch("x86_64") - @skipUnlessPlatform(["linux"]) - def test_save_core(self): - """ - Tests saving core minidump from lldb-dap. - """ - program = self.getBuildArtifact("a.out") - self.build_and_launch(program) + @requireLinux + def test_save_and_reload_core(self): + """Save minidump cores in every supported `--style` from a single + live stop, then re-attach to each and verify the frame, thread + count, and module count all round-trip.""" + + session = self.build_and_create_session() source = "main.cpp" - # source_path = os.path.join(os.getcwd(), source) - breakpoint1_line = line_number(source, "// breakpoint 1") - lines = [breakpoint1_line] - # Set breakpoint in the thread function so we can step the threads - breakpoint_ids = self.set_source_breakpoints(source, lines) - self.assertEqual( - len(breakpoint_ids), len(lines), "expect correct number of breakpoints" - ) - self.continue_to_breakpoints(breakpoint_ids) + program = self.getBuildArtifact("a.out") + breakpoint_line = line_number(source, "// breakpoint 1") - # Getting dap stack trace may trigger __lldb_caller_function JIT module to be created. - self.get_stackFrames(startFrame=0) + with session.configure(LaunchArgs(program)) as cm: + [bp_id] = session.resolve_source_breakpoints(source, [breakpoint_line]) - modules = self.dap_server.get_modules() - thread_count = len(self.dap_server.get_threads()) + stop_event = session.verify_stopped_on_breakpoint(bp_id, after=cm.process_event) - core_stack = self.getBuildArtifact("core.stack.dmp") - core_dirty = self.getBuildArtifact("core.dirty.dmp") - core_full = self.getBuildArtifact("core.full.dmp") + # Snapshot the live state so we can assert the reloaded core matches. + thread_count = len(session.get_threads()) + module_count = len(session.get_modules()) - base_command = "`process save-core --plugin-name=minidump " - self.dap_server.request_evaluate( - base_command + " --style=stack '%s'" % (core_stack), context="repl" - ) + core_styles = ["stack", "modified-memory", "full"] + top_frame = session.top_frame_from(stop_event) + for style in core_styles: + path = Path(self.getBuildArtifact(f"core.{style}.dmp")) + self.assertFalse(path.exists(), f"stale core file: {path}") - self.assertTrue(os.path.isfile(core_stack)) - self.verify_core_file(core_stack, len(modules), thread_count) + save_core = "process save-core --plugin-name=minidump" + top_frame.evaluate(f"`{save_core} --style={style} {path}", context="repl") + self.assertTrue(path.is_file(), f"{style} core file is a file") - self.dap_server.request_evaluate( - base_command + " --style=modified-memory '%s'" % (core_dirty), - context="repl", - ) - self.assertTrue(os.path.isfile(core_dirty)) - self.verify_core_file(core_dirty, len(modules), thread_count) + with self.subTest(style=style): + self.verify_core(style, path, module_count, thread_count) + + session.continue_to_exit(exitCode=3) + + def verify_core( + self, style: str, core_path: Path, module_count: int, thread_count: int + ): + """Attach to a saved core and verify the reloaded process state + matches what was captured live: current frame, thread count, + module count.""" + session = self.create_session(adapter=self.create_stdio_debug_adapter()) + process_event = session.attach(AttachArgs(coreFile=str(core_path))) - self.dap_server.request_evaluate( - base_command + " --style=full '%s'" % (core_full), context="repl" + stop_event = session.verify_stopped_on_exception(after=process_event) + top_frame = session.top_frame_from(stop_event).frame + self.assertTrue( + top_frame.name.startswith("function"), + "expected to stop inside `function`", ) - self.assertTrue(os.path.isfile(core_full)) - self.verify_core_file(core_full, len(modules), thread_count) - - def verify_core_file(self, core_path, expected_module_count, expected_thread_count): - # To verify, we'll launch with the mini dump - target = self.dbg.CreateTarget(None) - process = target.LoadCore(core_path) - - # check if the core is in desired state - self.assertTrue(process, PROCESS_IS_VALID) - self.assertTrue(process.GetProcessInfo().IsValid()) - self.assertNotEqual(target.GetTriple().find("linux"), -1) - self.assertTrue(target.GetNumModules(), expected_module_count) - self.assertEqual(process.GetNumThreads(), expected_thread_count) + + expected_line = line_number("main.cpp", "// breakpoint 1") + self.assertEqual(top_frame.line, expected_line) + + core_thread_count = len(session.get_threads()) + core_module_count = len(session.get_modules()) + + self.assertEqual(core_thread_count, thread_count) + if style != "full": # FIXME: There is a bug on linux + self.assertEqual(core_module_count, module_count) _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
