flutter_command_test.dart 25.8 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
// @dart = 2.8

7 8 9
import 'dart:async';
import 'dart:io' as io;

10
import 'package:args/command_runner.dart';
11
import 'package:file/memory.dart';
12
import 'package:flutter_tools/src/base/common.dart';
13
import 'package:flutter_tools/src/base/error_handling_io.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/signals.dart';
17
import 'package:flutter_tools/src/base/time.dart';
18
import 'package:flutter_tools/src/build_info.dart';
19
import 'package:flutter_tools/src/cache.dart';
20
import 'package:flutter_tools/src/dart/pub.dart';
21
import 'package:flutter_tools/src/globals.dart' as globals;
22
import 'package:flutter_tools/src/pre_run_validator.dart';
23
import 'package:flutter_tools/src/reporting/reporting.dart';
24
import 'package:flutter_tools/src/runner/flutter_command.dart';
25
import 'package:test/fake.dart';
26

27 28
import '../../src/common.dart';
import '../../src/context.dart';
29
import '../../src/test_flutter_command_runner.dart';
30
import 'utils.dart';
31

32
void main() {
33
  group('Flutter Command', () {
34
    FakeCache cache;
35
    TestUsage usage;
36
    FakeClock clock;
37
    FakeProcessInfo processInfo;
38 39 40 41 42 43 44
    MemoryFileSystem fileSystem;
    FakeProcessManager processManager;
    PreRunValidator preRunValidator;

    setUpAll(() {
      Cache.flutterRoot = '/path/to/sdk/flutter';
    });
45 46

    setUp(() {
47
      Cache.disableLocking();
48
      cache = FakeCache();
49
      usage = TestUsage();
50
      clock = FakeClock();
51 52
      processInfo = FakeProcessInfo();
      processInfo.maxRss = 10;
53 54 55
      fileSystem = MemoryFileSystem.test();
      processManager = FakeProcessManager.empty();
      preRunValidator = PreRunValidator(fileSystem: fileSystem);
56 57
    });

58 59 60 61
    tearDown(() {
      Cache.enableLocking();
    });

62
    testUsingContext('help text contains global options', () {
63
      final FakeDeprecatedCommand fake = FakeDeprecatedCommand();
64 65 66 67
      createTestCommandRunner(fake);
      expect(fake.usage, contains('Global options:\n'));
    });

68
    testUsingContext('honors shouldUpdateCache false', () async {
69
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
70
      await flutterCommand.run();
71 72

      expect(cache.artifacts, isEmpty);
73 74
      expect(flutterCommand.deprecated, isFalse);
      expect(flutterCommand.hidden, isFalse);
75 76
    },
    overrides: <Type, Generator>{
77 78
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
79 80 81 82
      Cache: () => cache,
    });

    testUsingContext('honors shouldUpdateCache true', () async {
83
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(shouldUpdateCache: true);
84
      await flutterCommand.run();
85 86
      // First call for universal, second for the rest
      expect(
87
        cache.artifacts,
88 89 90 91 92
        <Set<DevelopmentArtifact>>[
          <DevelopmentArtifact>{DevelopmentArtifact.universal},
          <DevelopmentArtifact>{},
        ],
      );
93
    },
94 95 96 97 98 99 100 101 102 103 104 105 106
    overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
      Cache: () => cache,
    });

    testUsingContext("throws toolExit if flutter_tools source dir doesn't exist", () async {
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
      await expectToolExitLater(
        flutterCommand.run(),
        contains('Flutter SDK installation appears corrupted'),
      );
    },
107 108
    overrides: <Type, Generator>{
      Cache: () => cache,
109 110 111
      FileSystem: () => fileSystem,
      PreRunValidator: () => preRunValidator,
      ProcessManager: () => processManager,
112
    });
113

114 115 116 117 118
    testUsingContext('deprecated command should warn', () async {
      final FakeDeprecatedCommand flutterCommand = FakeDeprecatedCommand();
      final CommandRunner<void> runner = createTestCommandRunner(flutterCommand);
      await runner.run(<String>['deprecated']);

119
      expect(testLogger.warningText,
120 121 122 123 124 125 126
        contains('The "deprecated" command is deprecated and will be removed in '
            'a future version of Flutter.'));
      expect(flutterCommand.usage,
        contains('Deprecated. This command will be removed in a future version '
            'of Flutter.'));
      expect(flutterCommand.deprecated, isTrue);
      expect(flutterCommand.hidden, isTrue);
127 128 129
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
130 131
    });

132 133 134
    testUsingContext('uses the error handling file system', () async {
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
        commandFunction: () async {
135
          expect(globals.fs, isA<ErrorHandlingFileSystem>());
136 137 138 139
          return const FlutterCommandResult(ExitStatus.success);
        }
      );
      await flutterCommand.run();
140 141 142
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
143 144
    });

145 146 147 148 149 150 151 152
    testUsingContext('finds the target file with default values', () async {
      globals.fs.file('lib/main.dart').createSync(recursive: true);
      final FakeTargetCommand fakeTargetCommand = FakeTargetCommand();
      final CommandRunner<void> runner = createTestCommandRunner(fakeTargetCommand);
      await runner.run(<String>['test']);

      expect(fakeTargetCommand.cachedTargetFile, 'lib/main.dart');
    }, overrides: <Type, Generator>{
153 154
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
155 156 157 158 159 160 161 162 163 164
    });

    testUsingContext('finds the target file with specified value', () async {
      globals.fs.file('lib/foo.dart').createSync(recursive: true);
      final FakeTargetCommand fakeTargetCommand = FakeTargetCommand();
      final CommandRunner<void> runner = createTestCommandRunner(fakeTargetCommand);
      await runner.run(<String>['test', '-t', 'lib/foo.dart']);

      expect(fakeTargetCommand.cachedTargetFile, 'lib/foo.dart');
    }, overrides: <Type, Generator>{
165 166
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
167 168 169 170 171 172
    });

    testUsingContext('throws tool exit if specified file does not exist', () async {
      final FakeTargetCommand fakeTargetCommand = FakeTargetCommand();
      final CommandRunner<void> runner = createTestCommandRunner(fakeTargetCommand);

173
      expect(() async => runner.run(<String>['test', '-t', 'lib/foo.dart']), throwsToolExit());
174
    }, overrides: <Type, Generator>{
175 176
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
177 178
    });

179
    void testUsingCommandContext(String testName, dynamic Function() testBody) {
180
      testUsingContext(testName, testBody, overrides: <Type, Generator>{
181
        FileSystem: () => fileSystem,
182
        ProcessInfo: () => processInfo,
183
        ProcessManager: () => processManager,
184 185 186 187 188 189
        SystemClock: () => clock,
        Usage: () => usage,
      });
    }

    testUsingCommandContext('reports command that results in success', () async {
190
      // Crash if called a third time which is unexpected.
191
      clock.times = <int>[1000, 2000];
192 193 194 195 196 197 198 199

      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
        commandFunction: () async {
          return const FlutterCommandResult(ExitStatus.success);
        }
      );
      await flutterCommand.run();

200 201 202 203 204 205 206
      expect(usage.events, <TestUsageEvent>[
        const TestUsageEvent(
          'tool-command-result',
          'dummy',
          label: 'success',
        ),
        const TestUsageEvent(
207 208 209
          'tool-command-max-rss',
          'dummy',
          label: 'success',
210 211 212
          value: 10,
        ),
      ]);
213 214
    });

