run_test.dart 40.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:async';

7 8
import 'package:file/file.dart';
import 'package:file/memory.dart';
9 10
import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
11
import 'package:flutter_tools/src/application_package.dart';
12
import 'package:flutter_tools/src/artifacts.dart';
13
import 'package:flutter_tools/src/base/common.dart';
14
import 'package:flutter_tools/src/base/file_system.dart';
15
import 'package:flutter_tools/src/base/io.dart';
16
import 'package:flutter_tools/src/base/logger.dart';
17
import 'package:flutter_tools/src/base/platform.dart';
18
import 'package:flutter_tools/src/base/terminal.dart';
19
import 'package:flutter_tools/src/base/user_messages.dart';
20
import 'package:flutter_tools/src/build_info.dart';
21
import 'package:flutter_tools/src/cache.dart';
22
import 'package:flutter_tools/src/commands/daemon.dart';
23
import 'package:flutter_tools/src/commands/run.dart';
24
import 'package:flutter_tools/src/devfs.dart';
25
import 'package:flutter_tools/src/device.dart';
26
import 'package:flutter_tools/src/globals.dart' as globals;
27
import 'package:flutter_tools/src/project.dart';
28
import 'package:flutter_tools/src/reporting/reporting.dart';
29
import 'package:flutter_tools/src/resident_runner.dart';
30
import 'package:flutter_tools/src/runner/flutter_command.dart';
31
import 'package:flutter_tools/src/vmservice.dart';
32
import 'package:test/fake.dart';
33
import 'package:vm_service/vm_service.dart';
34

35 36
import '../../src/common.dart';
import '../../src/context.dart';
37
import '../../src/fake_devices.dart';
38
import '../../src/fakes.dart';
39
import '../../src/test_flutter_command_runner.dart';
40

