Skip to content

Commit e692c5f

Browse files
committed
[lldb-dap] Reland refactor of DebugCommunication.
Originally commited in 362b9d7 and then reverted in cb63b75. This reland a subset of the changes to dap_server.py/DebugCommunication and addresses the python3.10 comptability issue. This includes less type annotations since those were the reason for the failures on that specific version of python. I've done additionall testing on python3.8, python3.10 and python3.13 to further validate these changes.
1 parent a5f1ddd commit e692c5f

File tree

9 files changed

+483
-375
lines changed

9 files changed

+483
-375
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 402 additions & 281 deletions
Large diffs are not rendered by default.

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
import time
3-
from typing import Optional
3+
from typing import Optional, Callable, Any, List, Union
44
import uuid
55

66
import dap_server
@@ -120,11 +120,19 @@ def wait_for_breakpoints_to_resolve(
120120
f"Expected to resolve all breakpoints. Unresolved breakpoint ids: {unresolved_breakpoints}",
121121
)
122122

123-
def waitUntil(self, condition_callback):
124-
for _ in range(20):
125-
if condition_callback():
123+
def wait_until(
124+
self,
125+
predicate: Callable[[], bool],
126+
delay: float = 0.5,
127+
timeout: float = DEFAULT_TIMEOUT,
128+
) -> bool:
129+
"""Repeatedly run the predicate until either the predicate returns True
130+
or a timeout has occurred."""
131+
deadline = time.monotonic() + timeout
132+
while deadline > time.monotonic():
133+
if predicate():
126134
return True
127-
time.sleep(0.5)
135+
time.sleep(delay)
128136
return False
129137

130138
def assertCapabilityIsSet(self, key: str, msg: Optional[str] = None) -> None:
@@ -137,13 +145,16 @@ def assertCapabilityIsNotSet(self, key: str, msg: Optional[str] = None) -> None:
137145
if key in self.dap_server.capabilities:
138146
self.assertEqual(self.dap_server.capabilities[key], False, msg)
139147

140-
def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
148+
def verify_breakpoint_hit(
149+
self, breakpoint_ids: List[Union[int, str]], timeout: float = DEFAULT_TIMEOUT
150+
):
141151
"""Wait for the process we are debugging to stop, and verify we hit
142152
any breakpoint location in the "breakpoint_ids" array.
143153
"breakpoint_ids" should be a list of breakpoint ID strings
144154
(["1", "2"]). The return value from self.set_source_breakpoints()
145155
or self.set_function_breakpoints() can be passed to this function"""
146156
stopped_events = self.dap_server.wait_for_stopped(timeout)
157+
normalized_bp_ids = [str(b) for b in breakpoint_ids]
147158
for stopped_event in stopped_events:
148159
if "body" in stopped_event:
149160
body = stopped_event["body"]
@@ -154,22 +165,16 @@ def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
154165
and body["reason"] != "instruction breakpoint"
155166
):
156167
continue
157-
if "description" not in body:
168+
if "hitBreakpointIds" not in body:
158169
continue
159-
# Descriptions for breakpoints will be in the form
160-
# "breakpoint 1.1", so look for any description that matches
161-
# ("breakpoint 1.") in the description field as verification
162-
# that one of the breakpoint locations was hit. DAP doesn't
163-
# allow breakpoints to have multiple locations, but LLDB does.
164-
# So when looking at the description we just want to make sure
165-
# the right breakpoint matches and not worry about the actual
166-
# location.
167-
description = body["description"]
168-
for breakpoint_id in breakpoint_ids:
169-
match_desc = f"breakpoint {breakpoint_id}."
170-
if match_desc in description:
170+
hit_breakpoint_ids = body["hitBreakpointIds"]
171+
for bp in hit_breakpoint_ids:
172+
if str(bp) in normalized_bp_ids:
171173
return
172-
self.assertTrue(False, f"breakpoint not hit, stopped_events={stopped_events}")
174+
self.assertTrue(
175+
False,
176+
f"breakpoint not hit, wanted breakpoint_ids {breakpoint_ids} in stopped_events {stopped_events}",
177+
)
173178

