Skip to content

Commit 13eca52

Browse files
authored
[lldb-dap] Re-land refactor of DebugCommunication. (#147787)
Originally commited in 362b9d7 and then reverted in cb63b75. This re-lands a subset of the changes to dap_server.py/DebugCommunication and addresses the python3.10 compatibility issue. This includes less type annotations since those were the reason for the failures on that specific version of python. I've done additional testing on python3.8, python3.10 and python3.13 to further validate these changes.
1 parent bce9b6d commit 13eca52

File tree

15 files changed

+579
-467
lines changed

15 files changed

+579
-467
lines changed

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

Lines changed: 457 additions & 345 deletions
Large diffs are not rendered by default.

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

Lines changed: 51 additions & 36 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
@@ -67,7 +67,10 @@ def set_source_breakpoints_assembly(
6767
self, source_reference, lines, data=None, wait_for_resolve=True
6868
):
6969
return self.set_source_breakpoints_from_source(
70-
Source(source_reference=source_reference), lines, data, wait_for_resolve
70+
Source.build(source_reference=source_reference),
71+
lines,
72+
data,
73+
wait_for_resolve,
7174
)
7275

7376
def set_source_breakpoints_from_source(
@@ -120,11 +123,19 @@ def wait_for_breakpoints_to_resolve(
120123
f"Expected to resolve all breakpoints. Unresolved breakpoint ids: {unresolved_breakpoints}",
121124
)
122125

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

130141
def assertCapabilityIsSet(self, key: str, msg: Optional[str] = None) -> None:
@@ -137,13 +148,16 @@ def assertCapabilityIsNotSet(self, key: str, msg: Optional[str] = None) -> None:
137148
if key in self.dap_server.capabilities:
138149
self.assertEqual(self.dap_server.capabilities[key], False, msg)
139150

140-
def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
151+
def verify_breakpoint_hit(
152+
self, breakpoint_ids: List[Union[int, str]], timeout: float = DEFAULT_TIMEOUT
153+
):
141154
"""Wait for the process we are debugging to stop, and verify we hit
142155
any breakpoint location in the "breakpoint_ids" array.
143156
"breakpoint_ids" should be a list of breakpoint ID strings
144157
(["1", "2"]). The return value from self.set_source_breakpoints()
145158
or self.set_function_breakpoints() can be passed to this function"""
146159
stopped_events = self.dap_server.wait_for_stopped(timeout)
160+
normalized_bp_ids = [str(b) for b in breakpoint_ids]
147161
for stopped_event in stopped_events:
148162
if "body" in stopped_event:
149163
body = stopped_event["body"]
@@ -154,22 +168,16 @@ def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
154168
and body["reason"] != "instruction breakpoint"
155169
):
156170
continue
157-
if "description" not in body:
171+
if "hitBreakpointIds" not in body:
158172
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:
173+
hit_breakpoint_ids = body["hitBreakpointIds"]
174+
for bp in hit_breakpoint_ids:
175+
if str(bp) in normalized_bp_ids:
171176
return
172-
self.assertTrue(False, f"breakpoint not hit, stopped_events={stopped_events}")
177+
self.assertTrue(
178+
False,
179+
f"breakpoint not hit, wanted breakpoint_ids {breakpoint_ids} in stopped_events {stopped_events}",
180+
)
173181

174182
def verify_all_breakpoints_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
175183
"""Wait for the process we are debugging to stop, and verify we hit
@@ -213,7 +221,7 @@ def verify_stop_exception_info(self, expected_description, timeout=DEFAULT_TIMEO
213221
return True
214222
return False
215223

216-
def verify_commands(self, flavor, output, commands):
224+
def verify_commands(self, flavor: str, output: str, commands: list[str]):
217225
self.assertTrue(output and len(output) > 0, "expect console output")
218226
lines = output.splitlines()
219227
prefix = "(lldb) "
@@ -226,10 +234,11 @@ def verify_commands(self, flavor, output, commands):
226234
found = True
227235
break
228236
self.assertTrue(
229-
found, "verify '%s' found in console output for '%s'" % (cmd, flavor)
237+
found,
238+
f"Command '{flavor}' - '{cmd}' not found in output: {output}",
230239
)
231240

232-
def get_dict_value(self, d, key_path):
241+
def get_dict_value(self, d: dict, key_path: list[str]) -> Any:
233242
"""Verify each key in the key_path array is in contained in each
234243
dictionary within "d". Assert if any key isn't in the
235244
corresponding dictionary. This is handy for grabbing values from VS
@@ -298,28 +307,34 @@ def get_source_and_line(self, threadId=None, frameIndex=0):
298307
return (source["path"], stackFrame["line"])
299308
return ("", 0)
300309

301-
def get_stdout(self, timeout=0.0):
302-
return self.dap_server.get_output("stdout", timeout=timeout)
310+
def get_stdout(self):
311+
return self.dap_server.get_output("stdout")
303312

304-
def get_console(self, timeout=0.0):
305-
return self.dap_server.get_output("console", timeout=timeout)
313+
def get_console(self):
314+
return self.dap_server.get_output("console")
306315

307-
def get_important(self, timeout=0.0):
308-
return self.dap_server.get_output("important", timeout=timeout)
316+
def get_important(self):
317+
return self.dap_server.get_output("important")
309318

310-
def collect_stdout(self, timeout_secs, pattern=None):
319+
def collect_stdout(
320+
self, timeout: float = DEFAULT_TIMEOUT, pattern: Optional[str] = None
321+
) -> str:
311322
return self.dap_server.collect_output(
312-
"stdout", timeout_secs=timeout_secs, pattern=pattern
323+
"stdout", timeout=timeout, pattern=pattern
313324
)
314325

315-
def collect_console(self, timeout_secs, pattern=None):
326+
def collect_console(
327+
self, timeout: float = DEFAULT_TIMEOUT, pattern: Optional[str] = None
328+
) -> str:
316329
return self.dap_server.collect_output(
317-
"console", timeout_secs=timeout_secs, pattern=pattern
330+
"console", timeout=timeout, pattern=pattern
318331
)
319332

320-
def collect_important(self, timeout_secs, pattern=None):
333+
def collect_important(
334+
self, timeout: float = DEFAULT_TIMEOUT, pattern: Optional[str] = None
335+
) -> str:
321336
return self.dap_server.collect_output(
322-
"important", timeout_secs=timeout_secs, pattern=pattern
337+
"important", timeout=timeout, pattern=pattern
323338
)
324339

325340
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/breakpoint-assembly/TestDAP_breakpointAssembly.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Test lldb-dap setBreakpoints request in assembly source references.
33
"""
44

5-
65
from lldbsuite.test.decorators import *
76
from dap_server import Source
87
import lldbdap_testcase
@@ -52,7 +51,7 @@ def test_break_on_invalid_source_reference(self):
5251

5352
# Verify that setting a breakpoint on an invalid source reference fails
5453
response = self.dap_server.request_setBreakpoints(
55-
Source(source_reference=-1), [1]
54+
Source.build(source_reference=-1), [1]
5655
)
5756
self.assertIsNotNone(response)
5857
breakpoints = response["body"]["breakpoints"]
@@ -69,7 +68,7 @@ def test_break_on_invalid_source_reference(self):
6968

7069
# Verify that setting a breakpoint on a source reference that is not created fails
7170
response = self.dap_server.request_setBreakpoints(
72-
Source(source_reference=200), [1]
71+
Source.build(source_reference=200), [1]
7372
)
7473
self.assertIsNotNone(response)
7574
breakpoints = response["body"]["breakpoints"]
@@ -116,7 +115,7 @@ def test_persistent_assembly_breakpoint(self):
116115

117116
persistent_breakpoint_source = self.dap_server.resolved_breakpoints[
118117
persistent_breakpoint_ids[0]
119-
].source()
118+
]["source"]
120119
self.assertIn(
121120
"adapterData",
122121
persistent_breakpoint_source,
@@ -139,7 +138,7 @@ def test_persistent_assembly_breakpoint(self):
139138
self.dap_server.request_initialize()
140139
self.dap_server.request_launch(program)
141140
new_session_breakpoints_ids = self.set_source_breakpoints_from_source(
142-
Source(raw_dict=persistent_breakpoint_source),
141+
Source(persistent_breakpoint_source),
143142
[persistent_breakpoint_line],
144143
)
145144

lldb/test/API/tools/lldb-dap/breakpoint-events/TestDAP_breakpointEvents.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def test_breakpoint_events(self):
5858
# Set breakpoints and verify that they got set correctly
5959
dap_breakpoint_ids = []
6060
response = self.dap_server.request_setBreakpoints(
61-
Source(main_source_path), [main_bp_line]
61+
Source.build(path=main_source_path), [main_bp_line]
6262
)
6363
self.assertTrue(response["success"])
6464
breakpoints = response["body"]["breakpoints"]
@@ -70,7 +70,7 @@ def test_breakpoint_events(self):
7070
)
7171

7272
response = self.dap_server.request_setBreakpoints(
73-
Source(foo_source_path), [foo_bp1_line]
73+
Source.build(path=foo_source_path), [foo_bp1_line]
7474
)
7575
self.assertTrue(response["success"])
7676
breakpoints = response["body"]["breakpoints"]

lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Test lldb-dap setBreakpoints request
33
"""
44

5-
65
from dap_server import Source
76
import shutil
87
from lldbsuite.test.decorators import *
@@ -58,7 +57,7 @@ def test_source_map(self):
5857

5958
# breakpoint in main.cpp
6059
response = self.dap_server.request_setBreakpoints(
61-
Source(new_main_path), [main_line]
60+
Source.build(path=new_main_path), [main_line]
6261
)
6362
breakpoints = response["body"]["breakpoints"]
6463
self.assertEqual(len(breakpoints), 1)
@@ -70,7 +69,7 @@ def test_source_map(self):
7069

7170
# 2nd breakpoint, which is from a dynamically loaded library
7271
response = self.dap_server.request_setBreakpoints(
73-
Source(new_other_path), [other_line]
72+
Source.build(path=new_other_path), [other_line]
7473
)
7574
breakpoints = response["body"]["breakpoints"]
7675
breakpoint = breakpoints[0]
@@ -85,7 +84,7 @@ def test_source_map(self):
8584

8685
# 2nd breakpoint again, which should be valid at this point
8786
response = self.dap_server.request_setBreakpoints(
88-
Source(new_other_path), [other_line]
87+
Source.build(path=new_other_path), [other_line]
8988
)
9089
breakpoints = response["body"]["breakpoints"]
9190
breakpoint = breakpoints[0]
@@ -129,7 +128,9 @@ def test_set_and_clear(self):
129128
self.build_and_launch(program)
130129

131130
# Set 3 breakpoints and verify that they got set correctly
132-
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
131+
response = self.dap_server.request_setBreakpoints(
132+
Source.build(path=self.main_path), lines
133+
)
133134
line_to_id = {}
134135
breakpoints = response["body"]["breakpoints"]
135136
self.assertEqual(
@@ -154,7 +155,9 @@ def test_set_and_clear(self):
154155
lines.remove(second_line)
155156
# Set 2 breakpoints and verify that the previous breakpoints that were
156157
# set above are still set.
157-
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
158+
response = self.dap_server.request_setBreakpoints(
159+
Source.build(path=self.main_path), lines
160+
)
158161
breakpoints = response["body"]["breakpoints"]
159162
self.assertEqual(
160163
len(breakpoints),
@@ -199,7 +202,9 @@ def test_set_and_clear(self):
199202
# Now clear all breakpoints for the source file by passing down an
200203
# empty lines array
201204
lines = []
202-
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
205+
response = self.dap_server.request_setBreakpoints(
206+
Source.build(path=self.main_path), lines
207+
)
203208
breakpoints = response["body"]["breakpoints"]
204209
self.assertEqual(
205210
len(breakpoints),
@@ -219,7 +224,9 @@ def test_set_and_clear(self):
219224
# Now set a breakpoint again in the same source file and verify it
220225
# was added.
221226
lines = [second_line]
222-
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
227+
response = self.dap_server.request_setBreakpoints(
228+
Source.build(path=self.main_path), lines
229+
)
223230
if response:
224231
breakpoints = response["body"]["breakpoints"]
225232
self.assertEqual(
@@ -270,7 +277,9 @@ def test_clear_breakpoints_unset_breakpoints(self):
270277
self.build_and_launch(program)
271278

272279
# Set one breakpoint and verify that it got set correctly.
273-
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
280+
response = self.dap_server.request_setBreakpoints(
281+
Source.build(path=self.main_path), lines
282+
)
274283
line_to_id = {}
275284
breakpoints = response["body"]["breakpoints"]
276285
self.assertEqual(
@@ -286,7 +295,9 @@ def test_clear_breakpoints_unset_breakpoints(self):
286295
# Now clear all breakpoints for the source file by not setting the
287296
# lines array.
288297
lines = None
289-
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
298+
response = self.dap_server.request_setBreakpoints(
299+
Source.build(path=self.main_path), lines
300+
)
290301
breakpoints = response["body"]["breakpoints"]
291302
self.assertEqual(len(breakpoints), 0, "expect no source breakpoints")
292303

@@ -362,7 +373,7 @@ def test_column_breakpoints(self):
362373
# Set two breakpoints on the loop line at different columns.
363374
columns = [13, 39]
364375
response = self.dap_server.request_setBreakpoints(
365-
Source(self.main_path),
376+
Source.build(path=self.main_path),
366377
[loop_line, loop_line],
367378
list({"column": c} for c in columns),
368379
)

0 commit comments

Comments
 (0)