41
void main() {
42 43 44 45
  setUpAll(() {
    Cache.disableLocking();
  });

46
  group('run', () {
47
    late TestDeviceManager testDeviceManager;
48
    late FileSystem fileSystem;
49

50
    setUp(() {
51
      testDeviceManager = TestDeviceManager(logger: BufferLogger.test());
52
      fileSystem = MemoryFileSystem.test();
53 54
    });

55
    testUsingContext('fails when target not found', () async {
56
      final RunCommand command = RunCommand();
57 58 59 60
      expect(
        () => createTestCommandRunner(command).run(<String>['run', '-t', 'abc123', '--no-pub']),
        throwsA(isA<ToolExit>().having((ToolExit error) => error.exitCode, 'exitCode', anyOf(isNull, 1))),
      );
61 62 63 64
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => FakeProcessManager.any(),
      Logger: () => BufferLogger.test(),
65
    });
66

67
    testUsingContext('does not support "--use-application-binary" and "--fast-start"', () async {
68 69 70
      fileSystem.file('lib/main.dart').createSync(recursive: true);
      fileSystem.file('pubspec.yaml').createSync();
      fileSystem.file('.packages').createSync();
71 72

      final RunCommand command = RunCommand();
73 74
      await expectLater(
        () => createTestCommandRunner(command).run(<String>[
75 76 77 78 79
          'run',
          '--use-application-binary=app/bar/faz',
          '--fast-start',
          '--no-pub',
          '--show-test-device',
80
        ]),
81
        throwsA(isException.having(
82 83 84 85 86
          (Exception exception) => exception.toString(),
          'toString',
          isNot(contains('--fast-start is not supported with --use-application-binary')),
        )),
      );
87
    }, overrides: <Type, Generator>{
88
      FileSystem: () => fileSystem,
89
      ProcessManager: () => FakeProcessManager.any(),
90
      Logger: () => BufferLogger.test(),
91 92
    });

93
    testUsingContext('Walks upward looking for a pubspec.yaml and succeeds if found', () async {
94 95 96 97 98 99
      fileSystem.file('pubspec.yaml').createSync();
      fileSystem.file('.packages')
        .writeAsStringSync('\n');
      fileSystem.file('lib/main.dart')
        .createSync(recursive: true);
      fileSystem.currentDirectory = fileSystem.directory('a/b/c')
100 101 102
        ..createSync(recursive: true);

      final RunCommand command = RunCommand();
103 104
      await expectLater(
        () => createTestCommandRunner(command).run(<String>[
105 106
          'run',
          '--no-pub',
107
        ]),
108
        throwsToolExit(),
109
      );
110
      final BufferLogger bufferLogger = globals.logger as BufferLogger;
111 112 113 114
      expect(
        bufferLogger.statusText,
        containsIgnoringWhitespace('Changing current working directory to:'),
      );
115
    }, overrides: <Type, Generator>{
116
      FileSystem: () => fileSystem,
117
      ProcessManager: () => FakeProcessManager.any(),
118
      Logger: () => BufferLogger.test(),
119 120 121
    });

    testUsingContext('Walks upward looking for a pubspec.yaml and exits if missing', () async {
122
      fileSystem.currentDirectory = fileSystem.directory('a/b/c')
123
        ..createSync(recursive: true);
124 125
      fileSystem.file('lib/main.dart')
        .createSync(recursive: true);
126 127

      final RunCommand command = RunCommand();
128 129
      await expectLater(
        () => createTestCommandRunner(command).run(<String>[
130 131
          'run',
          '--no-pub',
132
        ]),
133
        throwsToolExit(message: 'No pubspec.yaml file found'),
134
      );
135
    }, overrides: <Type, Generator>{
136
      FileSystem: () => fileSystem,
137
      ProcessManager: () => FakeProcessManager.any(),
138
      Logger: () => BufferLogger.test(),
139 140
    });

141
    group('run app', () {
142 143 144 145
      late MemoryFileSystem fs;
      late Artifacts artifacts;
      late TestUsage usage;
      late FakeAnsiTerminal fakeTerminal;
146 147 148 149

      setUpAll(() {
        Cache.disableLocking();
      });
150

151
      setUp(() {
152
        fakeTerminal = FakeAnsiTerminal();
153
        artifacts = Artifacts.test();
154
        usage = TestUsage();
155
        fs = MemoryFileSystem.test();
156

157
        fs.currentDirectory.childFile('pubspec.yaml')
158
          .writeAsStringSync('name: flutter_app');
159
        fs.currentDirectory.childFile('.packages')
160
          .writeAsStringSync('# Generated by pub on 2019-11-25 12:38:01.801784.');
161
        final Directory libDir = fs.currentDirectory.childDirectory('lib');
162 163 164 165 166
        libDir.createSync();
        final File mainFile = libDir.childFile('main.dart');
        mainFile.writeAsStringSync('void main() {}');
      });

167
      testUsingContext('exits with a user message when no supported devices attached', () async {
168
        final RunCommand command = RunCommand();
169
        testDeviceManager.devices = <Device>[];
170

171 172
        await expectLater(
          () => createTestCommandRunner(command).run(<String>[
173 174 175
            'run',
            '--no-pub',
            '--no-hot',
176 177 178
          ]),
          throwsA(isA<ToolExit>().having((ToolExit error) => error.message, 'message', isNull)),
        );
179

180 181 182 183
        expect(
          testLogger.statusText,
          containsIgnoringWhitespace(userMessages.flutterNoSupportedDevices),
        );
184
      }, overrides: <Type, Generator>{
185
        DeviceManager: () => testDeviceManager,
186
        FileSystem: () => fs,
187
        ProcessManager: () => FakeProcessManager.any(),
188
        Cache: () => Cache.test(processManager: FakeProcessManager.any()),
189 190
      });

191 192 193
      testUsingContext('exits and lists available devices when specified device not found', () async {
        final RunCommand command = RunCommand();
        final FakeDevice device = FakeDevice(isLocalEmulator: true);
194
        testDeviceManager
195
          ..devices = <Device>[device]
196
          ..specifiedDeviceId = 'invalid-device-id';
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211

        await expectLater(
              () => createTestCommandRunner(command).run(<String>[
            'run',
            '-d',
            'invalid-device-id',
            '--no-pub',
            '--no-hot',
          ]),
          throwsToolExit(),
        );
        expect(testLogger.statusText, contains("No supported devices found with name or id matching 'invalid-device-id'"));
        expect(testLogger.statusText, contains('The following devices were found:'));
        expect(testLogger.statusText, contains('FakeDevice (mobile) • fake_device • ios •  (simulator)'));
      }, overrides: <Type, Generator>{
212
        DeviceManager: () => testDeviceManager,
213 214 215 216 217
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
        Cache: () => Cache.test(processManager: FakeProcessManager.any()),
      });

218 219
      testUsingContext('fails when targeted device is not Android with --device-user', () async {
        final FakeDevice device = FakeDevice(isLocalEmulator: true);
220

221
        testDeviceManager.devices = <Device>[device];
222

223
        final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates();
224 225 226 227 228 229 230
        await expectLater(createTestCommandRunner(command).run(<String>[
          'run',
          '--no-pub',
          '--device-user',
          '10',
        ]), throwsToolExit(message: '--device-user is only supported for Android. At least one Android device is required.'));
      }, overrides: <Type, Generator>{
231
        FileSystem: () => fs,
232
        ProcessManager: () => FakeProcessManager.any(),
233
        DeviceManager: () => testDeviceManager,
234
        Stdio: () => FakeStdio(),
235
        Cache: () => Cache.test(processManager: FakeProcessManager.any()),
236 237
      });

238 239 240
      testUsingContext('succeeds when targeted device is an Android device with --device-user', () async {
        final FakeDevice device = FakeDevice(isLocalEmulator: true, platformType: PlatformType.android);

241
        testDeviceManager.devices = <Device>[device];
242 243 244 245 246 247 248 249 250 251 252 253

        final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates();
        await createTestCommandRunner(command).run(<String>[
          'run',
          '--no-pub',
          '--device-user',
          '10',
        ]);
        // Finishes normally without error.
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
254
        DeviceManager: () => testDeviceManager,
255 256 257 258
        Stdio: () => FakeStdio(),
        Cache: () => Cache.test(processManager: FakeProcessManager.any()),
      });

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
      testUsingContext('fails when v1 FlutterApplication is detected', () async {
        fs.file('pubspec.yaml').createSync();
        fs.file('android/AndroidManifest.xml')
          ..createSync(recursive: true)
          ..writeAsStringSync('''
          <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.example.v1">
             <application
                  android:name="io.flutter.app.FlutterApplication">
              </application>
          </manifest>
        ''', flush: true);
        fs.file('.packages').writeAsStringSync('\n');
        fs.file('lib/main.dart').createSync(recursive: true);
        final AndroidDevice device = AndroidDevice('1234',
          modelID: 'TestModel',
          logger: testLogger,
          platform: FakePlatform(),
          androidSdk: FakeAndroidSdk(),
          fileSystem: fs,
          processManager: FakeProcessManager.any(),
        );

282
        testDeviceManager.devices = <Device>[device];
283 284 285 286 287 288 289 290 291

        final RunCommand command = RunCommand();
        await expectLater(createTestCommandRunner(command).run(<String>[
          'run',
          '--pub',
        ]), throwsToolExit(message: 'Build failed due to use of deprecated Android v1 embedding.'));
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
292
        DeviceManager: () => testDeviceManager,
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
        Stdio: () => FakeStdio(),
        Cache: () => Cache.test(processManager: FakeProcessManager.any()),
      });

      testUsingContext('fails when v1 metadata is detected', () async {
        fs.file('pubspec.yaml').createSync();
        fs.file('android/AndroidManifest.xml')
          ..createSync(recursive: true)
          ..writeAsStringSync('''
          <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.example.v1">
              <application >
                <meta-data
                    android:name="flutterEmbedding"
                    android:value="1" />
              </application>
          </manifest>
        ''', flush: true);
        fs.file('.packages').writeAsStringSync('\n');
        fs.file('lib/main.dart').createSync(recursive: true);
        final AndroidDevice device = AndroidDevice('1234',
          modelID: 'TestModel',
          logger: testLogger,
          platform: FakePlatform(),
          androidSdk: FakeAndroidSdk(),
          fileSystem: fs,
          processManager: FakeProcessManager.any(),
        );

322
        testDeviceManager.devices = <Device>[device];
323 324 325 326 327 328 329 330 331

        final RunCommand command = RunCommand();
        await expectLater(createTestCommandRunner(command).run(<String>[
          'run',
          '--pub',
        ]), throwsToolExit(message: 'Build failed due to use of deprecated Android v1 embedding.'));
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
332
        DeviceManager: () => testDeviceManager,
333 334 335 336
        Stdio: () => FakeStdio(),
        Cache: () => Cache.test(processManager: FakeProcessManager.any()),
      });

337 338
      testUsingContext('shows unsupported devices when no supported devices are found',  () async {
        final RunCommand command = RunCommand();
339 340 341 342 343 344 345
        final FakeDevice mockDevice = FakeDevice(
          targetPlatform: TargetPlatform.android_arm,
          isLocalEmulator: true,
          sdkNameAndVersion: 'api-14',
          isSupported: false,
        );
        testDeviceManager.devices = <Device>[mockDevice];
346

347 348
        await expectLater(
          () => createTestCommandRunner(command).run(<String>[
349 350 351
            'run',
            '--no-pub',
            '--no-hot',
352 353 354
          ]),
          throwsA(isA<ToolExit>().having((ToolExit error) => error.message, 'message', isNull)),
        );
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372

        expect(
          testLogger.statusText,
          containsIgnoringWhitespace(userMessages.flutterNoSupportedDevices),
        );
        expect(
          testLogger.statusText,
          containsIgnoringWhitespace(userMessages.flutterFoundButUnsupportedDevices),
        );
        expect(
          testLogger.statusText,
          containsIgnoringWhitespace(
            userMessages.flutterMissPlatformProjects(
              Device.devicesPlatformTypes(<Device>[mockDevice]),
            ),
          ),
        );
      }, overrides: <Type, Generator>{
373
        DeviceManager: () => testDeviceManager,
374
        FileSystem: () => fs,
375
        ProcessManager: () => FakeProcessManager.any(),
376
        Cache: () => Cache.test(processManager: FakeProcessManager.any()),
377
      });
378

379 380 381 382 383 384
      testUsingContext('forwards --uninstall-only to DebuggingOptions', () async {
        final RunCommand command = RunCommand();
        final FakeDevice mockDevice = FakeDevice(
          sdkNameAndVersion: 'iOS 13',
        )..startAppSuccess = false;

385
        testDeviceManager.devices = <Device>[mockDevice];
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401

        // Causes swift to be detected in the analytics.
        fs.currentDirectory.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true);

        await expectToolExitLater(createTestCommandRunner(command).run(<String>[
          'run',
          '--no-pub',
          '--no-hot',
          '--uninstall-first',
        ]), isNull);

        final DebuggingOptions options = await command.createDebuggingOptions(false);
        expect(options.uninstallFirst, isTrue);
      }, overrides: <Type, Generator>{
        Artifacts: () => artifacts,
        Cache: () => Cache.test(processManager: FakeProcessManager.any()),
402
        DeviceManager: () => testDeviceManager,
403 404 405 406 407
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
        Usage: () => usage,
      });

408 409
      testUsingContext('passes device target platform to usage', () async {
        final RunCommand command = RunCommand();
410
        final FakeDevice mockDevice = FakeDevice(sdkNameAndVersion: 'iOS 13')
411
          ..startAppSuccess = false;
412

413
        testDeviceManager.devices = <Device>[mockDevice];
414

415 416
        // Causes swift to be detected in the analytics.
        fs.currentDirectory.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true);
417

418 419 420 421 422 423 424
        await expectToolExitLater(createTestCommandRunner(command).run(<String>[
          'run',
          '--no-pub',
          '--no-hot',
        ]), isNull);

        expect(usage.commands, contains(
425
          TestUsageCommand('run', parameters: CustomDimensions.fromMap(<String, String>{
426
            'cd3': 'false', 'cd4': 'ios', 'cd22': 'iOS 13',
427
            'cd23': 'debug', 'cd18': 'false', 'cd15': 'swift', 'cd31': 'true',
428
            'cd56': 'false',
429
          })
430
        )));
431
      }, overrides: <Type, Generator>{
432
        AnsiTerminal: () => fakeTerminal,
433
        Artifacts: () => artifacts,
434
        Cache: () => Cache.test(processManager: FakeProcessManager.any()),
435
        DeviceManager: () => testDeviceManager,
436
        FileSystem: () => fs,
437
        ProcessManager: () => FakeProcessManager.any(),
438
        Stdio: () => FakeStdio(),
439
        Usage: () => usage,
440
      });
441

442
      group('--machine', () {
443 444 445
        testUsingContext('enables multidex by default', () async {
          final DaemonCapturingRunCommand command = DaemonCapturingRunCommand();
          final FakeDevice device = FakeDevice();
446
          testDeviceManager.devices = <Device>[device];
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461

          await expectLater(
                () => createTestCommandRunner(command).run(<String>[
              'run',
              '--no-pub',
              '--machine',
              '-d',
              device.id,
            ]),
            throwsToolExit(),
          );
          expect(command.appDomain.multidexEnabled, isTrue);
        }, overrides: <Type, Generator>{
          Artifacts: () => artifacts,
          Cache: () => Cache.test(processManager: FakeProcessManager.any()),
462
          DeviceManager: () => testDeviceManager,
463 464 465 466 467 468 469 470 471 472
          FileSystem: () => fs,
          ProcessManager: () => FakeProcessManager.any(),
          Usage: () => usage,
          Stdio: () => FakeStdio(),
          Logger: () => AppRunLogger(parent: BufferLogger.test()),
        });

        testUsingContext('can disable multidex with --no-multidex', () async {
          final DaemonCapturingRunCommand command = DaemonCapturingRunCommand();
          final FakeDevice device = FakeDevice();
473
          testDeviceManager.devices = <Device>[device];
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489

          await expectLater(
                () => createTestCommandRunner(command).run(<String>[
              'run',
              '--no-pub',
              '--no-multidex',
              '--machine',
              '-d',
              device.id,
            ]),
            throwsToolExit(),
          );
          expect(command.appDomain.multidexEnabled, isFalse);
        }, overrides: <Type, Generator>{
          Artifacts: () => artifacts,
          Cache: () => Cache.test(processManager: FakeProcessManager.any()),
490
          DeviceManager: () => testDeviceManager,
491 492 493 494 495 496
          FileSystem: () => fs,
          ProcessManager: () => FakeProcessManager.any(),
          Usage: () => usage,
          Stdio: () => FakeStdio(),
          Logger: () => AppRunLogger(parent: BufferLogger.test()),
        });
497 498 499 500

        testUsingContext('can pass --device-user', () async {
          final DaemonCapturingRunCommand command = DaemonCapturingRunCommand();
          final FakeDevice device = FakeDevice(platformType: PlatformType.android);
501
          testDeviceManager.devices = <Device>[device];
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518

          await expectLater(
                () => createTestCommandRunner(command).run(<String>[
              'run',
              '--no-pub',
              '--machine',
              '--device-user',
              '10',
              '-d',
              device.id,
            ]),
            throwsToolExit(),
          );
          expect(command.appDomain.userIdentifier, '10');
        }, overrides: <Type, Generator>{
          Artifacts: () => artifacts,
          Cache: () => Cache.test(processManager: FakeProcessManager.any()),
519
          DeviceManager: () => testDeviceManager,
520 521 522 523 524 525
          FileSystem: () => fs,
          ProcessManager: () => FakeProcessManager.any(),
          Usage: () => usage,
          Stdio: () => FakeStdio(),
          Logger: () => AppRunLogger(parent: BufferLogger.test()),
        });
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553

        testUsingContext('can disable devtools with --no-devtools', () async {
          final DaemonCapturingRunCommand command = DaemonCapturingRunCommand();
          final FakeDevice device = FakeDevice();
          testDeviceManager.devices = <Device>[device];

          await expectLater(
                () => createTestCommandRunner(command).run(<String>[
              'run',
              '--no-pub',
              '--no-devtools',
              '--machine',
              '-d',
              device.id,
            ]),
            throwsToolExit(),
          );
          expect(command.appDomain.enableDevTools, isFalse);
        }, overrides: <Type, Generator>{
          Artifacts: () => artifacts,
          Cache: () => Cache.test(processManager: FakeProcessManager.any()),
          DeviceManager: () => testDeviceManager,
          FileSystem: () => fs,
          ProcessManager: () => FakeProcessManager.any(),
          Usage: () => usage,
          Stdio: () => FakeStdio(),
          Logger: () => AppRunLogger(parent: BufferLogger.test()),
        });
554
      });
555 556
    });

557
    group('Fatal Logs', () {
558 559
      late TestRunCommandWithFakeResidentRunner command;
      late MemoryFileSystem fs;
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625

      setUp(() {
        command = TestRunCommandWithFakeResidentRunner()
          ..fakeResidentRunner = FakeResidentRunner();
        fs = MemoryFileSystem.test();
      });

      testUsingContext("doesn't fail if --fatal-warnings specified and no warnings occur", () async {
        try {
          await createTestCommandRunner(command).run(<String>[
            'run',
            '--no-pub',
            '--no-hot',
            '--${FlutterOptions.kFatalWarnings}',
          ]);
        } on Exception {
          fail('Unexpected exception thrown');
        }
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
      });

      testUsingContext("doesn't fail if --fatal-warnings not specified", () async {
        testLogger.printWarning('Warning: Mild annoyance Will Robinson!');
        try {
          await createTestCommandRunner(command).run(<String>[
            'run',
            '--no-pub',
            '--no-hot',
          ]);
        } on Exception {
          fail('Unexpected exception thrown');
        }
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
      });

      testUsingContext('fails if --fatal-warnings specified and warnings emitted', () async {
        testLogger.printWarning('Warning: Mild annoyance Will Robinson!');
        await expectLater(createTestCommandRunner(command).run(<String>[
          'run',
          '--no-pub',
          '--no-hot',
          '--${FlutterOptions.kFatalWarnings}',
        ]), throwsToolExit(message: 'Logger received warning output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.'));
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
      });

      testUsingContext('fails if --fatal-warnings specified and errors emitted', () async {
        testLogger.printError('Error: Danger Will Robinson!');
        await expectLater(createTestCommandRunner(command).run(<String>[
          'run',
          '--no-pub',
          '--no-hot',
          '--${FlutterOptions.kFatalWarnings}',
        ]), throwsToolExit(message: 'Logger received error output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.'));
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
      });
    });

626
    testUsingContext('should only request artifacts corresponding to connected devices', () async {
627
      testDeviceManager.devices = <Device>[FakeDevice(targetPlatform: TargetPlatform.android_arm)];
628 629 630

      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
        DevelopmentArtifact.universal,
631
        DevelopmentArtifact.androidGenSnapshot,
632 633
      }));