174179
def verify_all_breakpoints_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
175180
"""Wait for the process we are debugging to stop, and verify we hit
@@ -213,7 +218,7 @@ def verify_stop_exception_info(self, expected_description, timeout=DEFAULT_TIMEO
213218
return True
214219
return False
215220

216-
def verify_commands(self, flavor, output, commands):
221+
def verify_commands(self, flavor: str, output: str, commands: list[str]):
217222
self.assertTrue(output and len(output) > 0, "expect console output")
218223
lines = output.splitlines()
219224
prefix = "(lldb) "
@@ -226,10 +231,11 @@ def verify_commands(self, flavor, output, commands):
226231
found = True
227232
break
228233
self.assertTrue(
229-
found, "verify '%s' found in console output for '%s'" % (cmd, flavor)
234+
found,
235+
f"Command '{flavor}' - '{cmd}' not found in output: {output}",
230236
)
231237

232-
def get_dict_value(self, d, key_path):
238+
def get_dict_value(self, d: dict, key_path: list[str]) -> Any:
233239
"""Verify each key in the key_path array is in contained in each
234240
dictionary within "d". Assert if any key isn't in the
235241
corresponding dictionary. This is handy for grabbing values from VS
@@ -298,28 +304,34 @@ def get_source_and_line(self, threadId=None, frameIndex=0):
298304
return (source["path"], stackFrame["line"])
299305
return ("", 0)
300306

301-
def get_stdout(self, timeout=0.0):
302-
return self.dap_server.get_output("stdout", timeout=timeout)
307+
def get_stdout(self):
308+
return self.dap_server.get_output("stdout")
303309

304-
def get_console(self, timeout=0.0):
305-
return self.dap_server.get_output("console", timeout=timeout)
310+
def get_console(self):
311+
return self.dap_server.get_output("console")
306312

307-
def get_important(self, timeout=0.0):
308-
return self.dap_server.get_output("important", timeout=timeout)
313+
def get_important(self):
314+
return self.dap_server.get_output("important")
309315

310-
def collect_stdout(self, timeout_secs, pattern=None):
316+
def collect_stdout(
317+
self, timeout: float = DEFAULT_TIMEOUT, pattern: Optional[str] = None
318+
) -> str:
311319
return self.dap_server.collect_output(
312-
"stdout", timeout_secs=timeout_secs, pattern=pattern
320+
"stdout", timeout=timeout, pattern=pattern
313321
)
314322

315-
def collect_console(self, timeout_secs, pattern=None):
323+
def collect_console(
324+
self, timeout: float = DEFAULT_TIMEOUT, pattern: Optional[str] = None
325+
) -> str:
316326
return self.dap_server.collect_output(
317-
"console", timeout_secs=timeout_secs, pattern=pattern
327+
"console", timeout=timeout, pattern=pattern
318328
)
319329

320-
def collect_important(self, timeout_secs, pattern=None):
330+
def collect_important(
331+
self, timeout: float = DEFAULT_TIMEOUT, pattern: Optional[str] = None
332+
) -> str:
321333
return self.dap_server.collect_output(
322-
"important", timeout_secs=timeout_secs, pattern=pattern
334+
"important", timeout=timeout, pattern=pattern
323335
)
324336

325337
def get_local_as_int(self, name, threadId=None):

lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def test_commands(self):
153153
breakpoint_ids = self.set_function_breakpoints(functions)
154154
self.assertEqual(len(breakpoint_ids), len(functions), "expect one breakpoint")
155155
self.continue_to_breakpoints(breakpoint_ids)
156-
output = self.collect_console(timeout_secs=10, pattern=stopCommands[-1])
156+
output = self.collect_console(timeout=10, pattern=stopCommands[-1])
157157
self.verify_commands("stopCommands", output, stopCommands)
158158

