Skip to content

Commit e32962e

Browse files
committed
[lldb-dap] Unify the timeouts for the DAP tests
Various DAP tests are specifying their own timeouts, with values ranging from "1" to "20". Most of them seem arbitrary, but some come with a comment. The performance characters of running these tests in CI are unpredictable (they generally run much slower than developers expect) and really not something we can make assumptions about. I suspect these timeouts are a contributing factor to the flakiness of the DAP tests. This PR unifies the timeouts around a central value in the DAP server. Fixes #162523
1 parent 3e251e7 commit e32962e

File tree

9 files changed

+67
-95
lines changed

9 files changed

+67
-95
lines changed

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

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
Literal,
2828
)
2929

30+
# set timeout based on whether ASAN was enabled or not. Increase
31+
# timeout by a factor of 10 if ASAN is enabled.
32+
DEFAULT_TIMEOUT = 10 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)
33+
3034
## DAP type references
3135

3236

@@ -282,26 +286,24 @@ def get_output(self, category: str, clear=True) -> str:
282286
def collect_output(
283287
self,
284288
category: str,
285-
timeout: float,
286289
pattern: Optional[str] = None,
287290
clear=True,
288291
) -> str:
289292
"""Collect output from 'output' events.
290293
Args:
291294
category: The category to collect.
292-
timeout: The max duration for collecting output.
293295
pattern:
294296
Optional, if set, return once this pattern is detected in the
295297
collected output.
296298
Returns:
297299
The collected output.
298300
"""
299-
deadline = time.monotonic() + timeout
301+
deadline = time.monotonic() + DEFAULT_TIMEOUT
300302
output = self.get_output(category, clear)
301303
while deadline >= time.monotonic() and (
302304
pattern is None or pattern not in output
303305
):
304-
event = self.wait_for_event(["output"], timeout=deadline - time.monotonic())
306+
event = self.wait_for_event(["output"])
305307
if not event: # Timeout or EOF
306308
break
307309
output += self.get_output(category, clear=clear)
@@ -555,25 +557,20 @@ def predicate(p: ProtocolMessage):
555557

556558
return cast(Optional[Response], self._recv_packet(predicate=predicate))
557559

558-
def wait_for_event(
559-
self, filter: List[str] = [], timeout: Optional[float] = None
560-
) -> Optional[Event]:
560+
def wait_for_event(self, filter: List[str] = []) -> Optional[Event]:
561561
"""Wait for the first event that matches the filter."""
562562

563563
def predicate(p: ProtocolMessage):
564564
return p["type"] == "event" and p["event"] in filter
565565

566566
return cast(
567-
Optional[Event], self._recv_packet(predicate=predicate, timeout=timeout)
567+
Optional[Event],
568+
self._recv_packet(predicate=predicate, timeout=DEFAULT_TIMEOUT),
568569
)
569570

570-
def wait_for_stopped(
571-
self, timeout: Optional[float] = None
572-
) -> Optional[List[Event]]:
571+
def wait_for_stopped(self) -> Optional[List[Event]]:
573572
stopped_events = []
574-
stopped_event = self.wait_for_event(
575-
filter=["stopped", "exited"], timeout=timeout
576-
)
573+
stopped_event = self.wait_for_event(filter=["stopped", "exited"])
577574
while stopped_event:
578575
stopped_events.append(stopped_event)
579576
# If we exited, then we are done
@@ -582,26 +579,28 @@ def wait_for_stopped(
582579
# Otherwise we stopped and there might be one or more 'stopped'
583580
# events for each thread that stopped with a reason, so keep
584581
# checking for more 'stopped' events and return all of them
585-
stopped_event = self.wait_for_event(
586-
filter=["stopped", "exited"], timeout=0.25
582+
# Use a shorter timeout for additional stopped events
583+
def predicate(p: ProtocolMessage):
584+
return p["type"] == "event" and p["event"] in ["stopped", "exited"]
585+
586+
stopped_event = cast(
587+
Optional[Event], self._recv_packet(predicate=predicate, timeout=0.25)
587588
)
588589
return stopped_events
589590

590-
def wait_for_breakpoint_events(self, timeout: Optional[float] = None):
591+
def wait_for_breakpoint_events(self):
591592
breakpoint_events: list[Event] = []
592593
while True:
593-
event = self.wait_for_event(["breakpoint"], timeout=timeout)
594+
event = self.wait_for_event(["breakpoint"])
594595
if not event:
595596
break
596597
breakpoint_events.append(event)
597598
return breakpoint_events
598599

599-
def wait_for_breakpoints_to_be_verified(
600-
self, breakpoint_ids: list[str], timeout: Optional[float] = None
601-
):
600+
def wait_for_breakpoints_to_be_verified(self, breakpoint_ids: list[str]):
602601
"""Wait for all breakpoints to be verified. Return all unverified breakpoints."""
603602
while any(id not in self.resolved_breakpoints for id in breakpoint_ids):
604-
breakpoint_event = self.wait_for_event(["breakpoint"], timeout=timeout)
603+
breakpoint_event = self.wait_for_event(["breakpoint"])
605604
if breakpoint_event is None:
606605
break
607606

@@ -614,14 +613,14 @@ def wait_for_breakpoints_to_be_verified(
614613
)
615614
]
616615

617-
def wait_for_exited(self, timeout: Optional[float] = None):
618-
event_dict = self.wait_for_event(["exited"], timeout=timeout)
616+
def wait_for_exited(self):
617+
event_dict = self.wait_for_event(["exited"])
619618
if event_dict is None:
620619
raise ValueError("didn't get exited event")
621620
return event_dict
622621

623-
def wait_for_terminated(self, timeout: Optional[float] = None):
624-
event_dict = self.wait_for_event(["terminated"], timeout)
622+
def wait_for_terminated(self):
623+
event_dict = self.wait_for_event(["terminated"])
625624
if event_dict is None:
626625
raise ValueError("didn't get terminated event")
627626
return event_dict

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

Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
class DAPTestCaseBase(TestBase):
1919
# set timeout based on whether ASAN was enabled or not. Increase
2020
# timeout by a factor of 10 if ASAN is enabled.
21-
DEFAULT_TIMEOUT = 10 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)
21+
DEFAULT_TIMEOUT = dap_server.DEFAULT_TIMEOUT
2222
NO_DEBUG_INFO_TESTCASE = True
2323

2424
def create_debug_adapter(
@@ -118,11 +118,9 @@ def set_function_breakpoints(
118118
self.wait_for_breakpoints_to_resolve(breakpoint_ids)
119119
return breakpoint_ids
120120

121-
def wait_for_breakpoints_to_resolve(
122-
self, breakpoint_ids: list[str], timeout: Optional[float] = DEFAULT_TIMEOUT
123-
):
121+
def wait_for_breakpoints_to_resolve(self, breakpoint_ids: list[str]):
124122
unresolved_breakpoints = self.dap_server.wait_for_breakpoints_to_be_verified(
125-
breakpoint_ids, timeout
123+
breakpoint_ids
126124
)
127125
self.assertEqual(
128126
len(unresolved_breakpoints),
@@ -134,11 +132,10 @@ def wait_until(
134132
self,
135133
predicate: Callable[[], bool],
136134
delay: float = 0.5,
137-
timeout: float = DEFAULT_TIMEOUT,
138135
) -> bool:
139136
"""Repeatedly run the predicate until either the predicate returns True
140137
or a timeout has occurred."""
141-
deadline = time.monotonic() + timeout
138+
deadline = time.monotonic() + self.DEFAULT_TIMEOUT
142139
while deadline > time.monotonic():
143140
if predicate():
144141
return True
@@ -155,15 +152,13 @@ def assertCapabilityIsNotSet(self, key: str, msg: Optional[str] = None) -> None:
155152
if key in self.dap_server.capabilities:
156153
self.assertEqual(self.dap_server.capabilities[key], False, msg)
157154

158-
def verify_breakpoint_hit(
159-
self, breakpoint_ids: List[Union[int, str]], timeout: float = DEFAULT_TIMEOUT
160-
):
155+
def verify_breakpoint_hit(self, breakpoint_ids: List[Union[int, str]]):
161156
"""Wait for the process we are debugging to stop, and verify we hit
162157
any breakpoint location in the "breakpoint_ids" array.
163158
"breakpoint_ids" should be a list of breakpoint ID strings
164159
(["1", "2"]). The return value from self.set_source_breakpoints()
165160
or self.set_function_breakpoints() can be passed to this function"""
166-
stopped_events = self.dap_server.wait_for_stopped(timeout)
161+
stopped_events = self.dap_server.wait_for_stopped()
167162
normalized_bp_ids = [str(b) for b in breakpoint_ids]
168163
for stopped_event in stopped_events:
169164
if "body" in stopped_event:
@@ -186,11 +181,11 @@ def verify_breakpoint_hit(
186181
f"breakpoint not hit, wanted breakpoint_ids {breakpoint_ids} in stopped_events {stopped_events}",
187182
)
188183

189-
def verify_all_breakpoints_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
184+
def verify_all_breakpoints_hit(self, breakpoint_ids):
190185
"""Wait for the process we are debugging to stop, and verify we hit
191186
all of the breakpoint locations in the "breakpoint_ids" array.
192187
"breakpoint_ids" should be a list of int breakpoint IDs ([1, 2])."""
193-
stopped_events = self.dap_server.wait_for_stopped(timeout)
188+
stopped_events = self.dap_server.wait_for_stopped()
194189
for stopped_event in stopped_events:
195190
if "body" in stopped_event:
196191
body = stopped_event["body"]
@@ -208,12 +203,12 @@ def verify_all_breakpoints_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
208203
return
209204
self.assertTrue(False, f"breakpoints not hit, stopped_events={stopped_events}")
210205

211-
def verify_stop_exception_info(self, expected_description, timeout=DEFAULT_TIMEOUT):
206+
def verify_stop_exception_info(self, expected_description):
212207
"""Wait for the process we are debugging to stop, and verify the stop
213208
reason is 'exception' and that the description matches
214209
'expected_description'
215210
"""
216-
stopped_events = self.dap_server.wait_for_stopped(timeout)
211+
stopped_events = self.dap_server.wait_for_stopped()
217212
for stopped_event in stopped_events:
218213
if "body" in stopped_event:
219214
body = stopped_event["body"]
@@ -338,26 +333,14 @@ def get_console(self):
338333
def get_important(self):
339334
return self.dap_server.get_output("important")
340335

341-
def collect_stdout(
342-
self, timeout: float = DEFAULT_TIMEOUT, pattern: Optional[str] = None
343-
) -> str:
344-
return self.dap_server.collect_output(
345-
"stdout", timeout=timeout, pattern=pattern
346-
)
336+
def collect_stdout(self, pattern: Optional[str] = None) -> str:
337+
return self.dap_server.collect_output("stdout", pattern=pattern)
347338

348-
def collect_console(
349-
self, timeout: float = DEFAULT_TIMEOUT, pattern: Optional[str] = None
350-
) -> str:
351-
return self.dap_server.collect_output(
352-
"console", timeout=timeout, pattern=pattern
353-
)
339+
def collect_console(self, pattern: Optional[str] = None) -> str:
340+
return self.dap_server.collect_output("console", pattern=pattern)
354341

355-
def collect_important(
356-
self, timeout: float = DEFAULT_TIMEOUT, pattern: Optional[str] = None
357-
) -> str:
358-
return self.dap_server.collect_output(
359-
"important", timeout=timeout, pattern=pattern
360-
)
342+
def collect_important(self, pattern: Optional[str] = None) -> str:
343+
return self.dap_server.collect_output("important", pattern=pattern)
361344

362345
def get_local_as_int(self, name, threadId=None):
363346
value = self.dap_server.get_local_variable_value(name, threadId=threadId)
@@ -393,22 +376,20 @@ def stepIn(
393376
targetId=None,
394377
waitForStop=True,
395378
granularity="statement",
396-
timeout=DEFAULT_TIMEOUT,
397379
):
398380
response = self.dap_server.request_stepIn(
399381
threadId=threadId, targetId=targetId, granularity=granularity
400382
)
401383
self.assertTrue(response["success"])
402384
if waitForStop:
403-
return self.dap_server.wait_for_stopped(timeout)
385+
return self.dap_server.wait_for_stopped()
404386
return None
405387

406388
def stepOver(
407389
self,
408390
threadId=None,
409391
waitForStop=True,
410392
granularity="statement",
411-
timeout=DEFAULT_TIMEOUT,
412393
):
413394
response = self.dap_server.request_next(
414395
threadId=threadId, granularity=granularity
@@ -417,40 +398,40 @@ def stepOver(
417398
response["success"], f"next request failed: response {response}"
418399
)
419400
if waitForStop:
420-
return self.dap_server.wait_for_stopped(timeout)
401+
return self.dap_server.wait_for_stopped()
421402
return None
422403

423-
def stepOut(self, threadId=None, waitForStop=True, timeout=DEFAULT_TIMEOUT):
404+
def stepOut(self, threadId=None, waitForStop=True):
424405
self.dap_server.request_stepOut(threadId=threadId)
425406
if waitForStop:
426-
return self.dap_server.wait_for_stopped(timeout)
407+
return self.dap_server.wait_for_stopped()
427408
return None
428409

429410
def do_continue(self): # `continue` is a keyword.
430411
resp = self.dap_server.request_continue()
431412
self.assertTrue(resp["success"], f"continue request failed: {resp}")
432413

433-
def continue_to_next_stop(self, timeout=DEFAULT_TIMEOUT):
414+
def continue_to_next_stop(self):
434415
self.do_continue()
435-
return self.dap_server.wait_for_stopped(timeout)
416+
return self.dap_server.wait_for_stopped()
436417

437-
def continue_to_breakpoint(self, breakpoint_id: str, timeout=DEFAULT_TIMEOUT):
438-
self.continue_to_breakpoints((breakpoint_id), timeout)
418+
def continue_to_breakpoint(self, breakpoint_id: str):
419+
self.continue_to_breakpoints((breakpoint_id))
439420

440-
def continue_to_breakpoints(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
421+
def continue_to_breakpoints(self, breakpoint_ids):
441422
self.do_continue()
442-
self.verify_breakpoint_hit(breakpoint_ids, timeout)
423+
self.verify_breakpoint_hit(breakpoint_ids)
443424

444-
def continue_to_exception_breakpoint(self, filter_label, timeout=DEFAULT_TIMEOUT):
425+
def continue_to_exception_breakpoint(self, filter_label):
445426
self.do_continue()
446427
self.assertTrue(
447-
self.verify_stop_exception_info(filter_label, timeout),
428+
self.verify_stop_exception_info(filter_label),
448429
'verify we got "%s"' % (filter_label),
449430
)
450431

451-
def continue_to_exit(self, exitCode=0, timeout=DEFAULT_TIMEOUT):
432+
def continue_to_exit(self, exitCode=0):
452433
self.do_continue()
453-
stopped_events = self.dap_server.wait_for_stopped(timeout)
434+
stopped_events = self.dap_server.wait_for_stopped()
454435
self.assertEqual(
455436
len(stopped_events), 1, "stopped_events = {}".format(stopped_events)
456437
)

lldb/test/API/tools/lldb-dap/attach-commands/TestDAP_attachCommands.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def test_commands(self):
7171
breakpoint_ids = self.set_function_breakpoints(functions)
7272
self.assertEqual(len(breakpoint_ids), len(functions), "expect one breakpoint")
7373
self.continue_to_breakpoints(breakpoint_ids)
74-
output = self.collect_console(timeout=10, pattern=stopCommands[-1])
74+
output = self.collect_console(pattern=stopCommands[-1])
7575
self.verify_commands("stopCommands", output, stopCommands)
7676

7777
# Continue after launch and hit the "pause()" call and stop the target.
@@ -81,7 +81,7 @@ def test_commands(self):
8181
time.sleep(0.5)
8282
self.dap_server.request_pause()
8383
self.dap_server.wait_for_stopped()
84-
output = self.collect_console(timeout=10, pattern=stopCommands[-1])
84+
output = self.collect_console(pattern=stopCommands[-1])
8585
self.verify_commands("stopCommands", output, stopCommands)
8686

8787
# Continue until the program exits
@@ -90,7 +90,6 @@ def test_commands(self):
9090
# "exitCommands" that were run after the second breakpoint was hit
9191
# and the "terminateCommands" due to the debugging session ending
9292
output = self.collect_console(
93-
timeout=10.0,
9493
pattern=terminateCommands[0],
9594
)
9695
self.verify_commands("exitCommands", output, exitCommands)
@@ -141,7 +140,6 @@ def test_terminate_commands(self):
141140
# "terminateCommands"
142141
self.dap_server.request_disconnect(terminateDebuggee=True)
143142
output = self.collect_console(
144-
timeout=1.0,
145143
pattern=terminateCommands[0],
146144
)
147145
self.verify_commands("terminateCommands", output, terminateCommands)

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
@@ -82,14 +82,14 @@ def test_breakpoint_events(self):
8282
)
8383

8484
# Flush the breakpoint events.
85-
self.dap_server.wait_for_breakpoint_events(timeout=5)
85+
self.dap_server.wait_for_breakpoint_events()
8686

8787
# Continue to the breakpoint
8888
self.continue_to_breakpoints(dap_breakpoint_ids)
8989

9090
verified_breakpoint_ids = []
9191
unverified_breakpoint_ids = []
92-
for breakpoint_event in self.dap_server.wait_for_breakpoint_events(timeout=5):
92+
for breakpoint_event in self.dap_server.wait_for_breakpoint_events():
9393
breakpoint = breakpoint_event["body"]["breakpoint"]
9494
id = breakpoint["id"]
9595
if breakpoint["verified"]:

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ def test_command_directive_quiet_on_success(self):
2323
exitCommands=["?" + command_quiet, command_not_quiet],
2424
)
2525
full_output = self.collect_console(
26-
timeout=1.0,
2726
pattern=command_not_quiet,
2827
)
2928
self.assertNotIn(command_quiet, full_output)
@@ -51,7 +50,6 @@ def do_test_abort_on_error(
5150
expectFailure=True,
5251
)
5352
full_output = self.collect_console(
54-
timeout=1.0,
5553
pattern=command_abort_on_error,
5654
)
5755
self.assertNotIn(command_quiet, full_output)

0 commit comments

Comments
 (0)