634
      testDeviceManager.devices = <Device>[FakeDevice()];
635 636 637 638 639 640

      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
        DevelopmentArtifact.universal,
        DevelopmentArtifact.iOS,
      }));

641
      testDeviceManager.devices = <Device>[
642
        FakeDevice(),
643 644
        FakeDevice(targetPlatform: TargetPlatform.android_arm),
      ];
645 646 647 648

      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
        DevelopmentArtifact.universal,
        DevelopmentArtifact.iOS,
649
        DevelopmentArtifact.androidGenSnapshot,
650 651
      }));

652
      testDeviceManager.devices = <Device>[
653 654
        FakeDevice(targetPlatform: TargetPlatform.web_javascript),
      ];
655 656 657 658 659 660

      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
        DevelopmentArtifact.universal,
        DevelopmentArtifact.web,
      }));
    }, overrides: <Type, Generator>{
661
      DeviceManager: () => testDeviceManager,
662 663 664
      Cache: () => Cache.test(processManager: FakeProcessManager.any()),
      FileSystem: () => MemoryFileSystem.test(),
      ProcessManager: () => FakeProcessManager.any(),
665
    });
666
  });
667 668

  group('dart-defines and web-renderer options', () {
669
    late List<String> dartDefines;
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707

    setUp(() {
      dartDefines = <String>[];
    });

    test('auto web-renderer with no dart-defines', () {
      dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'auto');
      expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=true']);
    });

    test('canvaskit web-renderer with no dart-defines', () {
      dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'canvaskit');
      expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=true']);
    });

    test('html web-renderer with no dart-defines', () {
      dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'html');
      expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']);
    });

    test('auto web-renderer with existing dart-defines', () {
      dartDefines = <String>['FLUTTER_WEB_USE_SKIA=false'];
      dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'auto');
      expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=true']);
    });

    test('canvaskit web-renderer with no dart-defines', () {
      dartDefines = <String>['FLUTTER_WEB_USE_SKIA=false'];
      dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'canvaskit');
      expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=true']);
    });

    test('html web-renderer with no dart-defines', () {
      dartDefines = <String>['FLUTTER_WEB_USE_SKIA=true'];
      dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'html');
      expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']);
    });
  });
