1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6import 'package:flutter_test/flutter_test.dart';
7
8import 'package:flutter_test/flutter_test.dart' as flutter_test show expect;
9
10import 'package:matcher/expect.dart' as matcher show expect;
11
12// We have to use matcher's expect because the flutter_test expect() goes
13// out of its way to check that we're not leaking APIs and the whole point
14// of this test is to see how we handle leaking APIs.
15
16class TestAPI {
17 Future<Object?> testGuard1() {
18 return TestAsyncUtils.guard<Object?>(() async {
19 return null;
20 });
21 }
22
23 Future<Object?> testGuard2() {
24 return TestAsyncUtils.guard<Object?>(() async {
25 return null;
26 });
27 }
28}
29
30class TestAPISubclass extends TestAPI {
31 Future<Object?> testGuard3() {
32 return TestAsyncUtils.guard<Object?>(() async {
33 return null;
34 });
35 }
36}
37
38class RecognizableTestException implements Exception {
39 const RecognizableTestException();
40}
41
42Future<Object> _guardedThrower() {
43 return TestAsyncUtils.guard<Object>(() async {
44 throw const RecognizableTestException();
45 });
46}
47
48void main() {
49 test('TestAsyncUtils - one class', () async {
50 final TestAPI testAPI = TestAPI();
51 Future<Object?>? f1, f2;
52 f1 = testAPI.testGuard1();
53 try {
54 f2 = testAPI.testGuard2();
55 fail('unexpectedly did not throw');
56 } on FlutterError catch (e) {
57 final List<String> lines = e.message.split('\n');
58 matcher.expect(lines[0], 'Guarded function conflict.');
59 matcher.expect(lines[1], 'You must use "await" with all Future-returning test APIs.');
60 matcher.expect(
61 lines[2],
62 matches(
63 r'The guarded method "testGuard1" from class TestAPI was called from .*test_async_utils_test.dart on line [0-9]+\.',
64 ),
65 );
66 matcher.expect(
67 lines[3],
68 matches(
69 r'Then, the "testGuard2" method \(also from class TestAPI\) was called from .*test_async_utils_test.dart on line [0-9]+\.',
70 ),
71 );
72 matcher.expect(
73 lines[4],
74 'The first method (TestAPI.testGuard1) had not yet finished executing at the time that the second method (TestAPI.testGuard2) was called. Since both are guarded, and the second was not a nested call inside the first, the first must complete its execution before the second can be called. Typically, this is achieved by putting an "await" statement in front of the call to the first.',
75 );
76 matcher.expect(lines[5], '');
77 matcher.expect(
78 lines[6],
79 'When the first method (TestAPI.testGuard1) was called, this was the stack:',
80 );
81 matcher.expect(lines.length, greaterThan(6));
82 }
83 expect(await f1, isNull);
84 expect(f2, isNull);
85 }, skip: kIsWeb); // [intended] depends on platform-specific stack traces.
86
87 test('TestAsyncUtils - two classes, all callers in superclass', () async {
88 final TestAPI testAPI = TestAPISubclass();
89 Future<Object?>? f1, f2;
90 f1 = testAPI.testGuard1();
91 try {
92 f2 = testAPI.testGuard2();
93 fail('unexpectedly did not throw');
94 } on FlutterError catch (e) {
95 final List<String> lines = e.message.split('\n');
96 matcher.expect(lines[0], 'Guarded function conflict.');
97 matcher.expect(lines[1], 'You must use "await" with all Future-returning test APIs.');
98 matcher.expect(
99 lines[2],
100 matches(
101 r'^The guarded method "testGuard1" from class TestAPI was called from .*test_async_utils_test.dart on line [0-9]+\.$',
102 ),
103 );
104 matcher.expect(
105 lines[3],
106 matches(
107 r'^Then, the "testGuard2" method \(also from class TestAPI\) was called from .*test_async_utils_test.dart on line [0-9]+\.$',
108 ),
109 );
110 matcher.expect(
111 lines[4],
112 'The first method (TestAPI.testGuard1) had not yet finished executing at the time that the second method (TestAPI.testGuard2) was called. Since both are guarded, and the second was not a nested call inside the first, the first must complete its execution before the second can be called. Typically, this is achieved by putting an "await" statement in front of the call to the first.',
113 );
114 matcher.expect(lines[5], '');
115 matcher.expect(
116 lines[6],
117 'When the first method (TestAPI.testGuard1) was called, this was the stack:',
118 );
119 matcher.expect(lines.length, greaterThan(7));
120 }
121 expect(await f1, isNull);
122 expect(f2, isNull);
123 }, skip: kIsWeb); // [intended] depends on platform-specific stack traces.
124
125 test('TestAsyncUtils - two classes, mixed callers', () async {
126 final TestAPISubclass testAPI = TestAPISubclass();
127 Future<Object?>? f1, f2;
128 f1 = testAPI.testGuard1();
129 try {
130 f2 = testAPI.testGuard3();
131 fail('unexpectedly did not throw');
132 } on FlutterError catch (e) {
133 final List<String> lines = e.message.split('\n');
134 matcher.expect(lines[0], 'Guarded function conflict.');
135 matcher.expect(lines[1], 'You must use "await" with all Future-returning test APIs.');
136 matcher.expect(
137 lines[2],
138 matches(
139 r'^The guarded method "testGuard1" from class TestAPI was called from .*test_async_utils_test.dart on line [0-9]+\.$',
140 ),
141 );
142 matcher.expect(
143 lines[3],
144 matches(
145 r'^Then, the "testGuard3" method from class TestAPISubclass was called from .*test_async_utils_test.dart on line [0-9]+\.$',
146 ),
147 );
148 matcher.expect(
149 lines[4],
150 'The first method (TestAPI.testGuard1) had not yet finished executing at the time that the second method (TestAPISubclass.testGuard3) was called. Since both are guarded, and the second was not a nested call inside the first, the first must complete its execution before the second can be called. Typically, this is achieved by putting an "await" statement in front of the call to the first.',
151 );
152 matcher.expect(lines[5], '');
153 matcher.expect(
154 lines[6],
155 'When the first method (TestAPI.testGuard1) was called, this was the stack:',
156 );
157 matcher.expect(lines.length, greaterThan(7));
158 }
159 expect(await f1, isNull);
160 expect(f2, isNull);
161 }, skip: kIsWeb); // [intended] depends on platform-specific stack traces.
162
163 test('TestAsyncUtils - expect() catches pending async work', () async {
164 final TestAPI testAPI = TestAPISubclass();
165 Future<Object?>? f1;
166 f1 = testAPI.testGuard1();
167 try {
168 flutter_test.expect(0, 0);
169 fail('unexpectedly did not throw');
170 } on FlutterError catch (e) {
171 final List<String> lines = e.message.split('\n');
172 matcher.expect(lines[0], 'Guarded function conflict.');
173 matcher.expect(lines[1], 'You must use "await" with all Future-returning test APIs.');
174 matcher.expect(
175 lines[2],
176 matches(
177 r'^The guarded method "testGuard1" from class TestAPI was called from .*test_async_utils_test.dart on line [0-9]+\.$',
178 ),
179 );
180 matcher.expect(
181 lines[3],
182 matches(
183 r'^Then, the "expect" function was called from .*test_async_utils_test.dart on line [0-9]+\.$',
184 ),
185 );
186 matcher.expect(
187 lines[4],
188 'The first method (TestAPI.testGuard1) had not yet finished executing at the time that the second function (expect) was called. Since both are guarded, and the second was not a nested call inside the first, the first must complete its execution before the second can be called. Typically, this is achieved by putting an "await" statement in front of the call to the first.',
189 );
190 matcher.expect(
191 lines[5],
192 'If you are confident that all test APIs are being called using "await", and this expect() call is not being called at the top level but is itself being called from some sort of callback registered before the testGuard1 method was called, then consider using expectSync() instead.',
193 );
194 matcher.expect(lines[6], '');
195 matcher.expect(
196 lines[7],
197 'When the first method (TestAPI.testGuard1) was called, this was the stack:',
198 );
199 matcher.expect(lines.length, greaterThan(7));
200 }
201 expect(await f1, isNull);
202 }, skip: kIsWeb); // [intended] depends on platform-specific stack traces.
203
204 testWidgets('TestAsyncUtils - expect() catches pending async work', (WidgetTester tester) async {
205 Future<Object?>? f1, f2;
206 try {
207 f1 = tester.pump();
208 f2 = tester.pump();
209 fail('unexpectedly did not throw');
210 } on FlutterError catch (e) {
211 final List<String> lines = e.message.split('\n');
212 matcher.expect(lines[0], 'Guarded function conflict.');
213 matcher.expect(lines[1], 'You must use "await" with all Future-returning test APIs.');
214 matcher.expect(
215 lines[2],
216 matches(
217 r'^The guarded method "pump" from class WidgetTester was called from .*test_async_utils_test.dart on line [0-9]+\.$',
218 ),
219 );
220 matcher.expect(
221 lines[3],
222 matches(r'^Then, it was called from .*test_async_utils_test.dart on line [0-9]+\.$'),
223 );
224 matcher.expect(
225 lines[4],
226 'The first method had not yet finished executing at the time that the second method was called. Since both are guarded, and the second was not a nested call inside the first, the first must complete its execution before the second can be called. Typically, this is achieved by putting an "await" statement in front of the call to the first.',
227 );
228 matcher.expect(lines[5], '');
229 matcher.expect(lines[6], 'When the first method was called, this was the stack:');
230 matcher.expect(lines.length, greaterThan(7));
231 // TODO(jacobr): add more tests like this if they are useful.
232
233 final DiagnosticPropertiesBuilder propertiesBuilder = DiagnosticPropertiesBuilder();
234 e.debugFillProperties(propertiesBuilder);
235 final List<DiagnosticsNode> information = propertiesBuilder.properties;
236 matcher.expect(information.length, 6);
237 matcher.expect(information[0].level, DiagnosticLevel.summary);
238 matcher.expect(information[1].level, DiagnosticLevel.hint);
239 matcher.expect(information[2].level, DiagnosticLevel.info);
240 matcher.expect(information[3].level, DiagnosticLevel.info);
241 matcher.expect(information[4].level, DiagnosticLevel.info);
242 matcher.expect(information[5].level, DiagnosticLevel.info);
243 matcher.expect(information[0], isA<DiagnosticsProperty<void>>());
244 matcher.expect(information[1], isA<DiagnosticsProperty<void>>());
245 matcher.expect(information[2], isA<DiagnosticsProperty<void>>());
246 matcher.expect(information[3], isA<DiagnosticsProperty<void>>());
247 matcher.expect(information[4], isA<DiagnosticsProperty<void>>());
248 matcher.expect(information[5], isA<DiagnosticsStackTrace>());
249 final DiagnosticsStackTrace stackTraceProperty = information[5] as DiagnosticsStackTrace;
250 matcher.expect(
251 stackTraceProperty.name,
252 '\nWhen the first method was called, this was the stack',
253 );
254 matcher.expect(stackTraceProperty.value, isA<StackTrace>());
255 }
256 await f1;
257 await f2;
258 }, skip: kIsWeb); // [intended] depends on platform-specific stack traces.
259
260 testWidgets('TestAsyncUtils - expect() catches pending async work', (WidgetTester tester) async {
261 Future<Object?>? f1;
262 try {
263 f1 = tester.pump();
264 TestAsyncUtils.verifyAllScopesClosed();
265 fail('unexpectedly did not throw');
266 } on FlutterError catch (e) {
267 final List<String> lines = e.message.split('\n');
268 matcher.expect(lines[0], 'Asynchronous call to guarded function leaked.');
269 matcher.expect(lines[1], 'You must use "await" with all Future-returning test APIs.');
270 matcher.expect(
271 lines[2],
272 matches(
273 r'^The guarded method "pump" from class WidgetTester was called from .*test_async_utils_test.dart on line [0-9]+, but never completed before its parent scope closed\.$',
274 ),
275 );
276 matcher.expect(
277 lines[3],
278 matches(
279 r'^The guarded method "pump" from class AutomatedTestWidgetsFlutterBinding was called from [^ ]+ on line [0-9]+, but never completed before its parent scope closed\.',
280 ),
281 );
282 matcher.expect(lines.length, 4);
283 final DiagnosticPropertiesBuilder propertiesBuilder = DiagnosticPropertiesBuilder();
284 e.debugFillProperties(propertiesBuilder);
285 final List<DiagnosticsNode> information = propertiesBuilder.properties;
286 matcher.expect(information.length, 4);
287 matcher.expect(information[0].level, DiagnosticLevel.summary);
288 matcher.expect(information[1].level, DiagnosticLevel.hint);
289 matcher.expect(information[2].level, DiagnosticLevel.info);
290 matcher.expect(information[3].level, DiagnosticLevel.info);
291 }
292 await f1;
293 }, skip: kIsWeb); // [intended] depends on platform-specific stack traces.
294
295 testWidgets('TestAsyncUtils - expect() catches pending async work', (WidgetTester tester) async {
296 Future<Object?>? f1;
297 try {
298 f1 = tester.pump();
299 TestAsyncUtils.verifyAllScopesClosed();
300 fail('unexpectedly did not throw');
301 } on FlutterError catch (e) {
302 final List<String> lines = e.message.split('\n');
303 matcher.expect(lines[0], 'Asynchronous call to guarded function leaked.');
304 matcher.expect(lines[1], 'You must use "await" with all Future-returning test APIs.');
305 matcher.expect(
306 lines[2],
307 matches(
308 r'^The guarded method "pump" from class WidgetTester was called from .*test_async_utils_test.dart on line [0-9]+, but never completed before its parent scope closed\.$',
309 ),
310 );
311 matcher.expect(
312 lines[3],
313 matches(
314 r'^The guarded method "pump" from class AutomatedTestWidgetsFlutterBinding was called from [^ ]+ on line [0-9]+, but never completed before its parent scope closed\.',
315 ),
316 );
317 matcher.expect(lines.length, 4);
318 final DiagnosticPropertiesBuilder propertiesBuilder = DiagnosticPropertiesBuilder();
319 e.debugFillProperties(propertiesBuilder);
320 final List<DiagnosticsNode> information = propertiesBuilder.properties;
321 matcher.expect(information.length, 4);
322 matcher.expect(information[0].level, DiagnosticLevel.summary);
323 matcher.expect(information[1].level, DiagnosticLevel.hint);
324 matcher.expect(information[2].level, DiagnosticLevel.info);
325 matcher.expect(information[3].level, DiagnosticLevel.info);
326 }
327 await f1;
328 }, skip: kIsWeb); // [intended] depends on platform-specific stack traces.
329
330 testWidgets('TestAsyncUtils - guard body can throw', (WidgetTester tester) async {
331 try {
332 await _guardedThrower();
333 expect(false, true); // _guardedThrower should throw and we shouldn't reach here
334 } on RecognizableTestException catch (e) {
335 expect(e, const RecognizableTestException());
336 }
337 });
338
339 test('TestAsyncUtils - web', () async {
340 final TestAPI testAPI = TestAPI();
341 Future<Object?>? f1, f2;
342 f1 = testAPI.testGuard1();
343 try {
344 f2 = testAPI.testGuard2();
345 fail('unexpectedly did not throw');
346 } on FlutterError catch (e) {
347 final List<String> lines = e.message.split('\n');
348 matcher.expect(lines[0], 'Guarded function conflict.');
349 matcher.expect(lines[1], 'You must use "await" with all Future-returning test APIs.');
350 matcher.expect(lines[2], '');
351 matcher.expect(lines[3], 'When the first function was called, this was the stack:');
352 matcher.expect(lines.length, greaterThan(3));
353 }
354 expect(await f1, isNull);
355 expect(f2, isNull);
356 }, skip: !kIsWeb); // [intended] depends on platform-specific stack traces.
357
358 // see also dev/manual_tests/test_data which contains tests run by the flutter_tools tests for 'flutter test'
359}
360

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com