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 | |
5 | import 'dart:async'; |
6 | |
7 | import 'package:args/command_runner.dart'; |
8 | import 'package:file/file.dart'; |
9 | import 'package:file/memory.dart'; |
10 | import 'package:flutter_tools/src/application_package.dart'; |
11 | import 'package:flutter_tools/src/artifacts.dart'; |
12 | import 'package:flutter_tools/src/base/common.dart'; |
13 | import 'package:flutter_tools/src/base/file_system.dart'; |
14 | import 'package:flutter_tools/src/base/io.dart'; |
15 | import 'package:flutter_tools/src/base/logger.dart'; |
16 | import 'package:flutter_tools/src/base/platform.dart'; |
17 | import 'package:flutter_tools/src/base/terminal.dart'; |
18 | import 'package:flutter_tools/src/build_info.dart'; |
19 | import 'package:flutter_tools/src/cache.dart'; |
20 | import 'package:flutter_tools/src/commands/daemon.dart'; |
21 | import 'package:flutter_tools/src/commands/run.dart'; |
22 | import 'package:flutter_tools/src/devfs.dart'; |
23 | import 'package:flutter_tools/src/device.dart'; |
24 | import 'package:flutter_tools/src/globals.dart' as globals; |
25 | import 'package:flutter_tools/src/ios/devices.dart'; |
26 | import 'package:flutter_tools/src/project.dart'; |
27 | import 'package:flutter_tools/src/resident_runner.dart'; |
28 | import 'package:flutter_tools/src/runner/flutter_command.dart'; |
29 | import 'package:flutter_tools/src/web/compile.dart'; |
30 | import 'package:test/fake.dart'; |
31 | import 'package:unified_analytics/unified_analytics.dart'as analytics; |
32 | import 'package:vm_service/vm_service.dart'; |
33 | |
34 | import '../../src/common.dart'; |
35 | import '../../src/context.dart'; |
36 | import '../../src/fake_devices.dart'; |
37 | import '../../src/fakes.dart'; |
38 | import '../../src/package_config.dart'; |
39 | import '../../src/test_flutter_command_runner.dart'; |
40 | |
41 | void main() { |
42 | setUpAll(() { |
43 | Cache.disableLocking(); |
44 | }); |
45 | |
46 | group('run', () { |
47 | late BufferLogger logger; |
48 | late TestDeviceManager testDeviceManager; |
49 | late FileSystem fileSystem; |
50 | |
51 | setUp(() { |
52 | logger = BufferLogger.test(); |
53 | testDeviceManager = TestDeviceManager(logger: logger); |
54 | fileSystem = MemoryFileSystem.test(); |
55 | }); |
56 | |
57 | testUsingContext( |
58 | 'fails when target not found', |
59 | () async { |
60 | final RunCommand command = RunCommand(); |
61 | expect( |
62 | () => createTestCommandRunner(command).run(<String>['run', '-t', 'abc123', '--no-pub']), |
63 | throwsA( |
64 | isA<ToolExit>().having( |
65 | (ToolExit error) => error.exitCode, |
66 | 'exitCode', |
67 | anyOf(isNull, 1), |
68 | ), |
69 | ), |
70 | ); |
71 | }, |
72 | overrides: <Type, Generator>{ |
73 | FileSystem: () => fileSystem, |
74 | ProcessManager: () => FakeProcessManager.any(), |
75 | Logger: () => logger, |
76 | }, |
77 | ); |
78 | |
79 | testUsingContext( |
80 | 'does not support "--use-application-binary" and "--fast-start"', |
81 | () async { |
82 | fileSystem.file('lib/main.dart').createSync(recursive: true); |
83 | fileSystem.file('pubspec.yaml').createSync(); |
84 | fileSystem.file('.dart_tool/package_config.json').createSync(recursive: true); |
85 | |
86 | final RunCommand command = RunCommand(); |
87 | await expectLater( |
88 | () => createTestCommandRunner(command).run(<String>[ |
89 | 'run', |
90 | '--use-application-binary=app/bar/faz', |
91 | '--fast-start', |
92 | '--no-pub', |
93 | '--show-test-device', |
94 | ]), |
95 | throwsA( |
96 | isException.having( |
97 | (Exception exception) => exception.toString(), |
98 | 'toString', |
99 | isNot(contains('--fast-start is not supported with --use-application-binary')), |
100 | ), |
101 | ), |
102 | ); |
103 | }, |
104 | overrides: <Type, Generator>{ |
105 | FileSystem: () => fileSystem, |
106 | ProcessManager: () => FakeProcessManager.any(), |
107 | Logger: () => logger, |
108 | }, |
109 | ); |
110 | |
111 | testUsingContext( |
112 | 'Walks upward looking for a pubspec.yaml and succeeds if found', |
113 | () async { |
114 | fileSystem.file('pubspec.yaml').createSync(); |
115 | fileSystem.file('.dart_tool/package_config.json') |
116 | ..createSync(recursive: true) |
117 | ..writeAsStringSync(''' |
118 | { |
119 | "packages": [], |
120 | "configVersion": 2 |
121 | } |
122 | '''); |
123 | fileSystem.file('lib/main.dart').createSync(recursive: true); |
124 | fileSystem.currentDirectory = fileSystem.directory('a/b/c')..createSync(recursive: true); |
125 | |
126 | final RunCommand command = RunCommand(); |
127 | await expectLater( |
128 | () => createTestCommandRunner(command).run(<String>['run', '--no-pub']), |
129 | throwsToolExit(), |
130 | ); |
131 | final BufferLogger bufferLogger = globals.logger as BufferLogger; |
132 | expect( |
133 | bufferLogger.statusText, |
134 | containsIgnoringWhitespace('Changing current working directory to:'), |
135 | ); |
136 | }, |
137 | overrides: <Type, Generator>{ |
138 | FileSystem: () => fileSystem, |
139 | ProcessManager: () => FakeProcessManager.any(), |
140 | Logger: () => logger, |
141 | }, |
142 | ); |
143 | |
144 | testUsingContext( |
145 | 'Walks upward looking for a pubspec.yaml and exits if missing', |
146 | () async { |
147 | fileSystem.currentDirectory = fileSystem.directory('a/b/c')..createSync(recursive: true); |
148 | fileSystem.file('lib/main.dart').createSync(recursive: true); |
149 | |
150 | final RunCommand command = RunCommand(); |
151 | await expectLater( |
152 | () => createTestCommandRunner(command).run(<String>['run', '--no-pub']), |
153 | throwsToolExit(message: 'No pubspec.yaml file found'), |
154 | ); |
155 | }, |
156 | overrides: <Type, Generator>{ |
157 | FileSystem: () => fileSystem, |
158 | ProcessManager: () => FakeProcessManager.any(), |
159 | Logger: () => logger, |
160 | }, |
161 | ); |
162 | |
163 | group('run app', () { |
164 | late MemoryFileSystem fs; |
165 | late Artifacts artifacts; |
166 | late FakeAnsiTerminal fakeTerminal; |
167 | late analytics.FakeAnalytics fakeAnalytics; |
168 | |
169 | setUpAll(() { |
170 | Cache.disableLocking(); |
171 | }); |
172 | |
173 | setUp(() { |
174 | fakeTerminal = FakeAnsiTerminal(); |
175 | artifacts = Artifacts.test(); |
176 | fs = MemoryFileSystem.test(); |
177 | |
178 | fs.currentDirectory.childFile('pubspec.yaml').writeAsStringSync( 'name: my_app'); |
179 | writePackageConfigFile(directory: fs.currentDirectory, mainLibName: 'my_app'); |
180 | |
181 | final Directory libDir = fs.currentDirectory.childDirectory('lib'); |
182 | libDir.createSync(); |
183 | final File mainFile = libDir.childFile('main.dart'); |
184 | mainFile.writeAsStringSync('void main() {}'); |
185 | fakeAnalytics = getInitializedFakeAnalyticsInstance( |
186 | fs: fs, |
187 | fakeFlutterVersion: FakeFlutterVersion(), |
188 | ); |
189 | }); |
190 | |
191 | testUsingContext( |
192 | 'exits with a user message when no supported devices attached', |
193 | () async { |
194 | final RunCommand command = RunCommand(); |
195 | testDeviceManager.devices = <Device>[]; |
196 | |
197 | await expectLater( |
198 | () => createTestCommandRunner(command).run(<String>['run', '--no-pub', '--no-hot']), |
199 | throwsA(isA<ToolExit>().having((ToolExit error) => error.message, 'message', isNull)), |
200 | ); |
201 | |
202 | expect( |
203 | testLogger.statusText, |
204 | containsIgnoringWhitespace('No supported devices connected.'), |
205 | ); |
206 | }, |
207 | overrides: <Type, Generator>{ |
208 | DeviceManager: () => testDeviceManager, |
209 | FileSystem: () => fs, |
210 | ProcessManager: () => FakeProcessManager.any(), |
211 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
212 | }, |
213 | ); |
214 | |
215 | testUsingContext( |
216 | 'exits and lists available devices when specified device not found', |
217 | () async { |
218 | final RunCommand command = RunCommand(); |
219 | final FakeDevice device = FakeDevice(isLocalEmulator: true); |
220 | testDeviceManager |
221 | ..devices = <Device>[device] |
222 | ..specifiedDeviceId = 'invalid-device-id'; |
223 | |
224 | await expectLater( |
225 | () => createTestCommandRunner( |
226 | command, |
227 | ).run(<String>['run', '-d', 'invalid-device-id', '--no-pub', '--no-hot']), |
228 | throwsToolExit(), |
229 | ); |
230 | expect( |
231 | testLogger.statusText, |
232 | contains("No supported devices found with name or id matching 'invalid-device-id'"), |
233 | ); |
234 | expect(testLogger.statusText, contains('The following devices were found:')); |
235 | expect( |
236 | testLogger.statusText, |
237 | contains('FakeDevice (mobile) • fake_device • ios • (simulator)'), |
238 | ); |
239 | }, |
240 | overrides: <Type, Generator>{ |
241 | DeviceManager: () => testDeviceManager, |
242 | FileSystem: () => fs, |
243 | ProcessManager: () => FakeProcessManager.any(), |
244 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
245 | }, |
246 | ); |
247 | |
248 | testUsingContext( |
249 | 'fails when targeted device is not Android with --device-user', |
250 | () async { |
251 | final FakeDevice device = FakeDevice(isLocalEmulator: true); |
252 | |
253 | testDeviceManager.devices = <Device>[device]; |
254 | |
255 | final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates(); |
256 | await expectLater( |
257 | createTestCommandRunner( |
258 | command, |
259 | ).run(<String>['run', '--no-pub', '--device-user', '10']), |
260 | throwsToolExit( |
261 | message: |
262 | '--device-user is only supported for Android. At least one Android device is required.', |
263 | ), |
264 | ); |
265 | }, |
266 | overrides: <Type, Generator>{ |
267 | FileSystem: () => fs, |
268 | ProcessManager: () => FakeProcessManager.any(), |
269 | DeviceManager: () => testDeviceManager, |
270 | Stdio: () => FakeStdio(), |
271 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
272 | }, |
273 | ); |
274 | |
275 | testUsingContext( |
276 | 'succeeds when targeted device is an Android device with --device-user', |
277 | () async { |
278 | final FakeDevice device = FakeDevice( |
279 | isLocalEmulator: true, |
280 | platformType: PlatformType.android, |
281 | ); |
282 | |
283 | testDeviceManager.devices = <Device>[device]; |
284 | |
285 | final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates(); |
286 | await createTestCommandRunner( |
287 | command, |
288 | ).run(<String>['run', '--no-pub', '--device-user', '10']); |
289 | // Finishes normally without error. |
290 | }, |
291 | overrides: <Type, Generator>{ |
292 | FileSystem: () => fs, |
293 | ProcessManager: () => FakeProcessManager.any(), |
294 | DeviceManager: () => testDeviceManager, |
295 | Stdio: () => FakeStdio(), |
296 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
297 | }, |
298 | ); |
299 | |
300 | testUsingContext( |
301 | 'shows unsupported devices when no supported devices are found', |
302 | () async { |
303 | final RunCommand command = RunCommand(); |
304 | final FakeDevice mockDevice = FakeDevice( |
305 | targetPlatform: TargetPlatform.android_arm, |
306 | isLocalEmulator: true, |
307 | sdkNameAndVersion: 'api-14', |
308 | isSupported: false, |
309 | ); |
310 | testDeviceManager.devices = <Device>[mockDevice]; |
311 | |
312 | await expectLater( |
313 | () => createTestCommandRunner(command).run(<String>['run', '--no-pub', '--no-hot']), |
314 | throwsA(isA<ToolExit>().having((ToolExit error) => error.message, 'message', isNull)), |
315 | ); |
316 | |
317 | expect( |
318 | testLogger.statusText, |
319 | containsIgnoringWhitespace('No supported devices connected.'), |
320 | ); |
321 | expect( |
322 | testLogger.statusText, |
323 | containsIgnoringWhitespace( |
324 | 'The following devices were found, but are not supported by this project:', |
325 | ), |
326 | ); |
327 | expect( |
328 | testLogger.statusText, |
329 | containsIgnoringWhitespace( |
330 | globals.userMessages.flutterMissPlatformProjects( |
331 | Device.devicesPlatformTypes(<Device>[mockDevice]), |
332 | ), |
333 | ), |
334 | ); |
335 | }, |
336 | overrides: <Type, Generator>{ |
337 | DeviceManager: () => testDeviceManager, |
338 | FileSystem: () => fs, |
339 | ProcessManager: () => FakeProcessManager.any(), |
340 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
341 | }, |
342 | ); |
343 | |
344 | testUsingContext( |
345 | 'prints warning when --flavor is used with an unsupported target platform', |
346 | () async { |
347 | const List<String> runCommand = <String>[ |
348 | 'run', |
349 | '--no-pub', |
350 | '--no-hot', |
351 | '--flavor=vanilla', |
352 | '-d', |
353 | 'all', |
354 | ]; |
355 | // Useful for test readability. |
356 | // ignore: avoid_redundant_argument_values |
357 | final FakeDevice deviceWithoutFlavorSupport = FakeDevice(supportsFlavors: false); |
358 | final FakeDevice deviceWithFlavorSupport = FakeDevice(supportsFlavors: true); |
359 | testDeviceManager.devices = <Device>[deviceWithoutFlavorSupport, deviceWithFlavorSupport]; |
360 | |
361 | await createTestCommandRunner(TestRunCommandThatOnlyValidates()).run(runCommand); |
362 | |
363 | expect( |
364 | logger.warningText, |
365 | contains( |
366 | '--flavor is only supported for Android, macOS, and iOS devices. ' |
367 | 'Flavor-related features may not function properly and could ' |
368 | 'behave differently in a future release.', |
369 | ), |
370 | ); |
371 | }, |
372 | overrides: <Type, Generator>{ |
373 | DeviceManager: () => testDeviceManager, |
374 | FileSystem: () => fs, |
375 | ProcessManager: () => FakeProcessManager.any(), |
376 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
377 | Logger: () => logger, |
378 | }, |
379 | ); |
380 | |
381 | testUsingContext( |
382 | 'forwards --uninstall-only to DebuggingOptions', |
383 | () async { |
384 | final RunCommand command = RunCommand(); |
385 | final FakeDevice mockDevice = FakeDevice(sdkNameAndVersion: 'iOS 13') |
386 | ..startAppSuccess = false; |
387 | |
388 | testDeviceManager.devices = <Device>[mockDevice]; |
389 | |
390 | // Causes swift to be detected in the analytics. |
391 | fs.currentDirectory |
392 | .childDirectory('ios') |
393 | .childFile('AppDelegate.swift') |
394 | .createSync(recursive: true); |
395 | |
396 | await expectToolExitLater( |
397 | createTestCommandRunner( |
398 | command, |
399 | ).run(<String>['run', '--no-pub', '--no-hot', '--uninstall-first']), |
400 | isNull, |
401 | ); |
402 | |
403 | final DebuggingOptions options = await command.createDebuggingOptions(false); |
404 | expect(options.uninstallFirst, isTrue); |
405 | }, |
406 | overrides: <Type, Generator>{ |
407 | Artifacts: () => artifacts, |
408 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
409 | DeviceManager: () => testDeviceManager, |
410 | FileSystem: () => fs, |
411 | ProcessManager: () => FakeProcessManager.any(), |
412 | }, |
413 | ); |
414 | |
415 | testUsingContext( |
416 | 'passes device target platform to analytics', |
417 | () async { |
418 | final RunCommand command = RunCommand(); |
419 | final FakeDevice mockDevice = FakeDevice(sdkNameAndVersion: 'iOS 13') |
420 | ..startAppSuccess = false; |
421 | |
422 | testDeviceManager.devices = <Device>[mockDevice]; |
423 | |
424 | // Causes swift to be detected in the analytics. |
425 | fs.currentDirectory |
426 | .childDirectory('ios') |
427 | .childFile('AppDelegate.swift') |
428 | .createSync(recursive: true); |
429 | |
430 | await expectToolExitLater( |
431 | createTestCommandRunner(command).run(<String>['run', '--no-pub', '--no-hot']), |
432 | isNull, |
433 | ); |
434 | |
435 | expect( |
436 | fakeAnalytics.sentEvents, |
437 | contains( |
438 | analytics.Event.commandUsageValues( |
439 | workflow: 'run', |
440 | commandHasTerminal: globals.stdio.hasTerminal, |
441 | runIsEmulator: false, |
442 | runTargetName: 'ios', |
443 | runTargetOsVersion: 'iOS 13', |
444 | runModeName: 'debug', |
445 | runProjectModule: false, |
446 | runProjectHostLanguage: 'swift', |
447 | runIOSInterfaceType: 'usb', |
448 | runIsTest: false, |
449 | ), |
450 | ), |
451 | ); |
452 | }, |
453 | overrides: <Type, Generator>{ |
454 | AnsiTerminal: () => fakeTerminal, |
455 | Artifacts: () => artifacts, |
456 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
457 | DeviceManager: () => testDeviceManager, |
458 | FileSystem: () => fs, |
459 | ProcessManager: () => FakeProcessManager.any(), |
460 | Stdio: () => FakeStdio(), |
461 | analytics.Analytics: () => fakeAnalytics, |
462 | }, |
463 | ); |
464 | |
465 | testUsingContext( |
466 | 'correctly reports tests to analytics', |
467 | () async { |
468 | fs.currentDirectory |
469 | .childDirectory('test') |
470 | .childFile('widget_test.dart') |
471 | .createSync(recursive: true); |
472 | fs.currentDirectory |
473 | .childDirectory('ios') |
474 | .childFile('AppDelegate.swift') |
475 | .createSync(recursive: true); |
476 | final RunCommand command = RunCommand(); |
477 | final FakeDevice mockDevice = FakeDevice(sdkNameAndVersion: 'iOS 13') |
478 | ..startAppSuccess = false; |
479 | |
480 | testDeviceManager.devices = <Device>[mockDevice]; |
481 | |
482 | await expectToolExitLater( |
483 | createTestCommandRunner( |
484 | command, |
485 | ).run(<String>['run', '--no-pub', '--no-hot', 'test/widget_test.dart']), |
486 | isNull, |
487 | ); |
488 | |
489 | expect( |
490 | fakeAnalytics.sentEvents, |
491 | contains( |
492 | analytics.Event.commandUsageValues( |
493 | workflow: 'run', |
494 | commandHasTerminal: globals.stdio.hasTerminal, |
495 | runIsEmulator: false, |
496 | runTargetName: 'ios', |
497 | runTargetOsVersion: 'iOS 13', |
498 | runModeName: 'debug', |
499 | runProjectModule: false, |
500 | runProjectHostLanguage: 'swift', |
501 | runIOSInterfaceType: 'usb', |
502 | runIsTest: true, |
503 | ), |
504 | ), |
505 | ); |
506 | }, |
507 | overrides: <Type, Generator>{ |
508 | AnsiTerminal: () => fakeTerminal, |
509 | Artifacts: () => artifacts, |
510 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
511 | DeviceManager: () => testDeviceManager, |
512 | FileSystem: () => fs, |
513 | ProcessManager: () => FakeProcessManager.any(), |
514 | Stdio: () => FakeStdio(), |
515 | analytics.Analytics: () => fakeAnalytics, |
516 | }, |
517 | ); |
518 | |
519 | group('--machine', () { |
520 | testUsingContext( |
521 | 'can pass --device-user', |
522 | () async { |
523 | final DaemonCapturingRunCommand command = DaemonCapturingRunCommand(); |
524 | final FakeDevice device = FakeDevice(platformType: PlatformType.android); |
525 | testDeviceManager.devices = <Device>[device]; |
526 | |
527 | await expectLater( |
528 | () => createTestCommandRunner(command).run(<String>[ |
529 | 'run', |
530 | '--no-pub', |
531 | '--machine', |
532 | '--device-user', |
533 | '10', |
534 | '-d', |
535 | device.id, |
536 | ]), |
537 | throwsToolExit(), |
538 | ); |
539 | expect(command.appDomain.userIdentifier, '10'); |
540 | }, |
541 | overrides: <Type, Generator>{ |
542 | Artifacts: () => artifacts, |
543 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
544 | DeviceManager: () => testDeviceManager, |
545 | FileSystem: () => fs, |
546 | ProcessManager: () => FakeProcessManager.any(), |
547 | Stdio: () => FakeStdio(), |
548 | Logger: () => AppRunLogger(parent: logger), |
549 | }, |
550 | ); |
551 | |
552 | testUsingContext( |
553 | 'can disable devtools with --no-devtools', |
554 | () async { |
555 | final DaemonCapturingRunCommand command = DaemonCapturingRunCommand(); |
556 | final FakeDevice device = FakeDevice(); |
557 | testDeviceManager.devices = <Device>[device]; |
558 | |
559 | await expectLater( |
560 | () => createTestCommandRunner( |
561 | command, |
562 | ).run(<String>['run', '--no-pub', '--no-devtools', '--machine', '-d', device.id]), |
563 | throwsToolExit(), |
564 | ); |
565 | expect(command.appDomain.enableDevTools, isFalse); |
566 | }, |
567 | overrides: <Type, Generator>{ |
568 | Artifacts: () => artifacts, |
569 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
570 | DeviceManager: () => testDeviceManager, |
571 | FileSystem: () => fs, |
572 | ProcessManager: () => FakeProcessManager.any(), |
573 | Stdio: () => FakeStdio(), |
574 | Logger: () => AppRunLogger(parent: logger), |
575 | }, |
576 | ); |
577 | }); |
578 | }); |
579 | |
580 | group('Fatal Logs', () { |
581 | late TestRunCommandWithFakeResidentRunner command; |
582 | late MemoryFileSystem fs; |
583 | |
584 | setUp(() { |
585 | command = TestRunCommandWithFakeResidentRunner()..fakeResidentRunner = FakeResidentRunner(); |
586 | fs = MemoryFileSystem.test(); |
587 | }); |
588 | |
589 | testUsingContext( |
590 | "doesn't fail if --fatal-warnings specified and no warnings occur", |
591 | () async { |
592 | try { |
593 | await createTestCommandRunner( |
594 | command, |
595 | ).run(<String>['run', '--no-pub', '--no-hot', '--${FlutterOptions.kFatalWarnings} ']); |
596 | } on Exception { |
597 | fail('Unexpected exception thrown'); |
598 | } |
599 | }, |
600 | overrides: <Type, Generator>{ |
601 | FileSystem: () => fs, |
602 | ProcessManager: () => FakeProcessManager.any(), |
603 | }, |
604 | ); |
605 | |
606 | testUsingContext( |
607 | "doesn't fail if --fatal-warnings not specified", |
608 | () async { |
609 | testLogger.printWarning('Warning: Mild annoyance Will Robinson!'); |
610 | try { |
611 | await createTestCommandRunner(command).run(<String>['run', '--no-pub', '--no-hot']); |
612 | } on Exception { |
613 | fail('Unexpected exception thrown'); |
614 | } |
615 | }, |
616 | overrides: <Type, Generator>{ |
617 | FileSystem: () => fs, |
618 | ProcessManager: () => FakeProcessManager.any(), |
619 | }, |
620 | ); |
621 | |
622 | testUsingContext( |
623 | 'fails if --fatal-warnings specified and warnings emitted', |
624 | () async { |
625 | testLogger.printWarning('Warning: Mild annoyance Will Robinson!'); |
626 | await expectLater( |
627 | createTestCommandRunner( |
628 | command, |
629 | ).run(<String>['run', '--no-pub', '--no-hot', '--${FlutterOptions.kFatalWarnings} ']), |
630 | throwsToolExit( |
631 | message: |
632 | 'Logger received warning output during the run, and "--${FlutterOptions.kFatalWarnings} " is enabled.', |
633 | ), |
634 | ); |
635 | }, |
636 | overrides: <Type, Generator>{ |
637 | FileSystem: () => fs, |
638 | ProcessManager: () => FakeProcessManager.any(), |
639 | }, |
640 | ); |
641 | |
642 | testUsingContext( |
643 | 'fails if --fatal-warnings specified and errors emitted', |
644 | () async { |
645 | testLogger.printError('Error: Danger Will Robinson!'); |
646 | await expectLater( |
647 | createTestCommandRunner( |
648 | command, |
649 | ).run(<String>['run', '--no-pub', '--no-hot', '--${FlutterOptions.kFatalWarnings} ']), |
650 | throwsToolExit( |
651 | message: |
652 | 'Logger received error output during the run, and "--${FlutterOptions.kFatalWarnings} " is enabled.', |
653 | ), |
654 | ); |
655 | }, |
656 | overrides: <Type, Generator>{ |
657 | FileSystem: () => fs, |
658 | ProcessManager: () => FakeProcessManager.any(), |
659 | }, |
660 | ); |
661 | }); |
662 | |
663 | testUsingContext( |
664 | 'should only request artifacts corresponding to connected devices', |
665 | () async { |
666 | testDeviceManager.devices = <Device>[ |
667 | FakeDevice(targetPlatform: TargetPlatform.android_arm), |
668 | ]; |
669 | |
670 | expect( |
671 | await RunCommand().requiredArtifacts, |
672 | unorderedEquals(<DevelopmentArtifact>{ |
673 | DevelopmentArtifact.universal, |
674 | DevelopmentArtifact.androidGenSnapshot, |
675 | }), |
676 | ); |
677 | |
678 | testDeviceManager.devices = <Device>[FakeDevice()]; |
679 | |
680 | expect( |
681 | await RunCommand().requiredArtifacts, |
682 | unorderedEquals(<DevelopmentArtifact>{ |
683 | DevelopmentArtifact.universal, |
684 | DevelopmentArtifact.iOS, |
685 | }), |
686 | ); |
687 | |
688 | testDeviceManager.devices = <Device>[ |
689 | FakeDevice(), |
690 | FakeDevice(targetPlatform: TargetPlatform.android_arm), |
691 | ]; |
692 | |
693 | expect( |
694 | await RunCommand().requiredArtifacts, |
695 | unorderedEquals(<DevelopmentArtifact>{ |
696 | DevelopmentArtifact.universal, |
697 | DevelopmentArtifact.iOS, |
698 | DevelopmentArtifact.androidGenSnapshot, |
699 | }), |
700 | ); |
701 | |
702 | testDeviceManager.devices = <Device>[ |
703 | FakeDevice(targetPlatform: TargetPlatform.web_javascript), |
704 | ]; |
705 | |
706 | expect( |
707 | await RunCommand().requiredArtifacts, |
708 | unorderedEquals(<DevelopmentArtifact>{ |
709 | DevelopmentArtifact.universal, |
710 | DevelopmentArtifact.web, |
711 | }), |
712 | ); |
713 | }, |
714 | overrides: <Type, Generator>{ |
715 | DeviceManager: () => testDeviceManager, |
716 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
717 | FileSystem: () => MemoryFileSystem.test(), |
718 | ProcessManager: () => FakeProcessManager.any(), |
719 | }, |
720 | ); |
721 | |
722 | group('usageValues', () { |
723 | testUsingContext( |
724 | 'with only non-iOS usb device', |
725 | () async { |
726 | final List<Device> devices = <Device>[ |
727 | FakeDevice( |
728 | targetPlatform: TargetPlatform.android_arm, |
729 | platformType: PlatformType.android, |
730 | ), |
731 | ]; |
732 | final TestRunCommandForUsageValues command = TestRunCommandForUsageValues( |
733 | devices: devices, |
734 | ); |
735 | final CommandRunner<void> runner = createTestCommandRunner(command); |
736 | try { |
737 | // run the command so that CLI args are parsed |
738 | await runner.run(<String>['run']); |
739 | } on ToolExit catch (error) { |
740 | // we can ignore the ToolExit, as we are only interested in |
741 | // command.usageValues. |
742 | expect( |
743 | error, |
744 | isA<ToolExit>().having( |
745 | (ToolExit exception) => exception.message, |
746 | 'message', |
747 | contains('No pubspec.yaml file found'), |
748 | ), |
749 | ); |
750 | } |
751 | |
752 | final analytics.Event usageValues = await command.unifiedAnalyticsUsageValues('run'); |
753 | |
754 | expect( |
755 | usageValues, |
756 | equals( |
757 | analytics.Event.commandUsageValues( |
758 | workflow: 'run', |
759 | commandHasTerminal: false, |
760 | runIsEmulator: false, |
761 | runTargetName: 'android-arm', |
762 | runTargetOsVersion: '', |
763 | runModeName: 'debug', |
764 | runProjectModule: false, |
765 | runProjectHostLanguage: '', |
766 | runIsTest: false, |
767 | ), |
768 | ), |
769 | ); |
770 | }, |
771 | overrides: <Type, Generator>{ |
772 | DeviceManager: () => testDeviceManager, |
773 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
774 | FileSystem: () => MemoryFileSystem.test(), |
775 | ProcessManager: () => FakeProcessManager.any(), |
776 | }, |
777 | ); |
778 | |
779 | testUsingContext( |
780 | 'with only iOS usb device', |
781 | () async { |
782 | final List<Device> devices = <Device>[FakeIOSDevice(sdkNameAndVersion: 'iOS 16.2')]; |
783 | final TestRunCommandForUsageValues command = TestRunCommandForUsageValues( |
784 | devices: devices, |
785 | ); |
786 | final CommandRunner<void> runner = createTestCommandRunner(command); |
787 | try { |
788 | // run the command so that CLI args are parsed |
789 | await runner.run(<String>['run']); |
790 | } on ToolExit catch (error) { |
791 | // we can ignore the ToolExit, as we are only interested in |
792 | // command.usageValues. |
793 | expect( |
794 | error, |
795 | isA<ToolExit>().having( |
796 | (ToolExit exception) => exception.message, |
797 | 'message', |
798 | contains('No pubspec.yaml file found'), |
799 | ), |
800 | ); |
801 | } |
802 | |
803 | final analytics.Event usageValues = await command.unifiedAnalyticsUsageValues('run'); |
804 | |
805 | expect( |
806 | usageValues, |
807 | equals( |
808 | analytics.Event.commandUsageValues( |
809 | workflow: 'run', |
810 | commandHasTerminal: false, |
811 | runIsEmulator: false, |
812 | runTargetName: 'ios', |
813 | runTargetOsVersion: 'iOS 16.2', |
814 | runModeName: 'debug', |
815 | runProjectModule: false, |
816 | runProjectHostLanguage: '', |
817 | runIOSInterfaceType: 'usb', |
818 | runIsTest: false, |
819 | ), |
820 | ), |
821 | ); |
822 | }, |
823 | overrides: <Type, Generator>{ |
824 | DeviceManager: () => testDeviceManager, |
825 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
826 | FileSystem: () => MemoryFileSystem.test(), |
827 | ProcessManager: () => FakeProcessManager.any(), |
828 | }, |
829 | ); |
830 | |
831 | testUsingContext( |
832 | 'with only iOS wireless device', |
833 | () async { |
834 | final List<Device> devices = <Device>[ |
835 | FakeIOSDevice( |
836 | connectionInterface: DeviceConnectionInterface.wireless, |
837 | sdkNameAndVersion: 'iOS 16.2', |
838 | ), |
839 | ]; |
840 | final TestRunCommandForUsageValues command = TestRunCommandForUsageValues( |
841 | devices: devices, |
842 | ); |
843 | final CommandRunner<void> runner = createTestCommandRunner(command); |
844 | try { |
845 | // run the command so that CLI args are parsed |
846 | await runner.run(<String>['run']); |
847 | } on ToolExit catch (error) { |
848 | // we can ignore the ToolExit, as we are only interested in |
849 | // command.usageValues. |
850 | expect( |
851 | error, |
852 | isA<ToolExit>().having( |
853 | (ToolExit exception) => exception.message, |
854 | 'message', |
855 | contains('No pubspec.yaml file found'), |
856 | ), |
857 | ); |
858 | } |
859 | |
860 | final analytics.Event usageValues = await command.unifiedAnalyticsUsageValues('run'); |
861 | |
862 | expect( |
863 | usageValues, |
864 | equals( |
865 | analytics.Event.commandUsageValues( |
866 | workflow: 'run', |
867 | commandHasTerminal: false, |
868 | runIsEmulator: false, |
869 | runTargetName: 'ios', |
870 | runTargetOsVersion: 'iOS 16.2', |
871 | runModeName: 'debug', |
872 | runProjectModule: false, |
873 | runProjectHostLanguage: '', |
874 | runIOSInterfaceType: 'wireless', |
875 | runIsTest: false, |
876 | ), |
877 | ), |
878 | ); |
879 | }, |
880 | overrides: <Type, Generator>{ |
881 | DeviceManager: () => testDeviceManager, |
882 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
883 | FileSystem: () => MemoryFileSystem.test(), |
884 | ProcessManager: () => FakeProcessManager.any(), |
885 | }, |
886 | ); |
887 | |
888 | testUsingContext( |
889 | 'with both iOS usb and wireless devices', |
890 | () async { |
891 | final List<Device> devices = <Device>[ |
892 | FakeIOSDevice( |
893 | connectionInterface: DeviceConnectionInterface.wireless, |
894 | sdkNameAndVersion: 'iOS 16.2', |
895 | ), |
896 | FakeIOSDevice(sdkNameAndVersion: 'iOS 16.2'), |
897 | ]; |
898 | final TestRunCommandForUsageValues command = TestRunCommandForUsageValues( |
899 | devices: devices, |
900 | ); |
901 | final CommandRunner<void> runner = createTestCommandRunner(command); |
902 | try { |
903 | // run the command so that CLI args are parsed |
904 | await runner.run(<String>['run']); |
905 | } on ToolExit catch (error) { |
906 | // we can ignore the ToolExit, as we are only interested in |
907 | // command.usageValues. |
908 | expect( |
909 | error, |
910 | isA<ToolExit>().having( |
911 | (ToolExit exception) => exception.message, |
912 | 'message', |
913 | contains('No pubspec.yaml file found'), |
914 | ), |
915 | ); |
916 | } |
917 | |
918 | final analytics.Event usageValues = await command.unifiedAnalyticsUsageValues('run'); |
919 | |
920 | expect( |
921 | usageValues, |
922 | equals( |
923 | analytics.Event.commandUsageValues( |
924 | workflow: 'run', |
925 | commandHasTerminal: false, |
926 | runIsEmulator: false, |
927 | runTargetName: 'multiple', |
928 | runTargetOsVersion: 'multiple', |
929 | runModeName: 'debug', |
930 | runProjectModule: false, |
931 | runProjectHostLanguage: '', |
932 | runIOSInterfaceType: 'wireless', |
933 | runIsTest: false, |
934 | ), |
935 | ), |
936 | ); |
937 | }, |
938 | overrides: <Type, Generator>{ |
939 | DeviceManager: () => testDeviceManager, |
940 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
941 | FileSystem: () => MemoryFileSystem.test(), |
942 | ProcessManager: () => FakeProcessManager.any(), |
943 | }, |
944 | ); |
945 | }); |
946 | |
947 | group('--web-header', () { |
948 | setUp(() { |
949 | fileSystem.file('lib/main.dart').createSync(recursive: true); |
950 | fileSystem.file('pubspec.yaml').createSync(); |
951 | fileSystem.file('.dart_tool/package_config.json') |
952 | ..createSync(recursive: true) |
953 | ..writeAsStringSync(''' |
954 | { |
955 | "packages": [], |
956 | "configVersion": 2 |
957 | } |
958 | '''); |
959 | final FakeDevice device = FakeDevice( |
960 | isLocalEmulator: true, |
961 | platformType: PlatformType.android, |
962 | ); |
963 | testDeviceManager.devices = <Device>[device]; |
964 | }); |
965 | |
966 | testUsingContext( |
967 | 'can accept simple, valid values', |
968 | () async { |
969 | final RunCommand command = RunCommand(); |
970 | await expectLater( |
971 | () => createTestCommandRunner( |
972 | command, |
973 | ).run(<String>['run', '--no-pub', '--no-hot', '--web-header', 'foo = bar']), |
974 | throwsToolExit(), |
975 | ); |
976 | |
977 | final DebuggingOptions options = await command.createDebuggingOptions(true); |
978 | expect(options.webHeaders, <String, String>{'foo': 'bar'}); |
979 | }, |
980 | overrides: <Type, Generator>{ |
981 | FileSystem: () => fileSystem, |
982 | ProcessManager: () => FakeProcessManager.any(), |
983 | Logger: () => logger, |
984 | DeviceManager: () => testDeviceManager, |
985 | }, |
986 | ); |
987 | |
988 | testUsingContext( |
989 | 'throws a ToolExit when no value is provided', |
990 | () async { |
991 | final RunCommand command = RunCommand(); |
992 | await expectLater( |
993 | () => createTestCommandRunner( |
994 | command, |
995 | ).run(<String>['run', '--no-pub', '--no-hot', '--web-header', 'foo']), |
996 | throwsToolExit(message: 'Invalid web headers: foo'), |
997 | ); |
998 | |
999 | await expectLater(() => command.createDebuggingOptions(true), throwsToolExit()); |
1000 | }, |
1001 | overrides: <Type, Generator>{ |
1002 | FileSystem: () => fileSystem, |
1003 | ProcessManager: () => FakeProcessManager.any(), |
1004 | Logger: () => logger, |
1005 | DeviceManager: () => testDeviceManager, |
1006 | }, |
1007 | ); |
1008 | |
1009 | testUsingContext( |
1010 | 'throws a ToolExit when value includes delimiter characters', |
1011 | () async { |
1012 | fileSystem.file('lib/main.dart').createSync(recursive: true); |
1013 | fileSystem.file('pubspec.yaml').createSync(); |
1014 | fileSystem.file('.dart_tool/package_config.json').createSync(recursive: true); |
1015 | |
1016 | final RunCommand command = RunCommand(); |
1017 | await expectLater( |
1018 | () => createTestCommandRunner(command).run(<String>[ |
1019 | 'run', |
1020 | '--no-pub', |
1021 | '--no-hot', |
1022 | '--web-header', |
1023 | 'hurray/headers=flutter', |
1024 | ]), |
1025 | throwsToolExit(), |
1026 | ); |
1027 | |
1028 | await expectLater( |
1029 | () => command.createDebuggingOptions(true), |
1030 | throwsToolExit(message: 'Invalid web headers: hurray/headers=flutter'), |
1031 | ); |
1032 | }, |
1033 | overrides: <Type, Generator>{ |
1034 | FileSystem: () => fileSystem, |
1035 | ProcessManager: () => FakeProcessManager.any(), |
1036 | Logger: () => logger, |
1037 | DeviceManager: () => testDeviceManager, |
1038 | }, |
1039 | ); |
1040 | |
1041 | testUsingContext( |
1042 | 'throws a ToolExit when using --wasm on a non-web platform', |
1043 | () async { |
1044 | final RunCommand command = RunCommand(); |
1045 | await expectLater( |
1046 | () => createTestCommandRunner(command).run(<String>['run', '--no-pub', '--wasm']), |
1047 | throwsToolExit(message: '--wasm is only supported on the web platform'), |
1048 | ); |
1049 | }, |
1050 | overrides: <Type, Generator>{ |
1051 | FileSystem: () => fileSystem, |
1052 | ProcessManager: () => FakeProcessManager.any(), |
1053 | Logger: () => logger, |
1054 | DeviceManager: () => testDeviceManager, |
1055 | }, |
1056 | ); |
1057 | |
1058 | testUsingContext( |
1059 | 'throws a ToolExit when using the skwasm renderer without --wasm', |
1060 | () async { |
1061 | final RunCommand command = RunCommand(); |
1062 | await expectLater( |
1063 | () => createTestCommandRunner( |
1064 | command, |
1065 | ).run(<String>['run', '--no-pub', ...WebRendererMode.skwasm.toCliDartDefines]), |
1066 | throwsToolExit(message: 'Skwasm renderer requires --wasm'), |
1067 | ); |
1068 | }, |
1069 | overrides: <Type, Generator>{ |
1070 | FileSystem: () => fileSystem, |
1071 | ProcessManager: () => FakeProcessManager.any(), |
1072 | Logger: () => logger, |
1073 | DeviceManager: () => testDeviceManager, |
1074 | }, |
1075 | ); |
1076 | |
1077 | testUsingContext( |
1078 | 'accepts headers with commas in them', |
1079 | () async { |
1080 | final RunCommand command = RunCommand(); |
1081 | await expectLater( |
1082 | () => createTestCommandRunner(command).run(<String>[ |
1083 | 'run', |
1084 | '--no-pub', |
1085 | '--no-hot', |
1086 | '--web-header', |
1087 | 'hurray=flutter,flutter=hurray', |
1088 | ]), |
1089 | throwsToolExit(), |
1090 | ); |
1091 | |
1092 | final DebuggingOptions options = await command.createDebuggingOptions(true); |
1093 | expect(options.webHeaders, <String, String>{'hurray': 'flutter,flutter=hurray'}); |
1094 | }, |
1095 | overrides: <Type, Generator>{ |
1096 | FileSystem: () => fileSystem, |
1097 | ProcessManager: () => FakeProcessManager.any(), |
1098 | Logger: () => logger, |
1099 | DeviceManager: () => testDeviceManager, |
1100 | }, |
1101 | ); |
1102 | }); |
1103 | }); |
1104 | |
1105 | group('terminal', () { |
1106 | late FakeAnsiTerminal fakeTerminal; |
1107 | |
1108 | setUp(() { |
1109 | fakeTerminal = FakeAnsiTerminal(); |
1110 | }); |
1111 | |
1112 | testUsingContext( |
1113 | 'Flutter run sets terminal singleCharMode to false on exit', |
1114 | () async { |
1115 | final FakeResidentRunner residentRunner = FakeResidentRunner(); |
1116 | final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner(); |
1117 | command.fakeResidentRunner = residentRunner; |
1118 | |
1119 | await createTestCommandRunner(command).run(<String>['run', '--no-pub']); |
1120 | // The sync completer where we initially set `terminal.singleCharMode` to |
1121 | // `true` does not execute in unit tests, so explicitly check the |
1122 | // `setSingleCharModeHistory` that the finally block ran, setting this |
1123 | // back to `false`. |
1124 | expect(fakeTerminal.setSingleCharModeHistory, contains(false)); |
1125 | }, |
1126 | overrides: <Type, Generator>{ |
1127 | AnsiTerminal: () => fakeTerminal, |
1128 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
1129 | FileSystem: () => MemoryFileSystem.test(), |
1130 | ProcessManager: () => FakeProcessManager.any(), |
1131 | }, |
1132 | ); |
1133 | |
1134 | testUsingContext( |
1135 | 'Flutter run catches StdinException while setting terminal singleCharMode to false', |
1136 | () async { |
1137 | fakeTerminal.hasStdin = false; |
1138 | final FakeResidentRunner residentRunner = FakeResidentRunner(); |
1139 | final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner(); |
1140 | command.fakeResidentRunner = residentRunner; |
1141 | |
1142 | try { |
1143 | await createTestCommandRunner(command).run(<String>['run', '--no-pub']); |
1144 | } catch (err) { |
1145 | fail('Expected no error, got$err '); |
1146 | } |
1147 | expect(fakeTerminal.setSingleCharModeHistory, isEmpty); |
1148 | }, |
1149 | overrides: <Type, Generator>{ |
1150 | AnsiTerminal: () => fakeTerminal, |
1151 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
1152 | FileSystem: () => MemoryFileSystem.test(), |
1153 | ProcessManager: () => FakeProcessManager.any(), |
1154 | }, |
1155 | ); |
1156 | }); |
1157 | |
1158 | testUsingContext( |
1159 | 'Flutter run catches catches errors due to vm service disconnection by text and throws a tool exit', |
1160 | () async { |
1161 | final FakeResidentRunner residentRunner = FakeResidentRunner(); |
1162 | residentRunner.rpcError = RPCError( |
1163 | 'flutter._listViews', |
1164 | RPCErrorKind.kServiceDisappeared.code, |
1165 | '', |
1166 | ); |
1167 | final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner(); |
1168 | command.fakeResidentRunner = residentRunner; |
1169 | |
1170 | await expectToolExitLater( |
1171 | createTestCommandRunner(command).run(<String>['run', '--no-pub']), |
1172 | contains('Lost connection to device.'), |
1173 | ); |
1174 | |
1175 | residentRunner.rpcError = RPCError( |
1176 | 'flutter._listViews', |
1177 | RPCErrorKind.kServerError.code, |
1178 | 'Service connection disposed.', |
1179 | ); |
1180 | |
1181 | await expectToolExitLater( |
1182 | createTestCommandRunner(command).run(<String>['run', '--no-pub']), |
1183 | contains('Lost connection to device.'), |
1184 | ); |
1185 | }, |
1186 | overrides: <Type, Generator>{ |
1187 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
1188 | FileSystem: () => MemoryFileSystem.test(), |
1189 | ProcessManager: () => FakeProcessManager.any(), |
1190 | }, |
1191 | ); |
1192 | |
1193 | testUsingContext( |
1194 | 'Flutter run catches catches errors due to vm service disconnection by code and throws a tool exit', |
1195 | () async { |
1196 | final FakeResidentRunner residentRunner = FakeResidentRunner(); |
1197 | residentRunner.rpcError = RPCError( |
1198 | 'flutter._listViews', |
1199 | RPCErrorKind.kServiceDisappeared.code, |
1200 | '', |
1201 | ); |
1202 | final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner(); |
1203 | command.fakeResidentRunner = residentRunner; |
1204 | |
1205 | await expectToolExitLater( |
1206 | createTestCommandRunner(command).run(<String>['run', '--no-pub']), |
1207 | contains('Lost connection to device.'), |
1208 | ); |
1209 | |
1210 | residentRunner.rpcError = RPCError( |
1211 | 'flutter._listViews', |
1212 | RPCErrorKind.kConnectionDisposed.code, |
1213 | 'dummy text not matched.', |
1214 | ); |
1215 | |
1216 | await expectToolExitLater( |
1217 | createTestCommandRunner(command).run(<String>['run', '--no-pub']), |
1218 | contains('Lost connection to device.'), |
1219 | ); |
1220 | }, |
1221 | overrides: <Type, Generator>{ |
1222 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
1223 | FileSystem: () => MemoryFileSystem.test(), |
1224 | ProcessManager: () => FakeProcessManager.any(), |
1225 | }, |
1226 | ); |
1227 | |
1228 | testUsingContext( |
1229 | 'Flutter run does not catch other RPC errors', |
1230 | () async { |
1231 | final FakeResidentRunner residentRunner = FakeResidentRunner(); |
1232 | residentRunner.rpcError = RPCError( |
1233 | 'flutter._listViews', |
1234 | RPCErrorKind.kInvalidParams.code, |
1235 | '', |
1236 | ); |
1237 | final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner(); |
1238 | command.fakeResidentRunner = residentRunner; |
1239 | |
1240 | await expectLater( |
1241 | () => createTestCommandRunner(command).run(<String>['run', '--no-pub']), |
1242 | throwsA(isA<RPCError>()), |
1243 | ); |
1244 | }, |
1245 | overrides: <Type, Generator>{ |
1246 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
1247 | FileSystem: () => MemoryFileSystem.test(), |
1248 | ProcessManager: () => FakeProcessManager.any(), |
1249 | }, |
1250 | ); |
1251 | |
1252 | testUsingContext( |
1253 | 'Configures web connection options to use web sockets by default', |
1254 | () async { |
1255 | final RunCommand command = RunCommand(); |
1256 | await expectLater( |
1257 | () => createTestCommandRunner(command).run(<String>['run', '--no-pub']), |
1258 | throwsToolExit(), |
1259 | ); |
1260 | |
1261 | final DebuggingOptions options = await command.createDebuggingOptions(true); |
1262 | |
1263 | expect(options.webUseSseForDebugBackend, false); |
1264 | expect(options.webUseSseForDebugProxy, false); |
1265 | expect(options.webUseSseForInjectedClient, false); |
1266 | }, |
1267 | overrides: <Type, Generator>{ |
1268 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
1269 | FileSystem: () => MemoryFileSystem.test(), |
1270 | ProcessManager: () => FakeProcessManager.any(), |
1271 | }, |
1272 | ); |
1273 | |
1274 | testUsingContext( |
1275 | 'flags propagate to debugging options', |
1276 | () async { |
1277 | final RunCommand command = RunCommand(); |
1278 | await expectLater( |
1279 | () => createTestCommandRunner(command).run(<String>[ |
1280 | 'run', |
1281 | '--start-paused', |
1282 | '--disable-service-auth-codes', |
1283 | '--use-test-fonts', |
1284 | '--trace-skia', |
1285 | '--trace-systrace', |
1286 | '--trace-to-file=path/to/trace.binpb', |
1287 | '--verbose-system-logs', |
1288 | '--native-null-assertions', |
1289 | '--enable-impeller', |
1290 | '--enable-vulkan-validation', |
1291 | '--trace-systrace', |
1292 | '--enable-software-rendering', |
1293 | '--skia-deterministic-rendering', |
1294 | '--enable-embedder-api', |
1295 | '--ci', |
1296 | '--debug-logs-dir=path/to/logs', |
1297 | ]), |
1298 | throwsToolExit(), |
1299 | ); |
1300 | |
1301 | final DebuggingOptions options = await command.createDebuggingOptions(false); |
1302 | |
1303 | expect(options.startPaused, true); |
1304 | expect(options.disableServiceAuthCodes, true); |
1305 | expect(options.useTestFonts, true); |
1306 | expect(options.traceSkia, true); |
1307 | expect(options.traceSystrace, true); |
1308 | expect(options.traceToFile, 'path/to/trace.binpb'); |
1309 | expect(options.verboseSystemLogs, true); |
1310 | expect(options.nativeNullAssertions, true); |
1311 | expect(options.traceSystrace, true); |
1312 | expect(options.enableImpeller, ImpellerStatus.enabled); |
1313 | expect(options.enableVulkanValidation, true); |
1314 | expect(options.enableSoftwareRendering, true); |
1315 | expect(options.skiaDeterministicRendering, true); |
1316 | expect(options.usingCISystem, true); |
1317 | expect(options.debugLogsDirectoryPath, 'path/to/logs'); |
1318 | }, |
1319 | overrides: <Type, Generator>{ |
1320 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
1321 | FileSystem: () => MemoryFileSystem.test(), |
1322 | ProcessManager: () => FakeProcessManager.any(), |
1323 | }, |
1324 | ); |
1325 | |
1326 | testUsingContext( |
1327 | 'usingCISystem can also be set by environment LUCI_CI', |
1328 | () async { |
1329 | final RunCommand command = RunCommand(); |
1330 | await expectLater( |
1331 | () => createTestCommandRunner(command).run(<String>['run']), |
1332 | throwsToolExit(), |
1333 | ); |
1334 | |
1335 | final DebuggingOptions options = await command.createDebuggingOptions(false); |
1336 | |
1337 | expect(options.usingCISystem, true); |
1338 | }, |
1339 | overrides: <Type, Generator>{ |
1340 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
1341 | FileSystem: () => MemoryFileSystem.test(), |
1342 | ProcessManager: () => FakeProcessManager.any(), |
1343 | Platform: () => FakePlatform(environment: <String, String>{'LUCI_CI': 'True'}), |
1344 | }, |
1345 | ); |
1346 | |
1347 | testUsingContext( |
1348 | 'wasm mode selects skwasm renderer by default', |
1349 | () async { |
1350 | final RunCommand command = RunCommand(); |
1351 | await expectLater( |
1352 | () => createTestCommandRunner(command).run(<String>['run', '-d chrome', '--wasm']), |
1353 | throwsToolExit(), |
1354 | ); |
1355 | |
1356 | final DebuggingOptions options = await command.createDebuggingOptions(false); |
1357 | |
1358 | expect(options.webUseWasm, true); |
1359 | expect(options.webRenderer, WebRendererMode.skwasm); |
1360 | }, |
1361 | overrides: <Type, Generator>{ |
1362 | Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
1363 | FileSystem: () => MemoryFileSystem.test(), |
1364 | ProcessManager: () => FakeProcessManager.any(), |
1365 | }, |
1366 | ); |
1367 | |
1368 | testUsingContext( |
1369 | 'fails when "--web-launch-url" is not supported', |
1370 | () async { |
1371 | final RunCommand command = RunCommand(); |
1372 | await expectLater( |
1373 | () => createTestCommandRunner( |
1374 | command, |
1375 | ).run(<String>['run', '--web-launch-url=http://flutter.dev']), |
1376 | throwsA( |
1377 | isException.having( |
1378 | (Exception exception) => exception.toString(), |
1379 | 'toString', |
1380 | isNot(contains('web-launch-url')), |
1381 | ), |
1382 | ), |
1383 | ); |
1384 | |
1385 | final DebuggingOptions options = await command.createDebuggingOptions(true); |
1386 | expect(options.webLaunchUrl,'http://flutter.dev'); |
1387 | |
1388 | final RegExp pattern = RegExp(r'^((http)?:\/\/)[^\s]+'); |
1389 | expect(pattern.hasMatch(options.webLaunchUrl!), true); |
1390 | }, |
1391 | overrides: <Type, Generator>{ |
1392 | ProcessManager: () => FakeProcessManager.any(), |
1393 | Logger: () => BufferLogger.test(), |
1394 | }, |
1395 | ); |
1396 | } |
1397 | |
1398 | class TestDeviceManager extends DeviceManager { |
1399 | TestDeviceManager({required super.logger}); |
1400 | List<Device> devices = <Device>[]; |
1401 | |
1402 | @override |
1403 | List<DeviceDiscovery> get deviceDiscoverers { |
1404 | final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery(); |
1405 | devices.forEach(discoverer.addDevice); |
1406 | return <DeviceDiscovery>[discoverer]; |
1407 | } |
1408 | } |
1409 | |
1410 | class FakeDevice extends Fake implements Device { |
1411 | FakeDevice({ |
1412 | bool isLocalEmulator = false, |
1413 | TargetPlatform targetPlatform = TargetPlatform.ios, |
1414 | String sdkNameAndVersion ='', |
1415 | PlatformType platformType = PlatformType.ios, |
1416 | bool isSupported = true, |
1417 | bool supportsFlavors = false, |
1418 | }) : _isLocalEmulator = isLocalEmulator, |
1419 | _targetPlatform = targetPlatform, |
1420 | _sdkNameAndVersion = sdkNameAndVersion, |
1421 | _platformType = platformType, |
1422 | _isSupported = isSupported, |
1423 | _supportsFlavors = supportsFlavors; |
1424 | |
1425 | static const int kSuccess = 1; |
1426 | static const int kFailure = -1; |
1427 | final TargetPlatform _targetPlatform; |
1428 | final bool _isLocalEmulator; |
1429 | final String _sdkNameAndVersion; |
1430 | final PlatformType _platformType; |
1431 | final bool _isSupported; |
1432 | final bool _supportsFlavors; |
1433 | |
1434 | @override |
1435 | Category get category => Category.mobile; |
1436 | |
1437 | @override |
1438 | String get id =>'fake_device'; |
1439 | |
1440 | Never _throwToolExit(int code) => throwToolExit('FakeDevice tool exit', exitCode: code); |
1441 | |
1442 | @override |
1443 | Future<bool> get isLocalEmulator => Future<bool>.value(_isLocalEmulator); |
1444 | |
1445 | @override |
1446 | bool supportsRuntimeMode(BuildMode mode) => true; |
1447 | |
1448 | @override |
1449 | Future<bool> get supportsHardwareRendering async => true; |
1450 | |
1451 | @override |
1452 | bool supportsHotReload = false; |
1453 | |
1454 | @override |
1455 | bool get supportsHotRestart => true; |
1456 | |
1457 | @override |
1458 | bool get supportsFastStart => false; |
1459 | |
1460 | @override |
1461 | bool get supportsFlavors => _supportsFlavors; |
1462 | |
1463 | @override |
1464 | bool get ephemeral => true; |
1465 | |
1466 | @override |
1467 | bool get isConnected => true; |
1468 | |
1469 | @override |
1470 | DeviceConnectionInterface get connectionInterface => DeviceConnectionInterface.attached; |
1471 | |
1472 | bool supported = true; |
1473 | |
1474 | @override |
1475 | bool isSupportedForProject(FlutterProject flutterProject) => _isSupported; |
1476 | |
1477 | @override |
1478 | bool isSupported() => supported; |
1479 | |
1480 | @override |
1481 | Future<String> get sdkNameAndVersion => Future<String>.value(_sdkNameAndVersion); |
1482 | |
1483 | @override |
1484 | Future<String> get targetPlatformDisplayName async => |
1485 | getNameForTargetPlatform(await targetPlatform); |
1486 | |
1487 | @override |
1488 | DeviceLogReader getLogReader({ApplicationPackage? app, bool includePastLogs = false}) { |
1489 | return FakeDeviceLogReader(); |
1490 | } |
1491 | |
1492 | @override |
1493 | String get name =>'FakeDevice'; |
1494 | |
1495 | @override |
1496 | String get displayName => name; |
1497 | |
1498 | @override |
1499 | Future<TargetPlatform> get targetPlatform async => _targetPlatform; |
1500 | |
1501 | @override |
1502 | PlatformType get platformType => _platformType; |
1503 | |
1504 | late bool startAppSuccess; |
1505 | |
1506 | @override |
1507 | DevFSWriter? createDevFSWriter(ApplicationPackage? app, String? userIdentifier) { |
1508 | return null; |
1509 | } |
1510 | |
1511 | @override |
1512 | Future<LaunchResult> startApp( |
1513 | ApplicationPackage? package, { |
1514 | String? mainPath, |
1515 | String? route, |
1516 | required DebuggingOptions debuggingOptions, |
1517 | Map<String, Object?> platformArgs = const <String, Object?>{}, |
1518 | bool prebuiltApplication = false, |
1519 | bool usesTerminalUi = true, |
1520 | bool ipv6 = false, |
1521 | String? userIdentifier, |
1522 | }) async { |
1523 | if (!startAppSuccess) { |
1524 | return LaunchResult.failed(); |
1525 | } |
1526 | if (startAppSuccess) { |
1527 | return LaunchResult.succeeded(); |
1528 | } |
1529 | final String dartFlags = debuggingOptions.dartFlags; |
1530 | // In release mode, --dart-flags should be set to the empty string and |
1531 | // provided flags should be dropped. In debug and profile modes, |
1532 | // --dart-flags should not be empty. |
1533 | if (debuggingOptions.buildInfo.isRelease) { |
1534 | if (dartFlags.isNotEmpty) { |
1535 | _throwToolExit(kFailure); |
1536 | } |
1537 | _throwToolExit(kSuccess); |
1538 | } else { |
1539 | if (dartFlags.isEmpty) { |
1540 | _throwToolExit(kFailure); |
1541 | } |
1542 | _throwToolExit(kSuccess); |
1543 | } |
1544 | } |
1545 | } |
1546 | |
1547 | class FakeIOSDevice extends Fake implements IOSDevice { |
1548 | FakeIOSDevice({ |
1549 | this.connectionInterface = DeviceConnectionInterface.attached, |
1550 | bool isLocalEmulator = false, |
1551 | String sdkNameAndVersion ='', |
1552 | }) : _isLocalEmulator = isLocalEmulator, |
1553 | _sdkNameAndVersion = sdkNameAndVersion; |
1554 | |
1555 | final bool _isLocalEmulator; |
1556 | final String _sdkNameAndVersion; |
1557 | |
1558 | @override |
1559 | Future<bool> get isLocalEmulator => Future<bool>.value(_isLocalEmulator); |
1560 | |
1561 | @override |
1562 | Future<String> get sdkNameAndVersion => Future<String>.value(_sdkNameAndVersion); |
1563 | |
1564 | @override |
1565 | final DeviceConnectionInterface connectionInterface; |
1566 | |
1567 | @override |
1568 | bool get isWirelesslyConnected => connectionInterface == DeviceConnectionInterface.wireless; |
1569 | |
1570 | @override |
1571 | Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios; |
1572 | } |
1573 | |
1574 | class TestRunCommandForUsageValues extends RunCommand { |
1575 | TestRunCommandForUsageValues({List<Device>? devices}) { |
1576 | this.devices = devices; |
1577 | } |
1578 | |
1579 | @override |
1580 | Future<BuildInfo> getBuildInfo({ |
1581 | FlutterProject? project, |
1582 | BuildMode? forcedBuildMode, |
1583 | File? forcedTargetFile, |
1584 | bool? forcedUseLocalCanvasKit, |
1585 | }) async { |
1586 | return const BuildInfo( |
1587 | BuildMode.debug, |
1588 | null, |
1589 | treeShakeIcons: false, |
1590 | packageConfigPath:'.dart_tool/package_config.json', |
1591 | ); |
1592 | } |
1593 | } |
1594 | |
1595 | class TestRunCommandWithFakeResidentRunner extends RunCommand { |
1596 | late FakeResidentRunner fakeResidentRunner; |
1597 | |
1598 | @override |
1599 | Future<ResidentRunner> createRunner({ |
1600 | required bool hotMode, |
1601 | required List<FlutterDevice> flutterDevices, |
1602 | required String? applicationBinaryPath, |
1603 | required FlutterProject flutterProject, |
1604 | }) async { |
1605 | return fakeResidentRunner; |
1606 | } |
1607 | |
1608 | @override |
1609 | // ignore: must_call_super |
1610 | Future<void> validateCommand() async { |
1611 | devices = <Device>[FakeDevice()..supportsHotReload = true]; |
1612 | } |
1613 | } |
1614 | |
1615 | class TestRunCommandThatOnlyValidates extends RunCommand { |
1616 | @override |
1617 | Future<FlutterCommandResult> runCommand() async { |
1618 | return FlutterCommandResult.success(); |
1619 | } |
1620 | |
1621 | @override |
1622 | bool get shouldRunPub => false; |
1623 | } |
1624 | |
1625 | class FakeResidentRunner extends Fake implements ResidentRunner { |
1626 | RPCError? rpcError; |
1627 | |
1628 | @override |
1629 | Future<int> run({ |
1630 | Completer<DebugConnectionInfo>? connectionInfoCompleter, |
1631 | Completer<void>? appStartedCompleter, |
1632 | bool enableDevTools = false, |
1633 | String? route, |
1634 | }) async { |
1635 | await null; |
1636 | if (rpcError != null) { |
1637 | throw rpcError!; |
1638 | } |
1639 | return 0; |
1640 | } |
1641 | } |
1642 | |
1643 | class DaemonCapturingRunCommand extends RunCommand { |
1644 | late Daemon daemon; |
1645 | late CapturingAppDomain appDomain; |
1646 | |
1647 | @override |
1648 | Daemon createMachineDaemon() { |
1649 | daemon = super.createMachineDaemon(); |
1650 | appDomain = daemon.appDomain = CapturingAppDomain(daemon); |
1651 | daemon.registerDomain(appDomain); |
1652 | return daemon; |
1653 | } |
1654 | } |
1655 | |
1656 | class CapturingAppDomain extends AppDomain { |
1657 | CapturingAppDomain(super.daemon); |
1658 | |
1659 | String? userIdentifier; |
1660 | bool? enableDevTools; |
1661 | |
1662 | @override |
1663 | Future<AppInstance> startApp( |
1664 | Device device, |
1665 | String projectDirectory, |
1666 | String target, |
1667 | String? route, |
1668 | DebuggingOptions options, |
1669 | bool enableHotReload, { |
1670 | File? applicationBinary, |
1671 | required bool trackWidgetCreation, |
1672 | String? projectRootPath, |
1673 | String? packagesFilePath, |
1674 | String? dillOutputPath, |
1675 | String? isolateFilter, |
1676 | bool machine = true, |
1677 | String? userIdentifier, |
1678 | }) async { |
1679 | this.userIdentifier = userIdentifier; |
1680 | enableDevTools = options.enableDevTools; |
1681 | throwToolExit(''); |
1682 | } |
1683 | } |
1684 | |
1685 | class FakeAnsiTerminal extends Fake implements AnsiTerminal { |
1686 | /// Setting to false will cause operations to Stdin to throw a [StdinException]. |
1687 | bool hasStdin = true; |
1688 | |
1689 | @override |
1690 | bool usesTerminalUi = false; |
1691 | |
1692 | /// A list of all the calls to the [singleCharMode] setter. |
1693 | List<bool> setSingleCharModeHistory = <bool>[]; |
1694 | |
1695 | @override |
1696 | set singleCharMode(bool value) { |
1697 | if (!hasStdin) { |
1698 | throw const StdinException( |
1699 | 'Error setting terminal line mode', |
1700 | OSError('The handle is invalid', 6), |
1701 | ); |
1702 | } |
1703 | setSingleCharModeHistory.add(value); |
1704 | } |
1705 | |
1706 | @override |
1707 | bool get singleCharMode => setSingleCharModeHistory.last; |
1708 | } |
1709 |
Definitions
- main
- TestDeviceManager
- TestDeviceManager
- deviceDiscoverers
- FakeDevice
- FakeDevice
- category
- id
- _throwToolExit
- isLocalEmulator
- supportsRuntimeMode
- supportsHardwareRendering
- supportsHotRestart
- supportsFastStart
- supportsFlavors
- ephemeral
- isConnected
- connectionInterface
- isSupportedForProject
- isSupported
- sdkNameAndVersion
- targetPlatformDisplayName
- getLogReader
- name
- displayName
- targetPlatform
- platformType
- createDevFSWriter
- startApp
- FakeIOSDevice
- FakeIOSDevice
- isLocalEmulator
- sdkNameAndVersion
- isWirelesslyConnected
- targetPlatform
- TestRunCommandForUsageValues
- TestRunCommandForUsageValues
- getBuildInfo
- TestRunCommandWithFakeResidentRunner
- createRunner
- validateCommand
- TestRunCommandThatOnlyValidates
- runCommand
- shouldRunPub
- FakeResidentRunner
- run
- DaemonCapturingRunCommand
- createMachineDaemon
- CapturingAppDomain
- CapturingAppDomain
- startApp
- FakeAnsiTerminal
- singleCharMode
Learn more about Flutter for embedded and desktop on industrialflutter.com