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 'dart:async';
6
7import 'package:args/command_runner.dart';
8import 'package:file/file.dart';
9import 'package:file/memory.dart';
10import 'package:flutter_tools/src/application_package.dart';
11import 'package:flutter_tools/src/artifacts.dart';
12import 'package:flutter_tools/src/base/common.dart';
13import 'package:flutter_tools/src/base/file_system.dart';
14import 'package:flutter_tools/src/base/io.dart';
15import 'package:flutter_tools/src/base/logger.dart';
16import 'package:flutter_tools/src/base/platform.dart';
17import 'package:flutter_tools/src/base/terminal.dart';
18import 'package:flutter_tools/src/build_info.dart';
19import 'package:flutter_tools/src/cache.dart';
20import 'package:flutter_tools/src/commands/daemon.dart';
21import 'package:flutter_tools/src/commands/run.dart';
22import 'package:flutter_tools/src/devfs.dart';
23import 'package:flutter_tools/src/device.dart';
24import 'package:flutter_tools/src/globals.dart' as globals;
25import 'package:flutter_tools/src/ios/devices.dart';
26import 'package:flutter_tools/src/project.dart';
27import 'package:flutter_tools/src/resident_runner.dart';
28import 'package:flutter_tools/src/runner/flutter_command.dart';
29import 'package:flutter_tools/src/web/compile.dart';
30import 'package:test/fake.dart';
31import 'package:unified_analytics/unified_analytics.dart' as analytics;
32import 'package:vm_service/vm_service.dart';
33
34import '../../src/common.dart';
35import '../../src/context.dart';
36import '../../src/fake_devices.dart';
37import '../../src/fakes.dart';
38import '../../src/package_config.dart';
39import '../../src/test_flutter_command_runner.dart';
40
41void 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
1398class 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
1410class 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
1547class 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
1574class 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
1595class 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
1615class 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
1625class 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
1643class 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
1656class 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
1685class 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

Provided by KDAB

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