Skip to content

[lldb-dap] Synchronously wait for breakpoints resolves in tests #140470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ def __init__(
self.initialized = False
self.frame_scopes = {}
self.init_commands = init_commands
self.resolved_breakpoints = {}

@classmethod
def encode_content(cls, s: str) -> bytes:
Expand Down Expand Up @@ -296,6 +297,9 @@ def _handle_recv_packet(self, packet: Optional[ProtocolMessage]) -> bool:
# and 'progressEnd' events. Keep these around in case test
# cases want to verify them.
self.progress_events.append(packet)
elif event == "breakpoint":
# Breakpoint events are sent when a breakpoint is resolved
self._update_verified_breakpoints([body["breakpoint"]])

elif packet_type == "response":
if packet["command"] == "disconnect":
Expand All @@ -309,6 +313,13 @@ def _process_continued(self, all_threads_continued: bool):
if all_threads_continued:
self.thread_stop_reasons = {}

def _update_verified_breakpoints(self, breakpoints: list[Event]):
for breakpoint in breakpoints:
if "id" in breakpoint:
self.resolved_breakpoints[str(breakpoint["id"])] = breakpoint.get(
"verified", False
)

def send_packet(self, command_dict: Request, set_sequence=True):
"""Take the "command_dict" python dictionary and encode it as a JSON
string and send the contents as a packet to the VSCode debug
Expand Down Expand Up @@ -454,6 +465,17 @@ def wait_for_breakpoint_events(self, timeout: Optional[float] = None):
breakpoint_events.append(event)
return breakpoint_events

def wait_for_breakpoints_to_be_verified(
self, breakpoint_ids: list[str], timeout: Optional[float] = None
):
"""Wait for all breakpoints to be verified. Return all unverified breakpoints."""
while any(id not in self.resolved_breakpoints for id in breakpoint_ids):
breakpoint_event = self.wait_for_event("breakpoint", timeout=timeout)
if breakpoint_event is None:
break

return [id for id in breakpoint_ids if id not in self.resolved_breakpoints]

def wait_for_exited(self, timeout: Optional[float] = None):
event_dict = self.wait_for_event("exited", timeout=timeout)
if event_dict is None:
Expand Down Expand Up @@ -1013,7 +1035,10 @@ def request_setBreakpoints(self, source: Source, line_array, data=None):
"type": "request",
"arguments": args_dict,
}
return self.send_recv(command_dict)
response = self.send_recv(command_dict)
if response["success"]:
self._update_verified_breakpoints(response["body"]["breakpoints"])
return response

def request_setExceptionBreakpoints(self, filters):
args_dict = {"filters": filters}
Expand All @@ -1039,7 +1064,10 @@ def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=Non
"type": "request",
"arguments": args_dict,
}
return self.send_recv(command_dict)
response = self.send_recv(command_dict)
if response["success"]:
self._update_verified_breakpoints(response["body"]["breakpoints"])
return response

def request_dataBreakpointInfo(
self, variablesReference, name, frameIndex=0, threadId=None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ def build_and_create_debug_adapter_for_attach(self):
self.build_and_create_debug_adapter(dictionary={"EXE": unique_name})
return self.getBuildArtifact(unique_name)

def set_source_breakpoints(self, source_path, lines, data=None):
def set_source_breakpoints(
self, source_path, lines, data=None, wait_for_resolve=True
):
"""Sets source breakpoints and returns an array of strings containing
the breakpoint IDs ("1", "2") for each breakpoint that was set.
Parameter data is array of data objects for breakpoints.
Expand All @@ -65,9 +67,13 @@ def set_source_breakpoints(self, source_path, lines, data=None):
breakpoint_ids = []
for breakpoint in breakpoints:
breakpoint_ids.append("%i" % (breakpoint["id"]))
if wait_for_resolve:
self.wait_for_breakpoints_to_resolve(breakpoint_ids)
return breakpoint_ids