708

709
  group('terminal', () {
710
    late FakeAnsiTerminal fakeTerminal;
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735

    setUp(() {
      fakeTerminal = FakeAnsiTerminal();
    });

    testUsingContext('Flutter run sets terminal singleCharMode to false on exit', () async {
      final FakeResidentRunner residentRunner = FakeResidentRunner();
      final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner();
      command.fakeResidentRunner = residentRunner;

      await createTestCommandRunner(command).run(<String>[
        'run',
        '--no-pub',
      ]);
      // The sync completer where we initially set `terminal.singleCharMode` to
      // `true` does not execute in unit tests, so explicitly check the
      // `setSingleCharModeHistory` that the finally block ran, setting this
      // back to `false`.
      expect(fakeTerminal.setSingleCharModeHistory, contains(false));
    }, overrides: <Type, Generator>{
      AnsiTerminal: () => fakeTerminal,
      Cache: () => Cache.test(processManager: FakeProcessManager.any()),
      FileSystem: () => MemoryFileSystem.test(),
      ProcessManager: () => FakeProcessManager.any(),
    });
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757

    testUsingContext('Flutter run catches StdinException while setting terminal singleCharMode to false', () async {
      fakeTerminal.hasStdin = false;
      final FakeResidentRunner residentRunner = FakeResidentRunner();
      final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner();
      command.fakeResidentRunner = residentRunner;

      try {
        await createTestCommandRunner(command).run(<String>[
          'run',
          '--no-pub',
        ]);
      } catch (err) { // ignore: avoid_catches_without_on_clauses
        fail('Expected no error, got $err');
      }
      expect(fakeTerminal.setSingleCharModeHistory, isEmpty);
    }, overrides: <Type, Generator>{
      AnsiTerminal: () => fakeTerminal,
      Cache: () => Cache.test(processManager: FakeProcessManager.any()),
      FileSystem: () => MemoryFileSystem.test(),
      ProcessManager: () => FakeProcessManager.any(),
    });
758 759
  });

