Files
clang-p2996/lldb/test/API/tools/lldb-dap/stackTraceDisassemblyDisplay/TestDAP_stackTraceDisassemblyDisplay.py
John Harrison 0e0b501bf5 [lldb-dap] Take two at refactoring the startup sequence. (#140331)
This is more straight forward refactor of the startup sequence that
reverts parts of ba29e60f9a. Unlike my
previous attempt, I ended up removing the pending request queue and not
including an `AsyncReqeustHandler` because I don't think we actually
need that at the moment.

The key is that during the startup flow there are 2 parallel operations
happening in the DAP that have different triggers.

* The `initialize` request is sent and once the response is received the
`launch` or `attach` is sent.
* When the `initialized` event is recieved the `setBreakpionts` and
other config requests are made followed by the `configurationDone`
event.

I moved the `initialized` event back to happen in the `PostRun` of the
`launch` or `attach` request handlers. This ensures that we have a valid
target by the time the configuration calls are made. I added also added
a few extra validations that to the `configurationeDone` handler to
ensure we're in an expected state.

I've also fixed up the tests to match the new flow. With the other
additional test fixes in 087a5d2ec7 I
think we've narrowed down the main source of test instability that
motivated the startup sequence change.
2025-05-16 19:28:34 -07:00

172 lines
5.7 KiB
Python

"""
Test lldb-dap stack trace when some of the source paths are missing
"""
from lldbsuite.test.lldbtest import line_number
import lldbdap_testcase
from contextlib import contextmanager
import os
OTHER_C_SOURCE_CODE = """
int no_source_func(int n) {
return n + 1; // Break here
}
"""
@contextmanager
def delete_file_on_exit(path):
try:
yield path
finally:
if os.path.exists(path):
os.remove(path)
class TestDAP_stackTraceMissingSourcePath(lldbdap_testcase.DAPTestCaseBase):
def build_and_run_until_breakpoint(self):
"""
Build the program and run until the breakpoint is hit, and return the stack frames.
"""
other_source_file = "other.c"
with delete_file_on_exit(other_source_file):
with open(other_source_file, "w") as f:
f.write(OTHER_C_SOURCE_CODE)
breakpoint_line = line_number(other_source_file, "// Break here")
program = self.getBuildArtifact("a.out")
self.build_and_launch(program, commandEscapePrefix="")
breakpoint_ids = self.set_source_breakpoints(
other_source_file, [breakpoint_line]
)
self.assertEqual(
len(breakpoint_ids), 1, "expect correct number of breakpoints"
)
self.continue_to_breakpoints(breakpoint_ids)
frames = self.get_stackFrames()
self.assertLessEqual(2, len(frames), "expect at least 2 frames")
self.assertIn(
"path",
frames[0]["source"],
"Expect source path to always be in frame (other.c)",
)
self.assertIn(
"path",
frames[1]["source"],
"Expect source path in always be in frame (main.c)",
)
return frames
def verify_frames_source(
self, frames, main_frame_assembly: bool, other_frame_assembly: bool
):
self.assertLessEqual(2, len(frames), "expect at least 2 frames")
source_0 = frames[0].get("source")
source_1 = frames[1].get("source")
self.assertIsNotNone(source_0, "Expects a source object in frame 0")
self.assertIsNotNone(source_1, "Expects a source object in frame 1")
# it does not always have a path.
source_0_path: str = source_0.get("path", "")
source_1_path: str = source_1.get("path", "")
if other_frame_assembly:
self.assertFalse(
source_0_path.endswith("other.c"),
"Expect original source path to not be in unavailable source frame (other.c)",
)
self.assertIn(
"sourceReference",
source_0,
"Expect sourceReference to be in unavailable source frame (other.c)",
)
else:
self.assertTrue(
source_0_path.endswith("other.c"),
"Expect original source path to be in normal source frame (other.c)",
)
self.assertNotIn(
"sourceReference",
source_0,
"Expect sourceReference to not be in normal source frame (other.c)",
)
if main_frame_assembly:
self.assertFalse(
source_1_path.endswith("main.c"),
"Expect original source path to not be in unavailable source frame (main.c)",
)
self.assertIn(
"sourceReference",
source_1,
"Expect sourceReference to be in unavailable source frame (main.c)",
)
else:
self.assertTrue(
source_1_path.endswith("main.c"),
"Expect original source path to be in normal source frame (main.c)",
)
self.assertNotIn(
"sourceReference",
source_1,
"Expect sourceReference to not be in normal source code frame (main.c)",
)
def test_stopDisassemblyDisplay(self):
"""
Test that with with all stop-disassembly-display values you get correct assembly / no assembly source code.
"""
self.build_and_run_until_breakpoint()
frames = self.get_stackFrames()
self.assertLessEqual(2, len(frames), "expect at least 2 frames")
self.assertIn(
"path",
frames[0]["source"],
"Expect source path to always be in frame (other.c)",
)
self.assertIn(
"path",
frames[1]["source"],
"Expect source path in always be in frame (main.c)",
)
self.dap_server.request_evaluate(
"settings set stop-disassembly-display never", context="repl"
)
frames = self.get_stackFrames()
self.verify_frames_source(
frames, main_frame_assembly=False, other_frame_assembly=False
)
self.dap_server.request_evaluate(
"settings set stop-disassembly-display always", context="repl"
)
frames = self.get_stackFrames()
self.verify_frames_source(
frames, main_frame_assembly=True, other_frame_assembly=True
)
self.dap_server.request_evaluate(
"settings set stop-disassembly-display no-source", context="repl"
)
frames = self.get_stackFrames()
self.verify_frames_source(
frames, main_frame_assembly=False, other_frame_assembly=True
)
self.dap_server.request_evaluate(
"settings set stop-disassembly-display no-debuginfo", context="repl"
)
frames = self.get_stackFrames()
self.verify_frames_source(
frames, main_frame_assembly=False, other_frame_assembly=False
)