159159
# Continue after launch and hit the "pause()" call and stop the target.
@@ -163,7 +163,7 @@ def test_commands(self):
163163
time.sleep(0.5)
164164
self.dap_server.request_pause()
165165
self.dap_server.wait_for_stopped()
166-
output = self.collect_console(timeout_secs=10, pattern=stopCommands[-1])
166+
output = self.collect_console(timeout=10, pattern=stopCommands[-1])
167167
self.verify_commands("stopCommands", output, stopCommands)
168168

169169
# Continue until the program exits
@@ -172,7 +172,7 @@ def test_commands(self):
172172
# "exitCommands" that were run after the second breakpoint was hit
173173
# and the "terminateCommands" due to the debugging session ending
174174
output = self.collect_console(
175-
timeout_secs=10.0,
175+
timeout=10.0,
176176
pattern=terminateCommands[0],
177177
)
178178
self.verify_commands("exitCommands", output, exitCommands)
@@ -223,7 +223,7 @@ def test_terminate_commands(self):
223223
# "terminateCommands"
224224
self.dap_server.request_disconnect(terminateDebuggee=True)
225225
output = self.collect_console(
226-
timeout_secs=1.0,
226+
timeout=1.0,
227227
pattern=terminateCommands[0],
228228
)
229229
self.verify_commands("terminateCommands", output, terminateCommands)

lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,14 @@
1010

1111