760 761 762 763 764 765 766 767 768 769
  testUsingContext('Flutter run catches service has disappear errors and throws a tool exit', () async {
    final FakeResidentRunner residentRunner = FakeResidentRunner();
    residentRunner.rpcError = RPCError('flutter._listViews', RPCErrorCodes.kServiceDisappeared, '');
    final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner();
    command.fakeResidentRunner = residentRunner;

    await expectToolExitLater(createTestCommandRunner(command).run(<String>[
      'run',
      '--no-pub',
    ]), contains('Lost connection to device.'));
770 771 772 773
  }, overrides: <Type, Generator>{
    Cache: () => Cache.test(processManager: FakeProcessManager.any()),
    FileSystem: () => MemoryFileSystem.test(),
    ProcessManager: () => FakeProcessManager.any(),
774 775 776 777 778 779 780 781 782 783 784 785
  });

  testUsingContext('Flutter run does not catch other RPC errors', () async {
    final FakeResidentRunner residentRunner = FakeResidentRunner();
    residentRunner.rpcError = RPCError('flutter._listViews', RPCErrorCodes.kInvalidParams, '');
    final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner();
    command.fakeResidentRunner = residentRunner;

    await expectLater(() => createTestCommandRunner(command).run(<String>[
      'run',
      '--no-pub',
    ]), throwsA(isA<RPCError>()));
786 787 788 789
  }, overrides: <Type, Generator>{
    Cache: () => Cache.test(processManager: FakeProcessManager.any()),
    FileSystem: () => MemoryFileSystem.test(),
    ProcessManager: () => FakeProcessManager.any(),
790
  });