def set_source_breakpoints_assembly(self, source_reference, lines, data=None):
def set_source_breakpoints_assembly(
self, source_reference, lines, data=None, wait_for_resolve=True
):
response = self.dap_server.request_setBreakpoints(
Source(source_reference=source_reference),
lines,
Expand All @@ -79,9 +85,13 @@ def set_source_breakpoints_assembly(self, source_reference, lines, data=None):
breakpoint_ids = []
for breakpoint in breakpoints:
breakpoint_ids.append("%i" % (breakpoint["id"]))
if wait_for_resolve:
self.wait_for_breakpoints_to_resolve(breakpoint_ids)
return breakpoint_ids

def set_function_breakpoints(self, functions, condition=None, hitCondition=None):
def set_function_breakpoints(
self, functions, condition=None, hitCondition=None, wait_for_resolve=True
):
"""Sets breakpoints by function name given an array of function names
and returns an array of strings containing the breakpoint IDs
("1", "2") for each breakpoint that was set.
Expand All @@ -95,8 +105,22 @@ def set_function_breakpoints(self, functions, condition=None, hitCondition=None)
breakpoint_ids = []
for breakpoint in breakpoints:
breakpoint_ids.append("%i" % (breakpoint["id"]))
if wait_for_resolve:
self.wait_for_breakpoints_to_resolve(breakpoint_ids)
return breakpoint_ids

def wait_for_breakpoints_to_resolve(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move this to DebugCommunication in dap_server.py? We already have helpers like wait_for_stopped, so this feels similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

self, breakpoint_ids: list[str], timeout: Optional[float] = DEFAULT_TIMEOUT
):
unresolved_breakpoints = self.dap_server.wait_for_breakpoints_to_be_verified(
breakpoint_ids, timeout
)
self.assertEqual(
len(unresolved_breakpoints),
0,
f"Expected to resolve all breakpoints. Unresolved breakpoint ids: {unresolved_breakpoints}",
)

def waitUntil(self, condition_callback):
for _ in range(20):
if condition_callback():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import os


@skip("Temporarily disable the breakpoint tests")
class TestDAP_setBreakpoints(lldbdap_testcase.DAPTestCaseBase):
def setUp(self):
lldbdap_testcase.DAPTestCaseBase.setUp(self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import lldbdap_testcase


@skip("Temporarily disable the breakpoint tests")
class TestDAP_setFunctionBreakpoints(lldbdap_testcase.DAPTestCaseBase):
@skipIfWindows
def test_set_and_clear(self):
Expand Down
6 changes: 5 additions & 1 deletion lldb/test/API/tools/lldb-dap/module/TestDAP_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ def run_test(self, symbol_basename, expect_debug_info_size):
program = self.getBuildArtifact(program_basename)
self.build_and_launch(program)
functions = ["foo"]
breakpoint_ids = self.set_function_breakpoints(functions)

# This breakpoint will be resolved only when the libfoo module is loaded
breakpoint_ids = self.set_function_breakpoints(
functions, wait_for_resolve=False
)
self.assertEqual(len(breakpoint_ids), len(functions), "expect one breakpoint")
self.continue_to_breakpoints(breakpoint_ids)
active_modules = self.dap_server.get_modules()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,18 @@ def test_terminated_event(self):
self.build_and_launch(program)
# Set breakpoints
functions = ["foo"]
breakpoint_ids = self.set_function_breakpoints(functions)

# This breakpoint will be resolved only when the libfoo module is loaded
breakpoint_ids = self.set_function_breakpoints(
functions, wait_for_resolve=False
)
self.assertEqual(len(breakpoint_ids), len(functions), "expect one breakpoint")
main_bp_line = line_number("main.cpp", "// main breakpoint 1")
breakpoint_ids.append(self.set_source_breakpoints("main.cpp", [main_bp_line]))
breakpoint_ids.append(
self.set_source_breakpoints(
"main.cpp", [main_bp_line], wait_for_resolve=False
)
)

self.continue_to_breakpoints(breakpoint_ids)
self.continue_to_exit()
Expand Down
Loading