215
    testUsingCommandContext('reports command that results in warning', () async {
216
      // Crash if called a third time which is unexpected.
217
      clock.times = <int>[1000, 2000];
218 219 220 221 222 223 224 225

      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
        commandFunction: () async {
          return const FlutterCommandResult(ExitStatus.warning);
        }
      );
      await flutterCommand.run();

226 227 228 229 230 231 232
      expect(usage.events, <TestUsageEvent>[
        const TestUsageEvent(
          'tool-command-result',
          'dummy',
          label: 'warning',
        ),
        const TestUsageEvent(
233 234 235
          'tool-command-max-rss',
          'dummy',
          label: 'warning',
236 237 238
          value: 10,
        ),
      ]);
239 240
    });

241
    testUsingCommandContext('reports command that results in error', () async {
242
      // Crash if called a third time which is unexpected.
243
      clock.times = <int>[1000, 2000];
244 245 246 247 248 249

      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
        commandFunction: () async {
          throwToolExit('fail');
        }
      );
250 251
      await expectLater(
        () => flutterCommand.run(),
252
        throwsToolExit(),
253 254 255 256 257 258 259 260 261 262 263 264 265 266
      );
      expect(usage.events, <TestUsageEvent>[
        const TestUsageEvent(
          'tool-command-result',
          'dummy',
          label: 'fail',
        ),
        const TestUsageEvent(
          'tool-command-max-rss',
          'dummy',
          label: 'fail',
          value: 10,
        ),
      ]);
267 268
    });

269 270 271 272 273 274 275 276
    test('FlutterCommandResult.success()', () async {
      expect(FlutterCommandResult.success().exitStatus, ExitStatus.success);
    });

    test('FlutterCommandResult.warning()', () async {
      expect(FlutterCommandResult.warning().exitStatus, ExitStatus.warning);
    });

277
    testUsingContext('devToolsServerAddress returns parsed uri', () async {
278
      final DummyFlutterCommand command = DummyFlutterCommand()..addDevToolsOptions(verboseHelp: false);
279 280 281 282 283 284
      await createTestCommandRunner(command).run(<String>[
        'dummy',
        '--${FlutterCommand.kDevToolsServerAddress}',
        'http://127.0.0.1:9105',
      ]);
      expect(command.devToolsServerAddress.toString(), equals('http://127.0.0.1:9105'));
285 286 287
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
288 289 290
    });

    testUsingContext('devToolsServerAddress returns null for bad input', () async {
291
      final DummyFlutterCommand command = DummyFlutterCommand()..addDevToolsOptions(verboseHelp: false);
292 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
      final CommandRunner<void> runner = createTestCommandRunner(command);
      await runner.run(<String>[
        'dummy',
        '--${FlutterCommand.kDevToolsServerAddress}',
        'hello-world',
      ]);
      expect(command.devToolsServerAddress, isNull);

      await runner.run(<String>[
        'dummy',
        '--${FlutterCommand.kDevToolsServerAddress}',
        '',
      ]);
      expect(command.devToolsServerAddress, isNull);

      await runner.run(<String>[
        'dummy',
        '--${FlutterCommand.kDevToolsServerAddress}',
        '9101',
      ]);
      expect(command.devToolsServerAddress, isNull);

      await runner.run(<String>[
        'dummy',
        '--${FlutterCommand.kDevToolsServerAddress}',
        '127.0.0.1:9101',
      ]);
      expect(command.devToolsServerAddress, isNull);
320 321 322
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
323 324
    });

325
    group('signals tests', () {
326
      FakeIoProcessSignal mockSignal;
327 328 329 330
      ProcessSignal signalUnderTest;
      StreamController<io.ProcessSignal> signalController;

      setUp(() {
331
        mockSignal = FakeIoProcessSignal();
332 333
        signalUnderTest = ProcessSignal(mockSignal);
        signalController = StreamController<io.ProcessSignal>();
334
        mockSignal.stream = signalController.stream;
335 336 337 338
      });

      testUsingContext('reports command that is killed', () async {
        // Crash if called a third time which is unexpected.
339
        clock.times = <int>[1000, 2000];
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359

        final Completer<void> completer = Completer<void>();
        setExitFunctionForTests((int exitCode) {
          expect(exitCode, 0);
          restoreExitFunction();
          completer.complete();
        });

        final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
          commandFunction: () async {
            final Completer<void> c = Completer<void>();
            await c.future;
            return null; // unreachable
          }
        );

        unawaited(flutterCommand.run());
        signalController.add(mockSignal);
        await completer.future;

360 361 362 363 364 365 366
        expect(usage.events, <TestUsageEvent>[
          const TestUsageEvent(
            'tool-command-result',
            'dummy',
            label: 'killed',
          ),
          const TestUsageEvent(
367 368 369
            'tool-command-max-rss',
            'dummy',
            label: 'killed',
370 371 372
            value: 10,
          ),
        ]);
373
      }, overrides: <Type, Generator>{
374 375
        FileSystem: () => fileSystem,
        ProcessManager: () => processManager,
376
        ProcessInfo: () => processInfo,
377 378 379 380 381 382 383
        Signals: () => FakeSignals(
          subForSigTerm: signalUnderTest,
          exitSignals: <ProcessSignal>[signalUnderTest],
        ),
        SystemClock: () => clock,
        Usage: () => usage,
      });
384 385

      testUsingContext('command release lock on kill signal', () async {
386
        clock.times = <int>[1000, 2000];
387 388 389 390 391 392 393 394 395
        final Completer<void> completer = Completer<void>();
        setExitFunctionForTests((int exitCode) {
          expect(exitCode, 0);
          restoreExitFunction();
          completer.complete();
        });
        final Completer<void> checkLockCompleter = Completer<void>();
        final DummyFlutterCommand flutterCommand =
            DummyFlutterCommand(commandFunction: () async {
396
          await globals.cache.lock();
397 398 399 400 401 402 403 404 405
          checkLockCompleter.complete();
          final Completer<void> c = Completer<void>();
          await c.future;
          return null; // unreachable
        });

        unawaited(flutterCommand.run());
        await checkLockCompleter.future;

406
        globals.cache.checkLockAcquired();
407 408 409 410

        signalController.add(mockSignal);
        await completer.future;
      }, overrides: <Type, Generator>{
411 412
        FileSystem: () => fileSystem,
        ProcessManager: () => processManager,
413
        ProcessInfo: () => processInfo,
414 415 416 417 418 419
        Signals: () => FakeSignals(
              subForSigTerm: signalUnderTest,
              exitSignals: <ProcessSignal>[signalUnderTest],
            ),
        Usage: () => usage
      });
420 421
    });

422
    testUsingCommandContext('report execution timing by default', () async {
423
      // Crash if called a third time which is unexpected.
424
      clock.times = <int>[1000, 2000];
425

426
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
427 428
      await flutterCommand.run();

429 430
      expect(usage.timings, contains(
        const TestTimingEvent(
431 432
          'flutter',
          'dummy',
433 434 435
          Duration(milliseconds: 1000),
          label: 'fail',
        )));
436 437
    });

438
    testUsingCommandContext('no timing report without usagePath', () async {
439
      // Crash if called a third time which is unexpected.
440
      clock.times = <int>[1000, 2000];
441

442
      final DummyFlutterCommand flutterCommand =
443
          DummyFlutterCommand(noUsagePath: true);
444
      await flutterCommand.run();
445 446

      expect(usage.timings, isEmpty);
447
    });
448

449
    testUsingCommandContext('report additional FlutterCommandResult data', () async {
450
      // Crash if called a third time which is unexpected.
451
      clock.times = <int>[1000, 2000];
452

453
      final FlutterCommandResult commandResult = FlutterCommandResult(
454
        ExitStatus.success,
455
        // nulls should be cleaned up.
456
        timingLabelParts: <String> ['blah1', 'blah2', null, 'blah3'],
457
        endTimeOverride: DateTime.fromMillisecondsSinceEpoch(1500),
458 459
      );

460
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
461 462
        commandFunction: () async => commandResult
      );
463
      await flutterCommand.run();
464 465 466

      expect(usage.timings, contains(
        const TestTimingEvent(
467 468
          'flutter',
          'dummy',
469 470 471
          Duration(milliseconds: 500),
          label: 'success-blah1-blah2-blah3',
        )));
472 473
    });

474
    testUsingCommandContext('report failed execution timing too', () async {
475
      // Crash if called a third time which is unexpected.
476
      clock.times = <int>[1000, 2000];
477

478
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
479 480 481 482
        commandFunction: () async {
          throwToolExit('fail');
        },
      );
483

484 485
      await expectLater(
        () => flutterCommand.run(),
486
        throwsToolExit(),
487 488 489 490 491 492 493 494 495
      );
      expect(usage.timings, contains(
        const TestTimingEvent(
          'flutter',
          'dummy',
          Duration(milliseconds: 1000),
          label: 'fail',
        ),
      ));
496
    });
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520

    testUsingContext('reports null safety analytics when reportNullSafety is true', () async {
      globals.fs.file('lib/main.dart')
        ..createSync(recursive: true)
        ..writeAsStringSync('// @dart=2.12');
      globals.fs.file('pubspec.yaml')
        .writeAsStringSync('name: example\n');
      globals.fs.file('.dart_tool/package_config.json')
        ..createSync(recursive: true)
        ..writeAsStringSync(r'''
{
  "configVersion": 2,
  "packages": [
    {
      "name": "example",
      "rootUri": "../",
      "packageUri": "lib/",
      "languageVersion": "2.12"
    }
  ],
  "generated": "2020-12-02T19:30:53.862346Z",
  "generator": "pub",
  "generatorVersion": "2.12.0-76.0.dev"
}
521
''');
522 523 524 525 526
      final FakeReportingNullSafetyCommand command = FakeReportingNullSafetyCommand();
      final CommandRunner<void> runner = createTestCommandRunner(command);

      await runner.run(<String>['test']);

527 528 529 530 531 532
      expect(usage.events, containsAll(<TestUsageEvent>[
        const TestUsageEvent(
          NullSafetyAnalysisEvent.kNullSafetyCategory,
          'runtime-mode',
          label: 'NullSafetyMode.sound',
        ),
533
        TestUsageEvent(
534 535
          NullSafetyAnalysisEvent.kNullSafetyCategory,
          'stats',
536
          parameters: CustomDimensions.fromMap(<String, String>{
537
            'cd49': '1', 'cd50': '1',
538
          }),
539 540 541 542 543 544 545
        ),
        const TestUsageEvent(
          NullSafetyAnalysisEvent.kNullSafetyCategory,
          'language-version',
          label: '2.12',
        ),
      ]));
546 547
    }, overrides: <Type, Generator>{
      Pub: () => FakePub(),
548
      Usage: () => usage,
549 550
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
551
    });
552 553 554 555 556

    testUsingContext('use packagesPath to generate BuildInfo', () async {
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(packagesPath: 'foo');
      final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
      expect(buildInfo.packagesPath, 'foo');
557 558 559
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
560
    });
561

562 563 564 565
    testUsingContext('use fileSystemScheme to generate BuildInfo', () async {
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(fileSystemScheme: 'foo');
      final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
      expect(buildInfo.fileSystemScheme, 'foo');
566 567 568
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
569 570 571 572 573 574
    });

    testUsingContext('use fileSystemRoots to generate BuildInfo', () async {
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand(fileSystemRoots: <String>['foo', 'bar']);
      final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
      expect(buildInfo.fileSystemRoots, <String>['foo', 'bar']);
575 576 577
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
578 579
    });

580 581 582 583 584 585
    testUsingContext('includes initializeFromDill in BuildInfo', () async {
      final DummyFlutterCommand flutterCommand = DummyFlutterCommand()..usesInitializeFromDillOption(hide: false);
      final CommandRunner<void> runner = createTestCommandRunner(flutterCommand);
      await runner.run(<String>['dummy', '--initialize-from-dill=/foo/bar.dill']);
      final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
      expect(buildInfo.initializeFromDill, '/foo/bar.dill');
586 587 588
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
589 590
    });

591 592 593 594 595 596
    testUsingContext('dds options', () async {
      final FakeDdsCommand ddsCommand = FakeDdsCommand();
      final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
      await runner.run(<String>['test', '--dds-port=1']);
      expect(ddsCommand.enableDds, isTrue);
      expect(ddsCommand.ddsPort, 1);
597 598 599
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
600 601 602 603 604 605 606
    });

    testUsingContext('dds options --dds', () async {
      final FakeDdsCommand ddsCommand = FakeDdsCommand();
      final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
      await runner.run(<String>['test', '--dds']);
      expect(ddsCommand.enableDds, isTrue);
607 608 609
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
610 611 612 613 614 615 616
    });

    testUsingContext('dds options --no-dds', () async {
      final FakeDdsCommand ddsCommand = FakeDdsCommand();
      final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
      await runner.run(<String>['test', '--no-dds']);
      expect(ddsCommand.enableDds, isFalse);
617 618 619
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
620 621 622 623 624 625 626
    });

    testUsingContext('dds options --disable-dds', () async {
      final FakeDdsCommand ddsCommand = FakeDdsCommand();
      final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
      await runner.run(<String>['test', '--disable-dds']);
      expect(ddsCommand.enableDds, isFalse);
627 628 629
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
630 631 632 633 634 635 636
    });

    testUsingContext('dds options --no-disable-dds', () async {
      final FakeDdsCommand ddsCommand = FakeDdsCommand();
      final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
      await runner.run(<String>['test', '--no-disable-dds']);
      expect(ddsCommand.enableDds, isTrue);
637 638 639
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
640 641 642 643 644 645 646
    });

    testUsingContext('dds options --dds --disable-dds', () async {
      final FakeDdsCommand ddsCommand = FakeDdsCommand();
      final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
      await runner.run(<String>['test', '--dds', '--disable-dds']);
      expect(() => ddsCommand.enableDds, throwsToolExit());
647 648 649
    }, overrides: <Type, Generator>{
      FileSystem: () => fileSystem,
      ProcessManager: () => processManager,
650
    });