791 792 793 794 795 796 797 798

  testUsingContext('Passes sksl bundle info the build options', () async {
    final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner();

    await expectLater(() => createTestCommandRunner(command).run(<String>[
      'run',
      '--no-pub',
      '--bundle-sksl-path=foo.json',
799
    ]), throwsToolExit(message: 'No SkSL shader bundle found at foo.json'));
800 801 802 803
  }, overrides: <Type, Generator>{
    Cache: () => Cache.test(processManager: FakeProcessManager.any()),
    FileSystem: () => MemoryFileSystem.test(),
    ProcessManager: () => FakeProcessManager.any(),
804
  });
805 806 807 808 809 810 811 812 813 814 815 816 817

  testUsingContext('Configures web connection options to use web sockets by default', () async {
    final RunCommand command = RunCommand();
    await expectLater(() => createTestCommandRunner(command).run(<String>[
      'run',
      '--no-pub',
    ]), throwsToolExit());

    final DebuggingOptions options = await command.createDebuggingOptions(true);

    expect(options.webUseSseForDebugBackend, false);
    expect(options.webUseSseForDebugProxy, false);
    expect(options.webUseSseForInjectedClient, false);
818 819 820 821
  }, overrides: <Type, Generator>{
    Cache: () => Cache.test(processManager: FakeProcessManager.any()),
    FileSystem: () => MemoryFileSystem.test(),
    ProcessManager: () => FakeProcessManager.any(),
822
  });
823

