fix(test): kill the a11y suite's pending-timer flake at its root
Finally caught with a creation stack trace: when the last gRPC stream closes, Http2ClientConnection arms the channel's 5-minute idleTimeout timer — even on a shut-down connection — so the test's 1-minute drain never covered it and the framework's pending-timer invariant tripped whenever the arm landed inside the test window (frequent while a real hub listens on 50051). The suite now closes the channel in real-async space at the end of the body (new @visibleForTesting HubService.debugResetChannel; shutdown is deliberately not awaited — it wedges on a mid-connect socket, but cancels its timers synchronously) and pumps past the idle timeout so the timer fires inside the test. 6 consecutive full-suite runs + 3 isolated runs green; before, roughly 1 in 3 full runs failed. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
383490027b
commit
c18bb7f357
2 changed files with 32 additions and 0 deletions
|
|
@ -5,6 +5,7 @@
|
|||
// Methods return UI-friendly types so pages stay free of
|
||||
// protobuf imports.
|
||||
|
||||
import 'dart:async' show unawaited;
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
|
|
@ -196,6 +197,24 @@ class HubService {
|
|||
}
|
||||
}
|
||||
|
||||
/// Test hook: close the gRPC channel and replace the client with
|
||||
/// a fresh, not-yet-dialled one (same endpoint + token). Suites
|
||||
/// that pump the real app against no hub end with this so the
|
||||
/// channel's internal reconnect/idle timers cannot outlive the
|
||||
/// test body — a real socket failure landing late otherwise arms
|
||||
/// a new backoff timer and trips the framework's pending-timer
|
||||
/// invariant (the a11y suite's long-standing flake).
|
||||
@visibleForTesting
|
||||
void debugResetChannel() {
|
||||
final old = _client;
|
||||
_client = HubClient(endpoint: old.endpoint, authToken: _lastAuthToken);
|
||||
// Deliberately not awaited: shutdown() wedges on a socket that
|
||||
// is mid-connect, but the channel marks itself shut down and
|
||||
// cancels its timers synchronously at the start — which is all
|
||||
// this hook needs.
|
||||
unawaited(old.close().catchError((_) {}));
|
||||
}
|
||||
|
||||
/// Reload the token from disk and reconnect using the current
|
||||
/// endpoint. Called by Settings after the operator pastes or
|
||||
/// clears a token.
|
||||
|
|
|
|||
|
|
@ -88,6 +88,19 @@ void main() {
|
|||
// !timersPending after the test body.
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
await tester.pump(const Duration(minutes: 1));
|
||||
// The suite's long-standing pending-timer flake, finally
|
||||
// caught with a creation stack: when the last gRPC stream
|
||||
// closes, Http2ClientConnection._handleActiveStateChanged
|
||||
// arms the channel's 5-minute idleTimeout timer — even on a
|
||||
// shut-down connection — so a 1-minute drain never covered
|
||||
// it. Close the channel in real-async space (lets in-flight
|
||||
// socket callbacks land), then pump PAST the idle timeout so
|
||||
// the timer fires inside the test body.
|
||||
await tester.runAsync(() async {
|
||||
HubService.instance.debugResetChannel();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||
});
|
||||
await tester.pump(const Duration(minutes: 6));
|
||||
expect(
|
||||
violations,
|
||||
isEmpty,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue