Skip to content

Commit 5d99c69

Browse files
authored
Suppress error causing the service_extension_test to be flaky (#8108)
1 parent 0d7df6b commit 5d99c69

File tree

2 files changed

+83
-36
lines changed

2 files changed

+83
-36
lines changed

packages/devtools_app/integration_test/test/live_connection/service_extensions_test.dart

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -35,42 +35,53 @@ void main() {
3535
await resetHistory();
3636
});
3737

38-
testWidgets('can call services and service extensions', (tester) async {
39-
await pumpAndConnectDevTools(tester, testApp);
40-
await tester.pump(longDuration);
41-
42-
// TODO(kenz): re-work this integration test so that we do not have to be
43-
// on the inspector screen for this to pass.
44-
await switchToScreen(
45-
tester,
46-
tabIcon: ScreenMetaData.inspector.icon!,
47-
screenId: ScreenMetaData.inspector.id,
48-
);
49-
await tester.pump(longDuration);
50-
51-
// Ensure all futures are completed before running checks.
52-
await serviceConnection.serviceManager.service!.allFuturesCompleted;
53-
54-
logStatus('verify Flutter framework service extensions');
55-
await _verifyBooleanExtension(tester);
56-
await _verifyNumericExtension(tester);
57-
await _verifyStringExtension(tester);
58-
59-
logStatus('verify Flutter engine service extensions');
60-
expect(
61-
await serviceConnection.queryDisplayRefreshRate,
62-
equals(60),
63-
);
64-
65-
logStatus('verify services that are registered to exactly one client');
66-
await _verifyHotReloadAndHotRestart();
67-
await expectLater(
68-
serviceConnection.serviceManager.callService('fakeMethod'),
69-
throwsException,
70-
);
71-
72-
await disconnectFromTestApp(tester);
73-
});
38+
testWidgets(
39+
'can call services and service extensions',
40+
ignoreAllowedExceptions(
41+
(tester) async {
42+
await pumpAndConnectDevTools(tester, testApp);
43+
await tester.pump(longDuration);
44+
45+
// TODO(kenz): re-work this integration test so that we do not have to be
46+
// on the inspector screen for this to pass.
47+
await switchToScreen(
48+
tester,
49+
tabIcon: ScreenMetaData.inspector.icon!,
50+
screenId: ScreenMetaData.inspector.id,
51+
);
52+
await tester.pump(longDuration);
53+
54+
// Ensure all futures are completed before running checks.
55+
await serviceConnection.serviceManager.service!.allFuturesCompleted;
56+
57+
logStatus('verify Flutter framework service extensions');
58+
await _verifyBooleanExtension(tester);
59+
await _verifyNumericExtension(tester);
60+
await _verifyStringExtension(tester);
61+
62+
logStatus('verify Flutter engine service extensions');
63+
expect(
64+
await serviceConnection.queryDisplayRefreshRate,
65+
equals(60),
66+
);
67+
68+
logStatus('verify services that are registered to exactly one client');
69+
await _verifyHotReloadAndHotRestart();
70+
await expectLater(
71+
serviceConnection.serviceManager.callService('fakeMethod'),
72+
throwsException,
73+
);
74+
75+
await disconnectFromTestApp(tester);
76+
},
77+
allowedExceptions: [
78+
AllowedException(
79+
msg: 'A SemanticsHandle was active at the end of the test.',
80+
issue: 'https://github.com/flutter/devtools/issues/8107',
81+
),
82+
],
83+
),
84+
);
7485

7586
testWidgets('loads initial extension states from device', (tester) async {
7687
await pumpAndConnectDevTools(tester, testApp);

packages/devtools_test/lib/src/integration_test/integration_test_utils.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:convert';
67
import 'dart:ui' as ui;
78

9+
import 'package:collection/collection.dart';
810
import 'package:devtools_app/devtools_app.dart';
911
import 'package:devtools_app/main.dart' as app;
1012
import 'package:devtools_app_shared/ui.dart';
@@ -157,3 +159,37 @@ Future<void> verifyScreenshot(
157159
},
158160
);
159161
}
162+
163+
class AllowedException {
164+
AllowedException({required this.msg, required this.issue});
165+
166+
final String msg;
167+
final String issue;
168+
}
169+
170+
/// Wraps the callback to [testWidgets] in a new zone that will catch any
171+
/// exceptions thrown during the test or after the test completes.
172+
///
173+
/// If the exception is included in [allowedExceptions], the exception will be
174+
/// logged but ignored. Otherwise, the exception will be rethrown.
175+
Future<void> Function(WidgetTester) ignoreAllowedExceptions(
176+
Future<void> Function(WidgetTester) testCallback, {
177+
required List<AllowedException> allowedExceptions,
178+
}) {
179+
return (WidgetTester tester) async {
180+
await runZonedGuarded(
181+
() async {
182+
await testCallback(tester);
183+
},
184+
(e, st) {
185+
final allowed = allowedExceptions
186+
.firstWhereOrNull((allowed) => '$e'.contains(allowed.msg));
187+
if (allowed == null) {
188+
throw Error.throwWithStackTrace(e, st);
189+
} else {
190+
logStatus('Ignoring exception due to ${allowed.issue}: $e');
191+
}
192+
},
193+
);
194+
};
195+
}

0 commit comments

Comments
 (0)