824
  testUsingContext('flags propagate to debugging options', () async {
825 826 827
    final RunCommand command = RunCommand();
    await expectLater(() => createTestCommandRunner(command).run(<String>[
      'run',
828 829 830 831 832 833 834 835
      '--start-paused',
      '--disable-service-auth-codes',
      '--use-test-fonts',
      '--trace-skia',
      '--trace-systrace',
      '--verbose-system-logs',
      '--null-assertions',
      '--native-null-assertions',
836
      '--enable-impeller',
837
      '--trace-systrace',
838 839
      '--enable-software-rendering',
      '--skia-deterministic-rendering',
840 841 842 843
    ]), throwsToolExit());

    final DebuggingOptions options = await command.createDebuggingOptions(false);

844 845 846 847 848 849 850 851
    expect(options.startPaused, true);
    expect(options.disableServiceAuthCodes, true);
    expect(options.useTestFonts, true);
    expect(options.traceSkia, true);
    expect(options.traceSystrace, true);
    expect(options.verboseSystemLogs, true);
    expect(options.nullAssertions, true);
    expect(options.nativeNullAssertions, true);
852
    expect(options.traceSystrace, true);
853
    expect(options.enableImpeller, true);
854 855
    expect(options.enableSoftwareRendering, true);
    expect(options.skiaDeterministicRendering, true);
856 857 858 859 860 861
  }, overrides: <Type, Generator>{
    Cache: () => Cache.test(processManager: FakeProcessManager.any()),
    FileSystem: () => MemoryFileSystem.test(),
    ProcessManager: () => FakeProcessManager.any(),
  });

862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
  testUsingContext('fails when "--web-launch-url" is not supported', () async {
    final RunCommand command = RunCommand();
    await expectLater(
          () => createTestCommandRunner(command).run(<String>[
        'run',
        '--web-launch-url=http://flutter.dev',
      ]),
      throwsA(isException.having(
            (Exception exception) => exception.toString(),
        'toString',
        isNot(contains('web-launch-url')),
      )),
    );

    final DebuggingOptions options = await command.createDebuggingOptions(true);
    expect(options.webLaunchUrl, 'http://flutter.dev');

    final RegExp pattern = RegExp(r'^((http)?:\/\/)[^\s]+');
880
    expect(pattern.hasMatch(options.webLaunchUrl!), true);
881 882 883 884
  }, overrides: <Type, Generator>{
    ProcessManager: () => FakeProcessManager.any(),
    Logger: () => BufferLogger.test(),
  });
885
}
886

887 888
class TestDeviceManager extends DeviceManager {
  TestDeviceManager({required this.logger}) : super(logger: logger);
889
  List<Device> devices = <Device>[];
890

891
  final Logger logger;
892

893 894 895 896 897 898
  @override
  List<DeviceDiscovery> get deviceDiscoverers {
    final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
    devices.forEach(discoverer.addDevice);
    return <DeviceDiscovery>[discoverer];
  }
Dan Field's avatar
Dan Field committed
899
}
900

901 902 903 904
class FakeAndroidSdk extends Fake implements AndroidSdk {
  @override
  String get adbPath => 'adb';
}
905

906 907 908
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
909
class FakeDevice extends Fake implements Device {
910 911 912 913 914
  FakeDevice({
    bool isLocalEmulator = false,
    TargetPlatform targetPlatform = TargetPlatform.ios,
    String sdkNameAndVersion = '',
    PlatformType platformType = PlatformType.ios,
915
    bool isSupported = true,
916 917 918
  }): _isLocalEmulator = isLocalEmulator,
      _targetPlatform = targetPlatform,
      _sdkNameAndVersion = sdkNameAndVersion,
919 920
      _platformType = platformType,
      _isSupported = isSupported;
921

922 923
  static const int kSuccess = 1;
  static const int kFailure = -1;
924
  final TargetPlatform _targetPlatform;
925
  final bool _isLocalEmulator;
926
  final String _sdkNameAndVersion;
927
  final PlatformType _platformType;
928
  final bool _isSupported;
929 930 931

  @override
  Category get category => Category.mobile;
932

933 934 935
  @override
  String get id => 'fake_device';

936
  Never _throwToolExit(int code) => throwToolExit('FakeDevice tool exit', exitCode: code);
937 938

  @override
939
  Future<bool> get isLocalEmulator => Future<bool>.value(_isLocalEmulator);
940

941 942 943
  @override
  bool supportsRuntimeMode(BuildMode mode) => true;

944 945 946
  @override
  Future<bool> get supportsHardwareRendering async => true;

947
  @override
948
  bool supportsHotReload = false;
949

950 951 952
  @override
  bool get supportsHotRestart => true;

953 954 955
  @override
  bool get supportsFastStart => false;

956 957 958
  bool supported = true;

  @override
959
  bool isSupportedForProject(FlutterProject flutterProject) => _isSupported;
960 961 962 963 964 965 966

  @override
  bool isSupported() => supported;

  @override
  Future<String> get sdkNameAndVersion => Future<String>.value(_sdkNameAndVersion);

967
  @override
968 969
  Future<String> get targetPlatformDisplayName async =>
      getNameForTargetPlatform(await targetPlatform);
970 971

  @override
972
  DeviceLogReader getLogReader({
973
    ApplicationPackage? app,
974 975
    bool includePastLogs = false,
  }) {
976
    return FakeDeviceLogReader();
977 978 979 980 981 982 983 984
  }

  @override
  String get name => 'FakeDevice';

  @override
  Future<TargetPlatform> get targetPlatform async => _targetPlatform;

985
  @override
986
  PlatformType get platformType => _platformType;
987

988
  late bool startAppSuccess;
989 990

