Skip to content

Commit 45b75e4

Browse files
JDevliegheregithub-actions[bot]
authored andcommitted
Automerge: [lldb-dap] Unify the timeouts for the DAP tests (#163292)
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
2 parents d02a088 + 25c62bc commit 45b75e4

File tree

11 files changed

+75
-103
lines changed

11 files changed

+75
-103
lines changed

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

Lines changed: 27 additions & 28 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)
@@ -339,7 +341,7 @@ def _recv_packet(
339341
self,
340342
*,
341343
predicate: Optional[Callable[[ProtocolMessage], bool]] = None,
342-
timeout: Optional[float] = None,
344+
timeout: Optional[float] = DEFAULT_TIMEOUT,
343345
) -> Optional[ProtocolMessage]:
344346
"""Processes received packets from the adapter.
345347
Updates the DebugCommunication stateful properties based on the received
@@ -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),
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
@@ -1610,7 +1609,7 @@ def terminate(self):
16101609
# new messages will arrive and it should shutdown on its
16111610
# own.
16121611
process.stdin.close()
1613-
process.wait(timeout=20)
1612+
process.wait(timeout=DEFAULT_TIMEOUT)
16141613
except subprocess.TimeoutExpired:
16151614
process.kill()
16161615
process.wait()

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/cancel/TestDAP_cancel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_pending_request(self):
4646

4747
# Use a relatively short timeout since this is only to ensure the
4848
# following request is queued.
49-
blocking_seq = self.async_blocking_request(duration=1.0)
49+
blocking_seq = self.async_blocking_request(duration=self.DEFAULT_TIMEOUT / 10)
5050
# Use a longer timeout to ensure we catch if the request was interrupted
5151
# properly.
5252
pending_seq = self.async_blocking_request(duration=self.DEFAULT_TIMEOUT / 2)

0 commit comments

Comments
 (0)