1212
class TestDAP_cancel(lldbdap_testcase.DAPTestCaseBase):
13-
def send_async_req(self, command: str, arguments={}) -> int:
14-
seq = self.dap_server.sequence
15-
self.dap_server.send_packet(
13+
def send_async_req(self, command: str, arguments: dict = {}) -> int:
14+
return self.dap_server.send_packet(
1615
{
1716
"type": "request",
1817
"command": command,
1918
"arguments": arguments,
2019
}
2120
)
22-
return seq
2321

2422
def async_blocking_request(self, duration: float) -> int:
2523
"""
@@ -54,18 +52,18 @@ def test_pending_request(self):
5452
pending_seq = self.async_blocking_request(duration=self.DEFAULT_TIMEOUT / 2)
5553
cancel_seq = self.async_cancel(requestId=pending_seq)
5654

57-
blocking_resp = self.dap_server.recv_packet(filter_type=["response"])
55+
blocking_resp = self.dap_server.receive_response(blocking_seq)
5856
self.assertEqual(blocking_resp["request_seq"], blocking_seq)
5957
self.assertEqual(blocking_resp["command"], "evaluate")
6058
self.assertEqual(blocking_resp["success"], True)
6159

62-
pending_resp = self.dap_server.recv_packet(filter_type=["response"])
60+
pending_resp = self.dap_server.receive_response(pending_seq)
6361
self.assertEqual(pending_resp["request_seq"], pending_seq)
6462
self.assertEqual(pending_resp["command"], "evaluate")
6563
self.assertEqual(pending_resp["success"], False)
6664
self.assertEqual(pending_resp["message"], "cancelled")
6765

68-
cancel_resp = self.dap_server.recv_packet(filter_type=["response"])
66+
cancel_resp = self.dap_server.receive_response(cancel_seq)
6967
self.assertEqual(cancel_resp["request_seq"], cancel_seq)
7068
self.assertEqual(cancel_resp["command"], "cancel")
7169
self.assertEqual(cancel_resp["success"], True)
@@ -80,19 +78,16 @@ def test_inflight_request(self):
8078

8179
blocking_seq = self.async_blocking_request(duration=self.DEFAULT_TIMEOUT / 2)
8280
# Wait for the sleep to start to cancel the inflight request.
83-
self.collect_console(
84-
timeout_secs=self.DEFAULT_TIMEOUT,
85-
pattern="starting sleep",
86-
)
81+
self.collect_console(pattern="starting sleep")
8782
cancel_seq = self.async_cancel(requestId=blocking_seq)
8883

89-
blocking_resp = self.dap_server.recv_packet(filter_type=["response"])
84+
blocking_resp = self.dap_server.receive_response(blocking_seq)
9085
self.assertEqual(blocking_resp["request_seq"], blocking_seq)
9186
self.assertEqual(blocking_resp["command"], "evaluate")
9287
self.assertEqual(blocking_resp["success"], False)
9388
self.assertEqual(blocking_resp["message"], "cancelled")
9489

95-
cancel_resp = self.dap_server.recv_packet(filter_type=["response"])
90+
cancel_resp = self.dap_server.receive_response(cancel_seq)
9691
self.assertEqual(cancel_resp["request_seq"], cancel_seq)
9792
self.assertEqual(cancel_resp["command"], "cancel")
9893
self.assertEqual(cancel_resp["success"], True)

lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import os
1+
"""
2+
Test lldb-dap command hooks
3+
"""
24

3-
import dap_server
45
import lldbdap_testcase
5-
from lldbsuite.test import lldbtest, lldbutil
66
from lldbsuite.test.decorators import *
77

88

@@ -23,7 +23,7 @@ def test_command_directive_quiet_on_success(self):
2323
exitCommands=["?" + command_quiet, command_not_quiet],
2424
)
2525
full_output = self.collect_console(
26-
timeout_secs=1.0,
26+
timeout=1.0,
2727
pattern=command_not_quiet,
2828
)
2929
self.assertNotIn(command_quiet, full_output)
@@ -51,7 +51,7 @@ def do_test_abort_on_error(
5151
expectFailure=True,
5252
)
5353
full_output = self.collect_console(
54-
timeout_secs=1.0,
54+
timeout=1.0,
5555
pattern=command_abort_on_error,
5656
)
5757
self.assertNotIn(command_quiet, full_output)
@@ -81,9 +81,6 @@ def test_command_directive_abort_on_error_attach_commands(self):
8181
expectFailure=True,
8282
)
8383
self.assertFalse(resp["success"], "expected 'attach' failure")
84-
full_output = self.collect_console(
85-
timeout_secs=1.0,
86-
pattern=command_abort_on_error,
87-
)
84+
full_output = self.collect_console(pattern=command_abort_on_error)
8885
self.assertNotIn(command_quiet, full_output)
8986
self.assertIn(command_abort_on_error, full_output)

lldb/test/API/tools/lldb-dap/console/TestDAP_console.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,7 @@ def test_exit_status_message_sigterm(self):
139139
process.wait()
140140

141141
# Get the console output
142-
console_output = self.collect_console(
143-
timeout_secs=10.0, pattern="exited with status"
144-
)
142+
console_output = self.collect_console(pattern="exited with status")
145143

146144
# Verify the exit status message is printed.
147145
self.assertRegex(
@@ -156,9 +154,7 @@ def test_exit_status_message_ok(self):
156154
self.continue_to_exit()
157155

158156
# Get the console output
159-
console_output = self.collect_console(
160-
timeout_secs=10.0, pattern="exited with status"
161-
)
157+
console_output = self.collect_console(pattern="exited with status")
162158

163159
# Verify the exit status message is printed.
164160
self.assertIn(
@@ -177,9 +173,7 @@ def test_diagnositcs(self):
177173
f"target create --core {core}", context="repl"
178174
)
179175

180-
diagnostics = self.collect_important(
181-
timeout_secs=self.DEFAULT_TIMEOUT, pattern="minidump file"
182-
)
176+
diagnostics = self.collect_important(pattern="minidump file")
183177

184178
self.assertIn(
185179
"warning: unable to retrieve process ID from minidump file",

0 commit comments

Comments
 (0)