  @override
991 992 993
  DevFSWriter? createDevFSWriter(
    covariant ApplicationPackage? app,
    String? userIdentifier,
994 995 996 997
  ) {
    return null;
  }

998 999
  @override
  Future<LaunchResult> startApp(
1000 1001 1002 1003 1004
    ApplicationPackage? package, {
    String? mainPath,
    String? route,
    required DebuggingOptions debuggingOptions,
    Map<String, Object?> platformArgs = const <String, Object?>{},
1005 1006 1007
    bool prebuiltApplication = false,
    bool usesTerminalUi = true,
    bool ipv6 = false,
1008
    String? userIdentifier,
1009
  }) async {
1010
    if (startAppSuccess == false) {
1011 1012
      return LaunchResult.failed();
    }
1013 1014 1015
    if (startAppSuccess == true) {
      return LaunchResult.succeeded();
    }
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
    final String dartFlags = debuggingOptions.dartFlags;
    // In release mode, --dart-flags should be set to the empty string and
    // provided flags should be dropped. In debug and profile modes,
    // --dart-flags should not be empty.
    if (debuggingOptions.buildInfo.isRelease) {
      if (dartFlags.isNotEmpty) {
        _throwToolExit(kFailure);
      }
      _throwToolExit(kSuccess);
    } else {
      if (dartFlags.isEmpty) {
        _throwToolExit(kFailure);
      }
      _throwToolExit(kSuccess);
    }
  }
}
1033

1034
class TestRunCommandWithFakeResidentRunner extends RunCommand {
1035
  late FakeResidentRunner fakeResidentRunner;
1036 1037 1038

  @override
  Future<ResidentRunner> createRunner({
1039 1040 1041 1042
    required bool hotMode,
    required List<FlutterDevice> flutterDevices,
    required String? applicationBinaryPath,
    required FlutterProject flutterProject,
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
  }) async {
    return fakeResidentRunner;
  }

  @override
  // ignore: must_call_super
  Future<void> validateCommand() async {
    devices = <Device>[FakeDevice()..supportsHotReload = true];
  }
}

1054 1055 1056 1057 1058 1059 1060
class TestRunCommandThatOnlyValidates extends RunCommand {
  @override
  Future<FlutterCommandResult> runCommand() async {
    return FlutterCommandResult.success();
  }
}

1061
class FakeResidentRunner extends Fake implements ResidentRunner {
1062
  RPCError? rpcError;
1063 1064 1065

  @override
  Future<int> run({
1066 1067
    Completer<DebugConnectionInfo>? connectionInfoCompleter,
    Completer<void>? appStartedCompleter,
1068
    bool enableDevTools = false,
1069
    String? route,
1070 1071 1072
  }) async {
    await null;
    if (rpcError != null) {
1073
      throw rpcError!;
1074 1075 1076 1077
    }
    return 0;
  }
}
1078 1079

class DaemonCapturingRunCommand extends RunCommand {
1080 1081
  late Daemon daemon;
  late CapturingAppDomain appDomain;
1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092

  @override
  Daemon createMachineDaemon() {
    daemon = super.createMachineDaemon();
    appDomain = daemon.appDomain = CapturingAppDomain(daemon);
    daemon.registerDomain(appDomain);
    return daemon;
  }
}

class CapturingAppDomain extends AppDomain {
1093
  CapturingAppDomain(super.daemon);
1094

1095 1096
  bool? multidexEnabled;
  String? userIdentifier;
1097
  bool? enableDevTools;
1098 1099 1100 1101 1102 1103

  @override
  Future<AppInstance> startApp(
    Device device,
    String projectDirectory,
    String target,
1104
    String? route,
1105 1106
    DebuggingOptions options,
    bool enableHotReload, {
1107 1108 1109 1110 1111
    File? applicationBinary,
    required bool trackWidgetCreation,
    String? projectRootPath,
    String? packagesFilePath,
    String? dillOutputPath,
1112 1113
    bool ipv6 = false,
    bool multidexEnabled = false,
1114
    String? isolateFilter,
1115
    bool machine = true,
1116
    String? userIdentifier,
1117
    bool enableDevTools = true,
1118 1119
  }) async {
    this.multidexEnabled = multidexEnabled;
1120
    this.userIdentifier = userIdentifier;
1121
    this.enableDevTools = enableDevTools;
1122 1123 1124
    throwToolExit('');
  }
}
1125 1126

class FakeAnsiTerminal extends Fake implements AnsiTerminal {
1127 1128 1129
  /// Setting to false will cause operations to Stdin to throw a [StdinException].
  bool hasStdin = true;

1130 1131 1132 1133 1134 1135 1136
  @override
  bool usesTerminalUi = false;

  /// A list of all the calls to the [singleCharMode] setter.
  List<bool> setSingleCharModeHistory = <bool>[];

  @override
1137 1138 1139 1140 1141 1142
  set singleCharMode(bool value) {
    if (!hasStdin) {
      throw const StdinException('Error setting terminal line mode', OSError('The handle is invalid', 6));
    }
    setSingleCharModeHistory.add(value);
  }
1143 1144 1145 1146

  @override
  bool get singleCharMode => setSingleCharModeHistory.last;
}