651 652 653
  });
}

654
class FakeDeprecatedCommand extends FlutterCommand {
655
  @override
656
  String get description => 'A fake command';
657 658

  @override
659 660 661 662
  String get name => 'deprecated';

  @override
  bool get deprecated => true;
663 664 665

  @override
  Future<FlutterCommandResult> runCommand() async {
666
    return FlutterCommandResult.success();
667
  }
668
}
669

670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
class FakeTargetCommand extends FlutterCommand {
  FakeTargetCommand() {
    usesTargetOption();
  }

  @override
  Future<FlutterCommandResult> runCommand() async {
    cachedTargetFile = targetFile;
    return FlutterCommandResult.success();
  }

  String cachedTargetFile;

  @override
  String get description => '';

  @override
  String get name => 'test';
}

690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
class FakeReportingNullSafetyCommand extends FlutterCommand {
  FakeReportingNullSafetyCommand() {
    argParser.addFlag('debug');
    argParser.addFlag('release');
    argParser.addFlag('jit-release');
    argParser.addFlag('profile');
  }

  @override
  String get description => 'test';

  @override
  String get name => 'test';

  @override
  bool get shouldRunPub => true;

  @override
  bool get reportNullSafety => true;

  @override
  Future<FlutterCommandResult> runCommand() async {
    return FlutterCommandResult.success();
  }
}

716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
class FakeDdsCommand extends FlutterCommand {
  FakeDdsCommand() {
    addDdsOptions(verboseHelp: false);
  }

  @override
  String get description => 'test';

  @override
  String get name => 'test';

  @override
  Future<FlutterCommandResult> runCommand() async {
    return FlutterCommandResult.success();
  }
}

733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
class FakeProcessInfo extends Fake implements ProcessInfo {
  @override
  int maxRss = 0;
}

class FakeIoProcessSignal extends Fake implements io.ProcessSignal {
  Stream<io.ProcessSignal> stream;

  @override
  Stream<io.ProcessSignal> watch() => stream;
}

class FakeCache extends Fake implements Cache {
  List<Set<DevelopmentArtifact>> artifacts = <Set<DevelopmentArtifact>>[];

  @override
  Future<void> updateAll(Set<DevelopmentArtifact> requiredArtifacts) async {
    artifacts.add(requiredArtifacts.toSet());
  }

  @override
  void releaseLock() { }
}
756 757 758 759 760

class FakeSignals implements Signals {
  FakeSignals({
    this.subForSigTerm,
    List<ProcessSignal> exitSignals,
761
  }) : delegate = Signals.test(exitSignals: exitSignals);
762 763 764 765 766 767

  final ProcessSignal subForSigTerm;
  final Signals delegate;

  @override
  Object addHandler(ProcessSignal signal, SignalHandler handler) {
768
    if (signal == ProcessSignal.sigterm) {
769 770 771 772 773 774 775 776 777 778 779 780
      return delegate.addHandler(subForSigTerm, handler);
    }
    return delegate.addHandler(signal, handler);
  }

  @override
  Future<bool> removeHandler(ProcessSignal signal, Object token) =>
    delegate.removeHandler(signal, token);

  @override
  Stream<Object> get errors => delegate.errors;
}
781

782 783 784 785 786 787 788 789
class FakeClock extends Fake implements SystemClock {
  List<int> times = <int>[];

  @override
  DateTime now() {
    return DateTime.fromMillisecondsSinceEpoch(times.removeAt(0));
  }
}
790

791 792 793 794 795 796 797 798 799 800 801
class FakePub extends Fake implements Pub {
  @override
  Future<void> get({
    PubContext context,
    String directory,
    bool skipIfAbsent = false,
    bool upgrade = false,
    bool offline = false,
    bool generateSyntheticPackage = false,
    String flutterRootOverride,
    bool checkUpToDate = false,
802
    bool shouldSkipThirdPartyGenerator = true,
803
    bool printProgress = true,
804 805
  }) async { }
}