Move tools tests into a general.shard directory in preparation to changing how we shard tools tests (#36108)

diff --git a/packages/flutter_tools/test/general.shard/commands/analyze_continuously_test.dart b/packages/flutter_tools/test/general.shard/commands/analyze_continuously_test.dart
new file mode 100644
index 0000000..05a49bf
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/analyze_continuously_test.dart
@@ -0,0 +1,105 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/os.dart';
+import 'package:flutter_tools/src/dart/analysis.dart';
+import 'package:flutter_tools/src/dart/pub.dart';
+import 'package:flutter_tools/src/dart/sdk.dart';
+import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  AnalysisServer server;
+  Directory tempDir;
+
+  setUp(() {
+    FlutterCommandRunner.initFlutterRoot();
+    tempDir = fs.systemTempDirectory.createTempSync('flutter_analysis_test.');
+  });
+
+  tearDown(() {
+    tryToDelete(tempDir);
+    return server?.dispose();
+  });
+
+  group('analyze --watch', () {
+    testUsingContext('AnalysisServer success', () async {
+      _createSampleProject(tempDir);
+
+      await pubGet(context: PubContext.flutterTests, directory: tempDir.path);
+
+      server = AnalysisServer(dartSdkPath, <String>[tempDir.path]);
+
+      int errorCount = 0;
+      final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
+      server.onErrors.listen((FileAnalysisErrors errors) => errorCount += errors.errors.length);
+
+      await server.start();
+      await onDone;
+
+      expect(errorCount, 0);
+    }, overrides: <Type, Generator>{
+      OperatingSystemUtils: () => os,
+    });
+  });
+
+  testUsingContext('AnalysisServer errors', () async {
+    _createSampleProject(tempDir, brokenCode: true);
+
+    await pubGet(context: PubContext.flutterTests, directory: tempDir.path);
+
+    server = AnalysisServer(dartSdkPath, <String>[tempDir.path]);
+
+    int errorCount = 0;
+    final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
+    server.onErrors.listen((FileAnalysisErrors errors) {
+      errorCount += errors.errors.length;
+    });
+
+    await server.start();
+    await onDone;
+
+    expect(errorCount, greaterThan(0));
+  }, overrides: <Type, Generator>{
+    OperatingSystemUtils: () => os,
+  });
+
+  testUsingContext('Returns no errors when source is error-free', () async {
+    const String contents = "StringBuffer bar = StringBuffer('baz');";
+    tempDir.childFile('main.dart').writeAsStringSync(contents);
+    server = AnalysisServer(dartSdkPath, <String>[tempDir.path]);
+
+    int errorCount = 0;
+    final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
+    server.onErrors.listen((FileAnalysisErrors errors) {
+      errorCount += errors.errors.length;
+    });
+    await server.start();
+    await onDone;
+    expect(errorCount, 0);
+  }, overrides: <Type, Generator>{
+    OperatingSystemUtils: () => os,
+  });
+}
+
+void _createSampleProject(Directory directory, { bool brokenCode = false }) {
+  final File pubspecFile = fs.file(fs.path.join(directory.path, 'pubspec.yaml'));
+  pubspecFile.writeAsStringSync('''
+name: foo_project
+''');
+
+  final File dartFile = fs.file(fs.path.join(directory.path, 'lib', 'main.dart'));
+  dartFile.parent.createSync();
+  dartFile.writeAsStringSync('''
+void main() {
+  print('hello world');
+  ${brokenCode ? 'prints("hello world");' : ''}
+}
+''');
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/analyze_once_test.dart b/packages/flutter_tools/test/general.shard/commands/analyze_once_test.dart
new file mode 100644
index 0000000..d783d64
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/analyze_once_test.dart
@@ -0,0 +1,242 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/analyze.dart';
+import 'package:flutter_tools/src/commands/create.dart';
+import 'package:flutter_tools/src/runner/flutter_command.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+/// Test case timeout for tests involving project analysis.
+const Timeout allowForSlowAnalyzeTests = Timeout.factor(5.0);
+
+final Generator _kNoColorTerminalPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
+final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
+  Platform: _kNoColorTerminalPlatform,
+};
+
+void main() {
+  final String analyzerSeparator = platform.isWindows ? '-' : '•';
+
+  group('analyze once', () {
+    Directory tempDir;
+    String projectPath;
+    File libMain;
+
+    setUpAll(() {
+      Cache.disableLocking();
+      tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_1.').absolute;
+      projectPath = fs.path.join(tempDir.path, 'flutter_project');
+      libMain = fs.file(fs.path.join(projectPath, 'lib', 'main.dart'));
+    });
+
+    tearDownAll(() {
+      tryToDelete(tempDir);
+    });
+
+    // Create a project to be analyzed
+    testUsingContext('flutter create', () async {
+      await runCommand(
+        command: CreateCommand(),
+        arguments: <String>['--no-wrap', 'create', projectPath],
+        statusTextContains: <String>[
+          'All done!',
+          'Your application code is in ${fs.path.normalize(fs.path.join(fs.path.relative(projectPath), 'lib', 'main.dart'))}',
+        ],
+      );
+      expect(libMain.existsSync(), isTrue);
+    }, timeout: allowForRemotePubInvocation);
+
+    // Analyze in the current directory - no arguments
+    testUsingContext('working directory', () async {
+      await runCommand(
+        command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
+        arguments: <String>['analyze'],
+        statusTextContains: <String>['No issues found!'],
+      );
+    }, timeout: allowForSlowAnalyzeTests);
+
+    // Analyze a specific file outside the current directory
+    testUsingContext('passing one file throws', () async {
+      await runCommand(
+        command: AnalyzeCommand(),
+        arguments: <String>['analyze', libMain.path],
+        toolExit: true,
+        exitMessageContains: 'is not a directory',
+      );
+    });
+
+    // Analyze in the current directory - no arguments
+    testUsingContext('working directory with errors', () async {
+      // Break the code to produce the "The parameter 'onPressed' is required" hint
+      // that is upgraded to a warning in package:flutter/analysis_options_user.yaml
+      // to assert that we are using the default Flutter analysis options.
+      // Also insert a statement that should not trigger a lint here
+      // but will trigger a lint later on when an analysis_options.yaml is added.
+      String source = await libMain.readAsString();
+      source = source.replaceFirst(
+        'onPressed: _incrementCounter,',
+        '// onPressed: _incrementCounter,',
+      );
+      source = source.replaceFirst(
+        '_counter++;',
+        '_counter++; throw "an error message";',
+      );
+      await libMain.writeAsString(source);
+
+      // Analyze in the current directory - no arguments
+      await runCommand(
+        command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
+        arguments: <String>['analyze'],
+        statusTextContains: <String>[
+          'Analyzing',
+          'warning $analyzerSeparator The parameter \'onPressed\' is required',
+          'info $analyzerSeparator The method \'_incrementCounter\' isn\'t used',
+        ],
+        exitMessageContains: '2 issues found.',
+        toolExit: true,
+      );
+    }, timeout: allowForSlowAnalyzeTests, overrides: noColorTerminalOverride);
+
+    // Analyze in the current directory - no arguments
+    testUsingContext('working directory with local options', () async {
+      // Insert an analysis_options.yaml file in the project
+      // which will trigger a lint for broken code that was inserted earlier
+      final File optionsFile = fs.file(fs.path.join(projectPath, 'analysis_options.yaml'));
+      await optionsFile.writeAsString('''
+  include: package:flutter/analysis_options_user.yaml
+  linter:
+    rules:
+      - only_throw_errors
+  ''');
+
+      // Analyze in the current directory - no arguments
+      await runCommand(
+        command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
+        arguments: <String>['analyze'],
+        statusTextContains: <String>[
+          'Analyzing',
+          'warning $analyzerSeparator The parameter \'onPressed\' is required',
+          'info $analyzerSeparator The method \'_incrementCounter\' isn\'t used',
+          'info $analyzerSeparator Only throw instances of classes extending either Exception or Error',
+        ],
+        exitMessageContains: '3 issues found.',
+        toolExit: true,
+      );
+    }, timeout: allowForSlowAnalyzeTests, overrides: noColorTerminalOverride);
+
+    testUsingContext('no duplicate issues', () async {
+      final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_2.').absolute;
+
+      try {
+        final File foo = fs.file(fs.path.join(tempDir.path, 'foo.dart'));
+        foo.writeAsStringSync('''
+import 'bar.dart';
+
+void foo() => bar();
+''');
+
+        final File bar = fs.file(fs.path.join(tempDir.path, 'bar.dart'));
+        bar.writeAsStringSync('''
+import 'dart:async'; // unused
+
+void bar() {
+}
+''');
+
+        // Analyze in the current directory - no arguments
+        await runCommand(
+          command: AnalyzeCommand(workingDirectory: tempDir),
+          arguments: <String>['analyze'],
+          statusTextContains: <String>[
+            'Analyzing',
+          ],
+          exitMessageContains: '1 issue found.',
+          toolExit: true,
+        );
+      } finally {
+        tryToDelete(tempDir);
+      }
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('returns no issues when source is error-free', () async {
+      const String contents = '''
+StringBuffer bar = StringBuffer('baz');
+''';
+      final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_3.');
+      tempDir.childFile('main.dart').writeAsStringSync(contents);
+      try {
+        await runCommand(
+          command: AnalyzeCommand(workingDirectory: fs.directory(tempDir)),
+          arguments: <String>['analyze'],
+          statusTextContains: <String>['No issues found!'],
+        );
+      } finally {
+        tryToDelete(tempDir);
+      }
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('returns no issues for todo comments', () async {
+      const String contents = '''
+// TODO(foobar):
+StringBuffer bar = StringBuffer('baz');
+''';
+      final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_4.');
+      tempDir.childFile('main.dart').writeAsStringSync(contents);
+      try {
+        await runCommand(
+          command: AnalyzeCommand(workingDirectory: fs.directory(tempDir)),
+          arguments: <String>['analyze'],
+          statusTextContains: <String>['No issues found!'],
+        );
+      } finally {
+        tryToDelete(tempDir);
+      }
+    }, overrides: noColorTerminalOverride);
+  });
+}
+
+void assertContains(String text, List<String> patterns) {
+  if (patterns == null) {
+    expect(text, isEmpty);
+  } else {
+    for (String pattern in patterns) {
+      expect(text, contains(pattern));
+    }
+  }
+}
+
+Future<void> runCommand({
+  FlutterCommand command,
+  List<String> arguments,
+  List<String> statusTextContains,
+  List<String> errorTextContains,
+  bool toolExit = false,
+  String exitMessageContains,
+}) async {
+  try {
+    arguments.insert(0, '--flutter-root=${Cache.flutterRoot}');
+    await createTestCommandRunner(command).run(arguments);
+    expect(toolExit, isFalse, reason: 'Expected ToolExit exception');
+  } on ToolExit catch (e) {
+    if (!toolExit) {
+      testLogger.clear();
+      rethrow;
+    }
+    if (exitMessageContains != null) {
+      expect(e.message, contains(exitMessageContains));
+    }
+  }
+  assertContains(testLogger.statusText, statusTextContains);
+  assertContains(testLogger.errorText, errorTextContains);
+
+  testLogger.clear();
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/analyze_test.dart b/packages/flutter_tools/test/general.shard/commands/analyze_test.dart
new file mode 100644
index 0000000..ee24615
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/analyze_test.dart
@@ -0,0 +1,53 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/analyze_base.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+const String _kFlutterRoot = '/data/flutter';
+
+void main() {
+  FileSystem fs;
+  Directory tempDir;
+
+  setUp(() {
+    fs = MemoryFileSystem();
+    fs.directory(_kFlutterRoot).createSync(recursive: true);
+    Cache.flutterRoot = _kFlutterRoot;
+    tempDir = fs.systemTempDirectory.createTempSync('flutter_analysis_test.');
+  });
+
+  tearDown(() {
+    tryToDelete(tempDir);
+  });
+
+  group('analyze', () {
+    testUsingContext('inRepo', () {
+      // Absolute paths
+      expect(inRepo(<String>[tempDir.path]), isFalse);
+      expect(inRepo(<String>[fs.path.join(tempDir.path, 'foo')]), isFalse);
+      expect(inRepo(<String>[Cache.flutterRoot]), isTrue);
+      expect(inRepo(<String>[fs.path.join(Cache.flutterRoot, 'foo')]), isTrue);
+
+      // Relative paths
+      fs.currentDirectory = Cache.flutterRoot;
+      expect(inRepo(<String>['.']), isTrue);
+      expect(inRepo(<String>['foo']), isTrue);
+      fs.currentDirectory = tempDir.path;
+      expect(inRepo(<String>['.']), isFalse);
+      expect(inRepo(<String>['foo']), isFalse);
+
+      // Ensure no exceptions
+      inRepo(null);
+      inRepo(<String>[]);
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/assemble_test.dart b/packages/flutter_tools/test/general.shard/commands/assemble_test.dart
new file mode 100644
index 0000000..e18ebb5
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/assemble_test.dart
@@ -0,0 +1,84 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/build_system/build_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/assemble.dart';
+import 'package:flutter_tools/src/globals.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/testbed.dart';
+
+void main() {
+  group('Assemble', () {
+    Testbed testbed;
+    MockBuildSystem mockBuildSystem;
+
+    setUpAll(() {
+      Cache.disableLocking();
+    });
+
+    setUp(() {
+      mockBuildSystem = MockBuildSystem();
+      testbed = Testbed(overrides: <Type, Generator>{
+        BuildSystem: ()  => mockBuildSystem,
+      });
+    });
+
+    test('Can list the output directory relative to project root', () => testbed.run(() async {
+      final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
+      await commandRunner.run(<String>['assemble', '--flutter-root=.', 'build-dir', '-dBuildMode=debug']);
+      final BufferLogger bufferLogger = logger;
+      final Environment environment = Environment(
+        defines: <String, String>{
+          'BuildMode': 'debug'
+        }, projectDir: fs.currentDirectory,
+        buildDir: fs.directory(getBuildDirectory()),
+      );
+
+      expect(bufferLogger.statusText.trim(),
+          fs.path.relative(environment.buildDir.path, from: fs.currentDirectory.path));
+    }));
+
+    test('Can describe a target', () => testbed.run(() async {
+      when(mockBuildSystem.describe('foobar', any)).thenReturn(<Map<String, Object>>[
+        <String, Object>{'fizz': 'bar'},
+      ]);
+      final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
+      await commandRunner.run(<String>['assemble', '--flutter-root=.', 'describe', 'foobar']);
+      final BufferLogger bufferLogger = logger;
+
+      expect(bufferLogger.statusText.trim(), '[{"fizz":"bar"}]');
+    }));
+
+    test('Can describe a target\'s inputs', () => testbed.run(() async {
+      when(mockBuildSystem.describe('foobar', any)).thenReturn(<Map<String, Object>>[
+        <String, Object>{'name': 'foobar', 'inputs': <String>['bar', 'baz']},
+      ]);
+      final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
+      await commandRunner.run(<String>['assemble', '--flutter-root=.', 'inputs', 'foobar']);
+      final BufferLogger bufferLogger = logger;
+
+      expect(bufferLogger.statusText.trim(), 'bar\nbaz');
+    }));
+
+    test('Can run a build', () => testbed.run(() async {
+      when(mockBuildSystem.build('foobar', any, any)).thenAnswer((Invocation invocation) async {
+        return BuildResult(true, const <String, ExceptionMeasurement>{}, const <String, PerformanceMeasurement>{});
+      });
+      final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
+      await commandRunner.run(<String>['assemble', 'run', 'foobar']);
+      final BufferLogger bufferLogger = logger;
+
+      expect(bufferLogger.statusText.trim(), 'build succeeded');
+    }));
+  });
+}
+
+class MockBuildSystem extends Mock implements BuildSystem {}
diff --git a/packages/flutter_tools/test/general.shard/commands/attach_test.dart b/packages/flutter_tools/test/general.shard/commands/attach_test.dart
new file mode 100644
index 0000000..c708f1e
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/attach_test.dart
@@ -0,0 +1,644 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/base/terminal.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/attach.dart';
+import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/resident_runner.dart';
+import 'package:flutter_tools/src/run_hot.dart';
+import 'package:meta/meta.dart';
+import 'package:mockito/mockito.dart';
+import 'package:multicast_dns/multicast_dns.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  group('attach', () {
+    StreamLogger logger;
+    FileSystem testFileSystem;
+
+    setUp(() {
+      Cache.disableLocking();
+      logger = StreamLogger();
+      testFileSystem = MemoryFileSystem(
+      style: platform.isWindows
+          ? FileSystemStyle.windows
+          : FileSystemStyle.posix,
+      );
+      testFileSystem.directory('lib').createSync();
+      testFileSystem.file(testFileSystem.path.join('lib', 'main.dart')).createSync();
+    });
+
+    group('with one device and no specified target file', () {
+      const int devicePort = 499;
+      const int hostPort = 42;
+
+      MockDeviceLogReader mockLogReader;
+      MockPortForwarder portForwarder;
+      MockAndroidDevice device;
+
+      setUp(() {
+        mockLogReader = MockDeviceLogReader();
+        portForwarder = MockPortForwarder();
+        device = MockAndroidDevice();
+        when(device.getLogReader()).thenAnswer((_) {
+          // Now that the reader is used, start writing messages to it.
+          Timer.run(() {
+            mockLogReader.addLine('Foo');
+            mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
+          });
+
+          return mockLogReader;
+        });
+        when(device.portForwarder)
+          .thenReturn(portForwarder);
+        when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
+          .thenAnswer((_) async => hostPort);
+        when(portForwarder.forwardedPorts)
+          .thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
+        when(portForwarder.unforward(any))
+          .thenAnswer((_) async => null);
+
+        // We cannot add the device to a device manager because that is
+        // only enabled by the context of each testUsingContext call.
+        //
+        // Instead each test will add the device to the device manager
+        // on its own.
+      });
+
+      tearDown(() {
+        mockLogReader.dispose();
+      });
+
+      testUsingContext('finds observatory port and forwards', () async {
+        testDeviceManager.addDevice(device);
+        final Completer<void> completer = Completer<void>();
+        final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
+          if (message == '[stdout] Done.') {
+            // The "Done." message is output by the AttachCommand when it's done.
+            completer.complete();
+          }
+        });
+        final Future<void> task = createTestCommandRunner(AttachCommand()).run(<String>['attach']);
+        await completer.future;
+        verify(
+          portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')),
+        ).called(1);
+        await expectLoggerInterruptEndsTask(task, logger);
+        await loggerSubscription.cancel();
+      }, overrides: <Type, Generator>{
+        FileSystem: () => testFileSystem,
+        Logger: () => logger,
+      });
+
+      testUsingContext('accepts filesystem parameters', () async {
+        testDeviceManager.addDevice(device);
+
+        const String filesystemScheme = 'foo';
+        const String filesystemRoot = '/build-output/';
+        const String projectRoot = '/build-output/project-root';
+        const String outputDill = '/tmp/output.dill';
+
+        final MockHotRunner mockHotRunner = MockHotRunner();
+        when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter')))
+            .thenAnswer((_) async => 0);
+
+        final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory();
+        when(
+          mockHotRunnerFactory.build(
+            any,
+            target: anyNamed('target'),
+            projectRootPath: anyNamed('projectRootPath'),
+            dillOutputPath: anyNamed('dillOutputPath'),
+            debuggingOptions: anyNamed('debuggingOptions'),
+            packagesFilePath: anyNamed('packagesFilePath'),
+            usesTerminalUi: anyNamed('usesTerminalUi'),
+            flutterProject: anyNamed('flutterProject'),
+            ipv6: false,
+          ),
+        ).thenReturn(mockHotRunner);
+
+        final AttachCommand command = AttachCommand(
+          hotRunnerFactory: mockHotRunnerFactory,
+        );
+        await createTestCommandRunner(command).run(<String>[
+          'attach',
+          '--filesystem-scheme',
+          filesystemScheme,
+          '--filesystem-root',
+          filesystemRoot,
+          '--project-root',
+          projectRoot,
+          '--output-dill',
+          outputDill,
+          '-v', // enables verbose logging
+        ]);
+
+        // Validate the attach call built a mock runner with the right
+        // project root and output dill.
+        final VerificationResult verificationResult = verify(
+          mockHotRunnerFactory.build(
+            captureAny,
+            target: anyNamed('target'),
+            projectRootPath: projectRoot,
+            dillOutputPath: outputDill,
+            debuggingOptions: anyNamed('debuggingOptions'),
+            packagesFilePath: anyNamed('packagesFilePath'),
+            usesTerminalUi: anyNamed('usesTerminalUi'),
+            flutterProject: anyNamed('flutterProject'),
+            ipv6: false,
+          ),
+        )..called(1);
+
+        final List<FlutterDevice> flutterDevices = verificationResult.captured.first;
+        expect(flutterDevices, hasLength(1));
+
+        // Validate that the attach call built a flutter device with the right
+        // output dill, filesystem scheme, and filesystem root.
+        final FlutterDevice flutterDevice = flutterDevices.first;
+
+        expect(flutterDevice.dillOutputPath, outputDill);
+        expect(flutterDevice.fileSystemScheme, filesystemScheme);
+        expect(flutterDevice.fileSystemRoots, const <String>[filesystemRoot]);
+      }, overrides: <Type, Generator>{
+        FileSystem: () => testFileSystem,
+      });
+
+      testUsingContext('exits when ipv6 is specified and debug-port is not', () async {
+        testDeviceManager.addDevice(device);
+
+        final AttachCommand command = AttachCommand();
+        await expectLater(
+          createTestCommandRunner(command).run(<String>['attach', '--ipv6']),
+          throwsToolExit(
+            message: 'When the --debug-port or --debug-uri is unknown, this command determines '
+                     'the value of --ipv6 on its own.',
+          ),
+        );
+      }, overrides: <Type, Generator>{
+        FileSystem: () => testFileSystem,
+      },);
+
+      testUsingContext('exits when observatory-port is specified and debug-port is not', () async {
+        testDeviceManager.addDevice(device);
+
+        final AttachCommand command = AttachCommand();
+        await expectLater(
+          createTestCommandRunner(command).run(<String>['attach', '--observatory-port', '100']),
+          throwsToolExit(
+            message: 'When the --debug-port or --debug-uri is unknown, this command does not use '
+                     'the value of --observatory-port.',
+          ),
+        );
+      }, overrides: <Type, Generator>{
+        FileSystem: () => testFileSystem,
+      },);
+    });
+
+
+    testUsingContext('selects specified target', () async {
+      const int devicePort = 499;
+      const int hostPort = 42;
+      final MockDeviceLogReader mockLogReader = MockDeviceLogReader();
+      final MockPortForwarder portForwarder = MockPortForwarder();
+      final MockAndroidDevice device = MockAndroidDevice();
+      final MockHotRunner mockHotRunner = MockHotRunner();
+      final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory();
+      when(device.portForwarder)
+        .thenReturn(portForwarder);
+      when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
+        .thenAnswer((_) async => hostPort);
+      when(portForwarder.forwardedPorts)
+        .thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
+      when(portForwarder.unforward(any))
+        .thenAnswer((_) async => null);
+      when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter')))
+          .thenAnswer((_) async => 0);
+      when(mockHotRunnerFactory.build(
+        any,
+        target: anyNamed('target'),
+        debuggingOptions: anyNamed('debuggingOptions'),
+        packagesFilePath: anyNamed('packagesFilePath'),
+        usesTerminalUi: anyNamed('usesTerminalUi'),
+        flutterProject: anyNamed('flutterProject'),
+        ipv6: false,
+      )).thenReturn(mockHotRunner);
+
+      testDeviceManager.addDevice(device);
+      when(device.getLogReader())
+        .thenAnswer((_) {
+          // Now that the reader is used, start writing messages to it.
+          Timer.run(() {
+            mockLogReader.addLine('Foo');
+            mockLogReader.addLine(
+                'Observatory listening on http://127.0.0.1:$devicePort');
+          });
+          return mockLogReader;
+        });
+      final File foo = fs.file('lib/foo.dart')
+        ..createSync();
+
+      // Delete the main.dart file to be sure that attach works without it.
+      fs.file(fs.path.join('lib', 'main.dart')).deleteSync();
+
+      final AttachCommand command = AttachCommand(hotRunnerFactory: mockHotRunnerFactory);
+      await createTestCommandRunner(command).run(<String>['attach', '-t', foo.path, '-v']);
+
+      verify(mockHotRunnerFactory.build(
+        any,
+        target: foo.path,
+        debuggingOptions: anyNamed('debuggingOptions'),
+        packagesFilePath: anyNamed('packagesFilePath'),
+        usesTerminalUi: anyNamed('usesTerminalUi'),
+        flutterProject: anyNamed('flutterProject'),
+        ipv6: false,
+      )).called(1);
+    }, overrides: <Type, Generator>{
+      FileSystem: () => testFileSystem,
+    });
+
+    group('forwarding to given port', () {
+      const int devicePort = 499;
+      const int hostPort = 42;
+      MockPortForwarder portForwarder;
+      MockAndroidDevice device;
+
+      setUp(() {
+        portForwarder = MockPortForwarder();
+        device = MockAndroidDevice();
+
+        when(device.portForwarder)
+          .thenReturn(portForwarder);
+        when(portForwarder.forward(devicePort))
+          .thenAnswer((_) async => hostPort);
+        when(portForwarder.forwardedPorts)
+          .thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
+        when(portForwarder.unforward(any))
+          .thenAnswer((_) async => null);
+      });
+
+      testUsingContext('succeeds in ipv4 mode', () async {
+        testDeviceManager.addDevice(device);
+
+        final Completer<void> completer = Completer<void>();
+        final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
+          if (message == '[verbose] Connecting to service protocol: http://127.0.0.1:42/') {
+            // Wait until resident_runner.dart tries to connect.
+            // There's nothing to connect _to_, so that's as far as we care to go.
+            completer.complete();
+          }
+        });
+        final Future<void> task = createTestCommandRunner(AttachCommand())
+          .run(<String>['attach', '--debug-port', '$devicePort']);
+        await completer.future;
+        verify(portForwarder.forward(devicePort)).called(1);
+
+        await expectLoggerInterruptEndsTask(task, logger);
+        await loggerSubscription.cancel();
+      }, overrides: <Type, Generator>{
+        FileSystem: () => testFileSystem,
+        Logger: () => logger,
+      });
+
+      testUsingContext('succeeds in ipv6 mode', () async {
+        testDeviceManager.addDevice(device);
+
+        final Completer<void> completer = Completer<void>();
+        final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
+          if (message == '[verbose] Connecting to service protocol: http://[::1]:42/') {
+            // Wait until resident_runner.dart tries to connect.
+            // There's nothing to connect _to_, so that's as far as we care to go.
+            completer.complete();
+          }
+        });
+        final Future<void> task = createTestCommandRunner(AttachCommand())
+          .run(<String>['attach', '--debug-port', '$devicePort', '--ipv6']);
+        await completer.future;
+        verify(portForwarder.forward(devicePort)).called(1);
+
+        await expectLoggerInterruptEndsTask(task, logger);
+        await loggerSubscription.cancel();
+      }, overrides: <Type, Generator>{
+        FileSystem: () => testFileSystem,
+        Logger: () => logger,
+      });
+
+      testUsingContext('skips in ipv4 mode with a provided observatory port', () async {
+        testDeviceManager.addDevice(device);
+
+        final Completer<void> completer = Completer<void>();
+        final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
+          if (message == '[verbose] Connecting to service protocol: http://127.0.0.1:42/') {
+            // Wait until resident_runner.dart tries to connect.
+            // There's nothing to connect _to_, so that's as far as we care to go.
+            completer.complete();
+          }
+        });
+        final Future<void> task = createTestCommandRunner(AttachCommand()).run(
+          <String>[
+            'attach',
+            '--debug-port',
+            '$devicePort',
+            '--observatory-port',
+            '$hostPort',
+          ],
+        );
+        await completer.future;
+        verifyNever(portForwarder.forward(devicePort));
+
+        await expectLoggerInterruptEndsTask(task, logger);
+        await loggerSubscription.cancel();
+      }, overrides: <Type, Generator>{
+        FileSystem: () => testFileSystem,
+        Logger: () => logger,
+      });
+
+      testUsingContext('skips in ipv6 mode with a provided observatory port', () async {
+        testDeviceManager.addDevice(device);
+
+        final Completer<void> completer = Completer<void>();
+        final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
+          if (message == '[verbose] Connecting to service protocol: http://[::1]:42/') {
+            // Wait until resident_runner.dart tries to connect.
+            // There's nothing to connect _to_, so that's as far as we care to go.
+            completer.complete();
+          }
+        });
+        final Future<void> task = createTestCommandRunner(AttachCommand()).run(
+          <String>[
+            'attach',
+            '--debug-port',
+            '$devicePort',
+            '--observatory-port',
+            '$hostPort',
+            '--ipv6',
+          ],
+        );
+        await completer.future;
+        verifyNever(portForwarder.forward(devicePort));
+
+        await expectLoggerInterruptEndsTask(task, logger);
+        await loggerSubscription.cancel();
+      }, overrides: <Type, Generator>{
+        FileSystem: () => testFileSystem,
+        Logger: () => logger,
+      });
+    });
+
+    testUsingContext('exits when no device connected', () async {
+      final AttachCommand command = AttachCommand();
+      await expectLater(
+        createTestCommandRunner(command).run(<String>['attach']),
+        throwsA(isInstanceOf<ToolExit>()),
+      );
+      expect(testLogger.statusText, contains('No supported devices connected'));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => testFileSystem,
+    });
+
+    testUsingContext('exits when multiple devices connected', () async {
+      Device aDeviceWithId(String id) {
+        final MockAndroidDevice device = MockAndroidDevice();
+        when(device.name).thenReturn('d$id');
+        when(device.id).thenReturn(id);
+        when(device.isLocalEmulator).thenAnswer((_) async => false);
+        when(device.sdkNameAndVersion).thenAnswer((_) async => 'Android 46');
+        return device;
+      }
+
+      final AttachCommand command = AttachCommand();
+      testDeviceManager.addDevice(aDeviceWithId('xx1'));
+      testDeviceManager.addDevice(aDeviceWithId('yy2'));
+      await expectLater(
+        createTestCommandRunner(command).run(<String>['attach']),
+        throwsA(isInstanceOf<ToolExit>()),
+      );
+      expect(testLogger.statusText, contains('More than one device'));
+      expect(testLogger.statusText, contains('xx1'));
+      expect(testLogger.statusText, contains('yy2'));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => testFileSystem,
+    });
+  });
+
+  group('mDNS Discovery', () {
+    final int year3000 = DateTime(3000).millisecondsSinceEpoch;
+
+    MDnsClient getMockClient(
+      List<PtrResourceRecord> ptrRecords,
+      Map<String, List<SrvResourceRecord>> srvResponse,
+    ) {
+      final MDnsClient client = MockMDnsClient();
+
+      when(client.lookup<PtrResourceRecord>(
+        ResourceRecordQuery.serverPointer(MDnsObservatoryDiscovery.dartObservatoryName),
+      )).thenAnswer((_) => Stream<PtrResourceRecord>.fromIterable(ptrRecords));
+
+      for (final MapEntry<String, List<SrvResourceRecord>> entry in srvResponse.entries) {
+        when(client.lookup<SrvResourceRecord>(
+          ResourceRecordQuery.service(entry.key),
+        )).thenAnswer((_) => Stream<SrvResourceRecord>.fromIterable(entry.value));
+      }
+      return client;
+    }
+
+    testUsingContext('No ports available', () async {
+      final MDnsClient client = getMockClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
+
+      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+      final int port = (await portDiscovery.query())?.port;
+      expect(port, isNull);
+    });
+
+    testUsingContext('One port available, no appId', () async {
+      final MDnsClient client = getMockClient(
+        <PtrResourceRecord>[
+          PtrResourceRecord('foo', year3000, domainName: 'bar'),
+        ],
+        <String, List<SrvResourceRecord>>{
+          'bar': <SrvResourceRecord>[
+            SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+          ],
+        },
+      );
+
+      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+      final int port = (await portDiscovery.query())?.port;
+      expect(port, 123);
+    });
+
+    testUsingContext('Multiple ports available, without appId', () async {
+      final MDnsClient client = getMockClient(
+        <PtrResourceRecord>[
+          PtrResourceRecord('foo', year3000, domainName: 'bar'),
+          PtrResourceRecord('baz', year3000, domainName: 'fiz'),
+        ],
+        <String, List<SrvResourceRecord>>{
+          'bar': <SrvResourceRecord>[
+            SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+          ],
+          'fiz': <SrvResourceRecord>[
+            SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
+          ],
+        },
+      );
+
+      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+      expect(() => portDiscovery.query(), throwsToolExit());
+    });
+
+    testUsingContext('Multiple ports available, with appId', () async {
+      final MDnsClient client = getMockClient(
+        <PtrResourceRecord>[
+          PtrResourceRecord('foo', year3000, domainName: 'bar'),
+          PtrResourceRecord('baz', year3000, domainName: 'fiz'),
+        ],
+        <String, List<SrvResourceRecord>>{
+          'bar': <SrvResourceRecord>[
+            SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+          ],
+          'fiz': <SrvResourceRecord>[
+            SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
+          ],
+        },
+      );
+
+      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+      final int port = (await portDiscovery.query(applicationId: 'fiz'))?.port;
+      expect(port, 321);
+    });
+
+    testUsingContext('Multiple ports available per process, with appId', () async {
+      final MDnsClient client = getMockClient(
+        <PtrResourceRecord>[
+          PtrResourceRecord('foo', year3000, domainName: 'bar'),
+          PtrResourceRecord('baz', year3000, domainName: 'fiz'),
+        ],
+        <String, List<SrvResourceRecord>>{
+          'bar': <SrvResourceRecord>[
+            SrvResourceRecord('bar', year3000, port: 1234, weight: 1, priority: 1, target: 'appId'),
+            SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+          ],
+          'fiz': <SrvResourceRecord>[
+            SrvResourceRecord('fiz', year3000, port: 4321, weight: 1, priority: 1, target: 'local'),
+            SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
+          ],
+        },
+      );
+
+      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+      final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
+      expect(port, 1234);
+    });
+
+    testUsingContext('Query returns null', () async {
+      final MDnsClient client = getMockClient(
+        <PtrResourceRecord>[],
+         <String, List<SrvResourceRecord>>{},
+      );
+
+      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+      final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
+      expect(port, isNull);
+    });
+  });
+}
+
+class MockMDnsClient extends Mock implements MDnsClient {}
+
+class MockPortForwarder extends Mock implements DevicePortForwarder {}
+
+class MockHotRunner extends Mock implements HotRunner {}
+
+class MockHotRunnerFactory extends Mock implements HotRunnerFactory {}
+
+class StreamLogger extends Logger {
+  @override
+  bool get isVerbose => true;
+
+  @override
+  void printError(
+    String message, {
+    StackTrace stackTrace,
+    bool emphasis,
+    TerminalColor color,
+    int indent,
+    int hangingIndent,
+    bool wrap,
+  }) {
+    _log('[stderr] $message');
+  }
+
+  @override
+  void printStatus(
+    String message, {
+    bool emphasis,
+    TerminalColor color,
+    bool newline,
+    int indent,
+    int hangingIndent,
+    bool wrap,
+  }) {
+    _log('[stdout] $message');
+  }
+
+  @override
+  void printTrace(String message) {
+    _log('[verbose] $message');
+  }
+
+  @override
+  Status startProgress(
+    String message, {
+    @required Duration timeout,
+    String progressId,
+    bool multilineOutput = false,
+    int progressIndicatorPadding = kDefaultStatusPadding,
+  }) {
+    _log('[progress] $message');
+    return SilentStatus(timeout: timeout)..start();
+  }
+
+  bool _interrupt = false;
+
+  void interrupt() {
+    _interrupt = true;
+  }
+
+  final StreamController<String> _controller = StreamController<String>.broadcast();
+
+  void _log(String message) {
+    _controller.add(message);
+    if (_interrupt) {
+      _interrupt = false;
+      throw const LoggerInterrupted();
+    }
+  }
+
+  Stream<String> get stream => _controller.stream;
+}
+
+class LoggerInterrupted implements Exception {
+  const LoggerInterrupted();
+}
+
+Future<void> expectLoggerInterruptEndsTask(Future<void> task, StreamLogger logger) async {
+  logger.interrupt(); // an exception during the task should cause it to fail...
+  try {
+    await task;
+    expect(false, isTrue); // (shouldn't reach here)
+  } on ToolExit catch (error) {
+    expect(error.exitCode, 2); // ...with exit code 2.
+  }
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_bundle_test.dart b/packages/flutter_tools/test/general.shard/commands/build_bundle_test.dart
new file mode 100644
index 0000000..6c6c8ad
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_bundle_test.dart
@@ -0,0 +1,96 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build_bundle.dart';
+import 'package:flutter_tools/src/bundle.dart';
+import 'package:flutter_tools/src/usage.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  Cache.disableLocking();
+
+  group('getUsage', () {
+    Directory tempDir;
+    MockBundleBuilder mockBundleBuilder;
+
+    setUp(() {
+      tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
+
+      mockBundleBuilder = MockBundleBuilder();
+      when(
+        mockBundleBuilder.build(
+          platform: anyNamed('platform'),
+          buildMode: anyNamed('buildMode'),
+          mainPath: anyNamed('mainPath'),
+          manifestPath: anyNamed('manifestPath'),
+          applicationKernelFilePath: anyNamed('applicationKernelFilePath'),
+          depfilePath: anyNamed('depfilePath'),
+          privateKeyPath: anyNamed('privateKeyPath'),
+          assetDirPath: anyNamed('assetDirPath'),
+          packagesPath: anyNamed('packagesPath'),
+          precompiledSnapshot: anyNamed('precompiledSnapshot'),
+          reportLicensedPackages: anyNamed('reportLicensedPackages'),
+          trackWidgetCreation: anyNamed('trackWidgetCreation'),
+          extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
+          extraGenSnapshotOptions: anyNamed('extraGenSnapshotOptions'),
+          fileSystemRoots: anyNamed('fileSystemRoots'),
+          fileSystemScheme: anyNamed('fileSystemScheme'),
+        ),
+      ).thenAnswer((_) => Future<void>.value());
+    });
+
+    tearDown(() {
+      tryToDelete(tempDir);
+    });
+
+    Future<BuildBundleCommand> runCommandIn(String projectPath, { List<String> arguments }) async {
+      final BuildBundleCommand command = BuildBundleCommand(bundleBuilder: mockBundleBuilder);
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+      await runner.run(<String>[
+        'bundle',
+        ...?arguments,
+        '--target=$projectPath/lib/main.dart',
+      ]);
+      return command;
+    }
+
+    testUsingContext('indicate that project is a module', () async {
+      final String projectPath = await createProject(tempDir,
+          arguments: <String>['--no-pub', '--template=module']);
+
+      final BuildBundleCommand command = await runCommandIn(projectPath);
+
+      expect(await command.usageValues,
+          containsPair(kCommandBuildBundleIsModule, 'true'));
+    }, timeout: allowForCreateFlutterProject);
+
+    testUsingContext('indicate that project is not a module', () async {
+      final String projectPath = await createProject(tempDir,
+          arguments: <String>['--no-pub', '--template=app']);
+
+      final BuildBundleCommand command = await runCommandIn(projectPath);
+
+      expect(await command.usageValues,
+          containsPair(kCommandBuildBundleIsModule, 'false'));
+    }, timeout: allowForCreateFlutterProject);
+
+    testUsingContext('indicate the target platform', () async {
+      final String projectPath = await createProject(tempDir,
+          arguments: <String>['--no-pub', '--template=app']);
+
+      final BuildBundleCommand command = await runCommandIn(projectPath);
+
+      expect(await command.usageValues,
+          containsPair(kCommandBuildBundleTargetPlatform, 'android-arm'));
+    }, timeout: allowForCreateFlutterProject);
+  });
+}
+
+class MockBundleBuilder extends Mock implements BundleBuilder {}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_fuchsia_test.dart b/packages/flutter_tools/test/general.shard/commands/build_fuchsia_test.dart
new file mode 100644
index 0000000..3734d04
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_fuchsia_test.dart
@@ -0,0 +1,237 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build.dart';
+import 'package:flutter_tools/src/fuchsia/fuchsia_kernel_compiler.dart';
+import 'package:flutter_tools/src/fuchsia/fuchsia_pm.dart';
+import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:meta/meta.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  Cache.disableLocking();
+
+  MemoryFileSystem memoryFileSystem;
+  MockPlatform linuxPlatform;
+  MockPlatform windowsPlatform;
+  MockFuchsiaSdk fuchsiaSdk;
+  MockFuchsiaArtifacts fuchsiaArtifacts;
+  MockFuchsiaArtifacts fuchsiaArtifactsNoCompiler;
+
+  setUp(() {
+    memoryFileSystem = MemoryFileSystem();
+    linuxPlatform = MockPlatform();
+    windowsPlatform = MockPlatform();
+    fuchsiaSdk = MockFuchsiaSdk();
+    fuchsiaArtifacts = MockFuchsiaArtifacts();
+    fuchsiaArtifactsNoCompiler = MockFuchsiaArtifacts();
+
+    when(linuxPlatform.isLinux).thenReturn(true);
+    when(windowsPlatform.isWindows).thenReturn(true);
+    when(windowsPlatform.isLinux).thenReturn(false);
+    when(windowsPlatform.isMacOS).thenReturn(false);
+    when(fuchsiaArtifacts.kernelCompiler).thenReturn(MockFile());
+    when(fuchsiaArtifactsNoCompiler.kernelCompiler).thenReturn(null);
+  });
+
+  group('Fuchsia build fails gracefully when', () {
+    testUsingContext('there is no Fuchsia project',
+        () async {
+      final BuildCommand command = BuildCommand();
+      applyMocksToCommand(command);
+      expect(
+          createTestCommandRunner(command)
+              .run(const <String>['build', 'fuchsia']),
+          throwsA(isInstanceOf<ToolExit>()));
+    }, overrides: <Type, Generator>{
+      Platform: () => linuxPlatform,
+      FileSystem: () => memoryFileSystem,
+      FuchsiaArtifacts: () => fuchsiaArtifacts,
+    });
+
+    testUsingContext('there is no cmx file', () async {
+      final BuildCommand command = BuildCommand();
+      applyMocksToCommand(command);
+      fs.directory('fuchsia').createSync(recursive: true);
+      fs.file('.packages').createSync();
+      fs.file('pubspec.yaml').createSync();
+
+      expect(
+          createTestCommandRunner(command)
+              .run(const <String>['build', 'fuchsia']),
+          throwsA(isInstanceOf<ToolExit>()));
+    }, overrides: <Type, Generator>{
+      Platform: () => linuxPlatform,
+      FileSystem: () => memoryFileSystem,
+      FuchsiaArtifacts: () => fuchsiaArtifacts,
+    });
+
+    testUsingContext('on Windows platform', () async {
+      final BuildCommand command = BuildCommand();
+      applyMocksToCommand(command);
+      const String appName = 'app_name';
+      fs
+          .file(fs.path.join('fuchsia', 'meta', '$appName.cmx'))
+          ..createSync(recursive: true)
+          ..writeAsStringSync('{}');
+      fs.file('.packages').createSync();
+      final File pubspecFile = fs.file('pubspec.yaml')..createSync();
+      pubspecFile.writeAsStringSync('name: $appName');
+
+      expect(
+          createTestCommandRunner(command)
+              .run(const <String>['build', 'fuchsia']),
+          throwsA(isInstanceOf<ToolExit>()));
+    }, overrides: <Type, Generator>{
+      Platform: () => windowsPlatform,
+      FileSystem: () => memoryFileSystem,
+      FuchsiaArtifacts: () => fuchsiaArtifacts,
+    });
+
+    testUsingContext('there is no Fuchsia kernel compiler', () async {
+      final BuildCommand command = BuildCommand();
+      applyMocksToCommand(command);
+      const String appName = 'app_name';
+      fs
+          .file(fs.path.join('fuchsia', 'meta', '$appName.cmx'))
+          ..createSync(recursive: true)
+          ..writeAsStringSync('{}');
+      fs.file('.packages').createSync();
+      fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+      final File pubspecFile = fs.file('pubspec.yaml')..createSync();
+      pubspecFile.writeAsStringSync('name: $appName');
+      expect(
+          createTestCommandRunner(command)
+              .run(const <String>['build', 'fuchsia']),
+          throwsA(isInstanceOf<ToolExit>()));
+    }, overrides: <Type, Generator>{
+      Platform: () => linuxPlatform,
+      FileSystem: () => memoryFileSystem,
+      FuchsiaArtifacts: () => fuchsiaArtifactsNoCompiler,
+    });
+  });
+
+  testUsingContext('Fuchsia build parts fit together right', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    const String appName = 'app_name';
+    fs
+        .file(fs.path.join('fuchsia', 'meta', '$appName.cmx'))
+        ..createSync(recursive: true)
+        ..writeAsStringSync('{}');
+    fs.file('.packages').createSync();
+    fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+    final File pubspecFile = fs.file('pubspec.yaml')..createSync();
+    pubspecFile.writeAsStringSync('name: $appName');
+
+    await createTestCommandRunner(command)
+        .run(const <String>['build', 'fuchsia']);
+    final String farPath =
+        fs.path.join(getFuchsiaBuildDirectory(), 'pkg', 'app_name-0.far');
+    expect(fs.file(farPath).existsSync(), isTrue);
+  }, overrides: <Type, Generator>{
+    Platform: () => linuxPlatform,
+    FileSystem: () => memoryFileSystem,
+    FuchsiaSdk: () => fuchsiaSdk,
+  });
+}
+
+class MockPlatform extends Mock implements Platform {
+  @override
+  Map<String, String> environment = <String, String>{
+    'FLUTTER_ROOT': '/',
+  };
+}
+
+class MockFuchsiaPM extends Mock implements FuchsiaPM {
+  String _appName;
+
+  @override
+  Future<bool> init(String buildPath, String appName) async {
+    if (!fs.directory(buildPath).existsSync()) {
+      return false;
+    }
+    fs
+        .file(fs.path.join(buildPath, 'meta', 'package'))
+        .createSync(recursive: true);
+    _appName = appName;
+    return true;
+  }
+
+  @override
+  Future<bool> genkey(String buildPath, String outKeyPath) async {
+    if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync()) {
+      return false;
+    }
+    fs.file(outKeyPath).createSync(recursive: true);
+    return true;
+  }
+
+  @override
+  Future<bool> build(
+      String buildPath, String keyPath, String manifestPath) async {
+    if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync() ||
+        !fs.file(keyPath).existsSync() ||
+        !fs.file(manifestPath).existsSync()) {
+      return false;
+    }
+    fs.file(fs.path.join(buildPath, 'meta.far')).createSync(recursive: true);
+    return true;
+  }
+
+  @override
+  Future<bool> archive(
+      String buildPath, String keyPath, String manifestPath) async {
+    if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync() ||
+        !fs.file(keyPath).existsSync() ||
+        !fs.file(manifestPath).existsSync()) {
+      return false;
+    }
+    if (_appName == null) {
+      return false;
+    }
+    fs
+        .file(fs.path.join(buildPath, '$_appName-0.far'))
+        .createSync(recursive: true);
+    return true;
+  }
+}
+
+class MockFuchsiaKernelCompiler extends Mock implements FuchsiaKernelCompiler {
+  @override
+  Future<void> build({
+    @required FuchsiaProject fuchsiaProject,
+    @required String target, // E.g., lib/main.dart
+    BuildInfo buildInfo = BuildInfo.debug,
+  }) async {
+    final String outDir = getFuchsiaBuildDirectory();
+    final String appName = fuchsiaProject.project.manifest.appName;
+    final String manifestPath = fs.path.join(outDir, '$appName.dilpmanifest');
+    fs.file(manifestPath).createSync(recursive: true);
+  }
+}
+
+class MockFuchsiaSdk extends Mock implements FuchsiaSdk {
+  @override
+  final FuchsiaPM fuchsiaPM = MockFuchsiaPM();
+
+  @override
+  final FuchsiaKernelCompiler fuchsiaKernelCompiler =
+      MockFuchsiaKernelCompiler();
+}
+
+class MockFile extends Mock implements File {}
+
+class MockFuchsiaArtifacts extends Mock implements FuchsiaArtifacts {}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_linux_test.dart b/packages/flutter_tools/test/general.shard/commands/build_linux_test.dart
new file mode 100644
index 0000000..638a20b
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_linux_test.dart
@@ -0,0 +1,125 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build.dart';
+import 'package:flutter_tools/src/linux/makefile.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  MockProcessManager mockProcessManager;
+  MockProcess mockProcess;
+  MockPlatform linuxPlatform;
+  MockPlatform notLinuxPlatform;
+
+  setUpAll(() {
+    Cache.disableLocking();
+  });
+
+  setUp(() {
+    mockProcessManager = MockProcessManager();
+    mockProcess = MockProcess();
+    linuxPlatform = MockPlatform();
+    notLinuxPlatform = MockPlatform();
+    when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
+      return 0;
+    });
+    when(mockProcess.stderr).thenAnswer((Invocation invocation) {
+      return const Stream<List<int>>.empty();
+    });
+    when(mockProcess.stdout).thenAnswer((Invocation invocation) {
+      return const Stream<List<int>>.empty();
+    });
+    when(linuxPlatform.isLinux).thenReturn(true);
+    when(notLinuxPlatform.isLinux).thenReturn(false);
+  });
+
+  testUsingContext('Linux build fails when there is no linux project', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    expect(createTestCommandRunner(command).run(
+      const <String>['build', 'linux']
+    ), throwsA(isInstanceOf<ToolExit>()));
+  }, overrides: <Type, Generator>{
+    Platform: () => linuxPlatform,
+    FileSystem: () => MemoryFileSystem(),
+  });
+
+  testUsingContext('Linux build fails on non-linux platform', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    fs.file('linux/build.sh').createSync(recursive: true);
+    fs.file('pubspec.yaml').createSync();
+    fs.file('.packages').createSync();
+    fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+
+    expect(createTestCommandRunner(command).run(
+      const <String>['build', 'linux']
+    ), throwsA(isInstanceOf<ToolExit>()));
+  }, overrides: <Type, Generator>{
+    Platform: () => notLinuxPlatform,
+    FileSystem: () => MemoryFileSystem(),
+  });
+
+  testUsingContext('Linux build invokes make and writes temporary files', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    fs.file('linux/build.sh').createSync(recursive: true);
+    fs.file('pubspec.yaml').createSync();
+    fs.file('.packages').createSync();
+    fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+
+    when(mockProcessManager.start(<String>[
+      'make',
+      '-C',
+      '/linux',
+    ], runInShell: true)).thenAnswer((Invocation invocation) async {
+      return mockProcess;
+    });
+
+    await createTestCommandRunner(command).run(
+      const <String>['build', 'linux']
+    );
+    expect(fs.file('linux/flutter/generated_config').existsSync(), true);
+  }, overrides: <Type, Generator>{
+    FileSystem: () => MemoryFileSystem(),
+    ProcessManager: () => mockProcessManager,
+    Platform: () => linuxPlatform,
+  });
+
+  testUsingContext('linux can extract binary name from Makefile', () async {
+    fs.file('linux/Makefile')
+      ..createSync(recursive: true)
+      ..writeAsStringSync(r'''
+# Comment
+SOMETHING_ELSE=FOO
+BINARY_NAME=fizz_bar
+''');
+    fs.file('pubspec.yaml').createSync();
+    fs.file('.packages').createSync();
+    final FlutterProject flutterProject = FlutterProject.current();
+
+    expect(makefileExecutableName(flutterProject.linux), 'fizz_bar');
+  }, overrides: <Type, Generator>{FileSystem: () => MemoryFileSystem()});
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
+class MockProcess extends Mock implements Process {}
+class MockPlatform extends Mock implements Platform {
+  @override
+  Map<String, String> environment = <String, String>{
+    'FLUTTER_ROOT': '/',
+  };
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_macos_test.dart b/packages/flutter_tools/test/general.shard/commands/build_macos_test.dart
new file mode 100644
index 0000000..ceb07b8
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_macos_test.dart
@@ -0,0 +1,117 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  MockProcessManager mockProcessManager;
+  MemoryFileSystem memoryFilesystem;
+  MockProcess mockProcess;
+  MockPlatform macosPlatform;
+  MockPlatform notMacosPlatform;
+
+  setUpAll(() {
+    Cache.disableLocking();
+  });
+
+  setUp(() {
+    mockProcessManager = MockProcessManager();
+    memoryFilesystem = MemoryFileSystem();
+    mockProcess = MockProcess();
+    macosPlatform = MockPlatform();
+    notMacosPlatform = MockPlatform();
+    when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
+    return 0;
+    });
+    when(mockProcess.stderr).thenAnswer((Invocation invocation) {
+      return const Stream<List<int>>.empty();
+    });
+    when(mockProcess.stdout).thenAnswer((Invocation invocation) {
+      return const Stream<List<int>>.empty();
+    });
+    when(macosPlatform.isMacOS).thenReturn(true);
+    when(notMacosPlatform.isMacOS).thenReturn(false);
+  });
+
+  testUsingContext('macOS build fails when there is no macos project', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    expect(createTestCommandRunner(command).run(
+      const <String>['build', 'macos']
+    ), throwsA(isInstanceOf<ToolExit>()));
+  }, overrides: <Type, Generator>{
+    Platform: () => macosPlatform,
+  });
+
+  testUsingContext('macOS build fails on non-macOS platform', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    fs.file('pubspec.yaml').createSync();
+    fs.file('.packages').createSync();
+    fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+
+    expect(createTestCommandRunner(command).run(
+      const <String>['build', 'macos']
+    ), throwsA(isInstanceOf<ToolExit>()));
+  }, overrides: <Type, Generator>{
+    Platform: () => notMacosPlatform,
+    FileSystem: () => memoryFilesystem,
+  });
+
+  testUsingContext('macOS build invokes build script', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    fs.directory('macos').createSync();
+    fs.file('pubspec.yaml').createSync();
+    fs.file('.packages').createSync();
+    fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+    final FlutterProject flutterProject = FlutterProject.fromDirectory(fs.currentDirectory);
+    final Directory flutterBuildDir = fs.directory(getMacOSBuildDirectory());
+
+    when(mockProcessManager.start(<String>[
+      '/usr/bin/env',
+      'xcrun',
+      'xcodebuild',
+      '-workspace', flutterProject.macos.xcodeWorkspace.path,
+      '-configuration', 'Debug',
+      '-scheme', 'Runner',
+      '-derivedDataPath', flutterBuildDir.absolute.path,
+      'OBJROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}',
+      'SYMROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Products')}',
+    ], runInShell: true)).thenAnswer((Invocation invocation) async {
+      return mockProcess;
+    });
+
+    await createTestCommandRunner(command).run(
+      const <String>['build', 'macos']
+    );
+  }, overrides: <Type, Generator>{
+    FileSystem: () => memoryFilesystem,
+    ProcessManager: () => mockProcessManager,
+    Platform: () => macosPlatform,
+  });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
+class MockProcess extends Mock implements Process {}
+class MockPlatform extends Mock implements Platform {
+  @override
+  Map<String, String> environment = <String, String>{
+    'FLUTTER_ROOT': '/',
+  };
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_web_test.dart b/packages/flutter_tools/test/general.shard/commands/build_web_test.dart
new file mode 100644
index 0000000..2adb818
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_web_test.dart
@@ -0,0 +1,97 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:flutter_tools/src/resident_runner.dart';
+import 'package:flutter_tools/src/resident_web_runner.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:flutter_tools/src/web/compile.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/testbed.dart';
+
+void main() {
+  MockWebCompilationProxy mockWebCompilationProxy;
+  Testbed testbed;
+  MockPlatform mockPlatform;
+
+  setUpAll(() {
+    Cache.disableLocking();
+  });
+
+  setUp(() {
+    mockWebCompilationProxy = MockWebCompilationProxy();
+    testbed = Testbed(setup: () {
+      fs.file('pubspec.yaml')
+        ..createSync()
+        ..writeAsStringSync('name: foo\n');
+      fs.file('.packages').createSync();
+      fs.file(fs.path.join('web', 'index.html')).createSync(recursive: true);
+      fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+      when(mockWebCompilationProxy.initialize(
+        projectDirectory: anyNamed('projectDirectory'),
+        release: anyNamed('release')
+      )).thenAnswer((Invocation invocation) {
+        final String path = fs.path.join('.dart_tool', 'build', 'flutter_web', 'foo', 'lib', 'main_web_entrypoint.dart.js');
+        fs.file(path).createSync(recursive: true);
+        fs.file('$path.map').createSync();
+        return Future<bool>.value(true);
+      });
+    }, overrides: <Type, Generator>{
+      WebCompilationProxy: () => mockWebCompilationProxy,
+      Platform: () => mockPlatform,
+      FlutterVersion: () => MockFlutterVersion(),
+    });
+  });
+
+  test('Refuses to build for web when missing index.html', () => testbed.run(() async {
+    fs.file(fs.path.join('web', 'index.html')).deleteSync();
+
+    expect(buildWeb(
+      FlutterProject.current(),
+      fs.path.join('lib', 'main.dart'),
+      BuildInfo.debug,
+    ), throwsA(isInstanceOf<ToolExit>()));
+  }));
+
+  test('Refuses to build using runner when missing index.html', () => testbed.run(() async {
+    fs.file(fs.path.join('web', 'index.html')).deleteSync();
+
+    final ResidentWebRunner runner = ResidentWebRunner(
+      <FlutterDevice>[],
+      flutterProject: FlutterProject.current(),
+      ipv6: false,
+      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
+    );
+    expect(await runner.run(), 1);
+  }));
+
+  test('Can build for web', () => testbed.run(() async {
+
+    await buildWeb(
+      FlutterProject.current(),
+      fs.path.join('lib', 'main.dart'),
+      BuildInfo.debug,
+    );
+  }));
+}
+
+class MockWebCompilationProxy extends Mock implements WebCompilationProxy {}
+class MockPlatform extends Mock implements Platform {
+  @override
+  Map<String, String> environment = <String, String>{
+    'FLUTTER_ROOT': '/',
+  };
+}
+class MockFlutterVersion extends Mock implements FlutterVersion {
+  @override
+  bool get isMaster => true;
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_windows_test.dart b/packages/flutter_tools/test/general.shard/commands/build_windows_test.dart
new file mode 100644
index 0000000..301fa7d
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_windows_test.dart
@@ -0,0 +1,146 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build.dart';
+import 'package:flutter_tools/src/windows/visual_studio.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+import 'package:xml/xml.dart' as xml;
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  MockProcessManager mockProcessManager;
+  MemoryFileSystem memoryFilesystem;
+  MockProcess mockProcess;
+  MockPlatform windowsPlatform;
+  MockPlatform notWindowsPlatform;
+  MockVisualStudio mockVisualStudio;
+  const String solutionPath = r'C:\windows\Runner.sln';
+  const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community';
+  const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat';
+
+  setUpAll(() {
+    Cache.disableLocking();
+  });
+
+  setUp(() {
+    mockProcessManager = MockProcessManager();
+    memoryFilesystem = MemoryFileSystem(style: FileSystemStyle.windows);
+    mockProcess = MockProcess();
+    windowsPlatform = MockPlatform()
+        ..environment['PROGRAMFILES(X86)'] = r'C:\Program Files (x86)\';
+    notWindowsPlatform = MockPlatform();
+    mockVisualStudio = MockVisualStudio();
+    when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
+      return 0;
+    });
+    when(mockProcess.stderr).thenAnswer((Invocation invocation) {
+      return const Stream<List<int>>.empty();
+    });
+    when(mockProcess.stdout).thenAnswer((Invocation invocation) {
+      return const Stream<List<int>>.empty();
+    });
+    when(windowsPlatform.isWindows).thenReturn(true);
+    when(notWindowsPlatform.isWindows).thenReturn(false);
+  });
+
+  testUsingContext('Windows build fails when there is no vcvars64.bat', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    fs.file(solutionPath).createSync(recursive: true);
+    expect(createTestCommandRunner(command).run(
+      const <String>['build', 'windows']
+    ), throwsA(isInstanceOf<ToolExit>()));
+  }, overrides: <Type, Generator>{
+    Platform: () => windowsPlatform,
+    FileSystem: () => memoryFilesystem,
+    VisualStudio: () => mockVisualStudio,
+  });
+
+  testUsingContext('Windows build fails when there is no windows project', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
+    expect(createTestCommandRunner(command).run(
+      const <String>['build', 'windows']
+    ), throwsA(isInstanceOf<ToolExit>()));
+  }, overrides: <Type, Generator>{
+    Platform: () => windowsPlatform,
+    FileSystem: () => memoryFilesystem,
+    VisualStudio: () => mockVisualStudio,
+  });
+
+  testUsingContext('Windows build fails on non windows platform', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    fs.file(solutionPath).createSync(recursive: true);
+    when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
+    fs.file('pubspec.yaml').createSync();
+    fs.file('.packages').createSync();
+    fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+
+    expect(createTestCommandRunner(command).run(
+      const <String>['build', 'windows']
+    ), throwsA(isInstanceOf<ToolExit>()));
+  }, overrides: <Type, Generator>{
+    Platform: () => notWindowsPlatform,
+    FileSystem: () => memoryFilesystem,
+    VisualStudio: () => mockVisualStudio,
+  });
+
+  testUsingContext('Windows build invokes msbuild and writes generated files', () async {
+    final BuildCommand command = BuildCommand();
+    applyMocksToCommand(command);
+    fs.file(solutionPath).createSync(recursive: true);
+    when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
+    fs.file('pubspec.yaml').createSync();
+    fs.file('.packages').createSync();
+    fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+
+    when(mockProcessManager.start(<String>[
+      r'C:\packages\flutter_tools\bin\vs_build.bat',
+      vcvarsPath,
+      fs.path.basename(solutionPath),
+      'Release',
+    ], workingDirectory: fs.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
+      return mockProcess;
+    });
+
+    await createTestCommandRunner(command).run(
+      const <String>['build', 'windows']
+    );
+
+    // Spot-check important elements from the properties file.
+    final File propsFile = fs.file(r'C:\windows\flutter\Generated.props');
+    expect(propsFile.existsSync(), true);
+    final xml.XmlDocument props = xml.parse(propsFile.readAsStringSync());
+    expect(props.findAllElements('PropertyGroup').first.getAttribute('Label'), 'UserMacros');
+    expect(props.findAllElements('ItemGroup').length, 1);
+    expect(props.findAllElements('FLUTTER_ROOT').first.text, r'C:\');
+  }, overrides: <Type, Generator>{
+    FileSystem: () => memoryFilesystem,
+    ProcessManager: () => mockProcessManager,
+    Platform: () => windowsPlatform,
+    VisualStudio: () => mockVisualStudio,
+  });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
+class MockProcess extends Mock implements Process {}
+class MockPlatform extends Mock implements Platform {
+  @override
+  Map<String, String> environment = <String, String>{
+    'FLUTTER_ROOT': r'C:\',
+  };
+}
+class MockVisualStudio extends Mock implements VisualStudio {}
diff --git a/packages/flutter_tools/test/general.shard/commands/clean_test.dart b/packages/flutter_tools/test/general.shard/commands/clean_test.dart
new file mode 100644
index 0000000..85926c7
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/clean_test.dart
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_tools/src/base/config.dart';
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/commands/clean.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+
+void main() {
+  final MockFileSystem mockFileSystem = MockFileSystem();
+  final MockDirectory currentDirectory = MockDirectory();
+  final MockDirectory exampleDirectory = MockDirectory();
+  final MockDirectory buildDirectory = MockDirectory();
+  final MockDirectory dartToolDirectory = MockDirectory();
+  final MockFile pubspec = MockFile();
+  final MockFile examplePubspec = MockFile();
+
+  when(mockFileSystem.currentDirectory).thenReturn(currentDirectory);
+  when(currentDirectory.childDirectory('example')).thenReturn(exampleDirectory);
+  when(currentDirectory.childFile('pubspec.yaml')).thenReturn(pubspec);
+  when(pubspec.path).thenReturn('/test/pubspec.yaml');
+  when(exampleDirectory.childFile('pubspec.yaml')).thenReturn(examplePubspec);
+  when(currentDirectory.childDirectory('.dart_tool')).thenReturn(dartToolDirectory);
+  when(examplePubspec.path).thenReturn('/test/example/pubspec.yaml');
+  when(mockFileSystem.isFileSync('/test/pubspec.yaml')).thenReturn(false);
+  when(mockFileSystem.isFileSync('/test/example/pubspec.yaml')).thenReturn(false);
+  when(mockFileSystem.directory('build')).thenReturn(buildDirectory);
+  when(mockFileSystem.path).thenReturn(fs.path);
+  when(buildDirectory.existsSync()).thenReturn(true);
+  when(dartToolDirectory.existsSync()).thenReturn(true);
+  group(CleanCommand, () {
+    testUsingContext('removes build and .dart_tool directories', () async {
+      await CleanCommand().runCommand();
+      verify(buildDirectory.deleteSync(recursive: true)).called(1);
+      verify(dartToolDirectory.deleteSync(recursive: true)).called(1);
+    }, overrides: <Type, Generator>{
+      FileSystem: () => mockFileSystem,
+      Config: () => null,
+    });
+  });
+}
+
+class MockFileSystem extends Mock implements FileSystem {}
+class MockFile extends Mock implements File {}
+class MockDirectory extends Mock implements Directory {}
diff --git a/packages/flutter_tools/test/general.shard/commands/config_test.dart b/packages/flutter_tools/test/general.shard/commands/config_test.dart
new file mode 100644
index 0000000..b95b4ed
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/config_test.dart
@@ -0,0 +1,56 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'package:flutter_tools/src/android/android_sdk.dart';
+import 'package:flutter_tools/src/android/android_studio.dart';
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/commands/config.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  MockAndroidStudio mockAndroidStudio;
+  MockAndroidSdk mockAndroidSdk;
+
+  setUp(() {
+    mockAndroidStudio = MockAndroidStudio();
+    mockAndroidSdk = MockAndroidSdk();
+  });
+
+  group('config', () {
+    testUsingContext('machine flag', () async {
+      final BufferLogger logger = context.get<Logger>();
+      final ConfigCommand command = ConfigCommand();
+      await command.handleMachine();
+
+      expect(logger.statusText, isNotEmpty);
+      final dynamic jsonObject = json.decode(logger.statusText);
+      expect(jsonObject, isMap);
+
+      expect(jsonObject.containsKey('android-studio-dir'), true);
+      expect(jsonObject['android-studio-dir'], isNotNull);
+
+      expect(jsonObject.containsKey('android-sdk'), true);
+      expect(jsonObject['android-sdk'], isNotNull);
+    }, overrides: <Type, Generator>{
+      AndroidStudio: () => mockAndroidStudio,
+      AndroidSdk: () => mockAndroidSdk,
+    });
+  });
+}
+
+class MockAndroidStudio extends Mock implements AndroidStudio, Comparable<AndroidStudio> {
+  @override
+  String get directory => 'path/to/android/stdio';
+}
+
+class MockAndroidSdk extends Mock implements AndroidSdk {
+  @override
+  String get directory => 'path/to/android/sdk';
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/create_test.dart b/packages/flutter_tools/test/general.shard/commands/create_test.dart
new file mode 100644
index 0000000..7a8c087
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/create_test.dart
@@ -0,0 +1,1266 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This test performs too poorly to run with coverage enabled.
+@Tags(<String>['create', 'no_coverage'])
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/net.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/create.dart';
+import 'package:flutter_tools/src/dart/sdk.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:flutter_tools/src/version.dart';
+
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+
+const String frameworkRevision = '12345678';
+const String frameworkChannel = 'omega';
+final Generator _kNoColorTerminalPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
+final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
+  Platform: _kNoColorTerminalPlatform,
+};
+const String samplesIndexJson = '''[
+  { "id": "sample1" },
+  { "id": "sample2" }
+]''';
+
+void main() {
+  Directory tempDir;
+  Directory projectDir;
+  FlutterVersion mockFlutterVersion;
+  LoggingProcessManager loggingProcessManager;
+
+  setUpAll(() {
+    Cache.disableLocking();
+  });
+
+  setUp(() {
+    loggingProcessManager = LoggingProcessManager();
+    tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_create_test.');
+    projectDir = tempDir.childDirectory('flutter_project');
+    mockFlutterVersion = MockFlutterVersion();
+  });
+
+  tearDown(() {
+    tryToDelete(tempDir);
+  });
+
+  // Verify that we create a default project ('app') that is
+  // well-formed.
+  testUsingContext('can create a default project', () async {
+    await _createAndAnalyzeProject(
+      projectDir,
+      <String>[],
+      <String>[
+        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+        'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+        'flutter_project.iml',
+        'ios/Flutter/AppFrameworkInfo.plist',
+        'ios/Runner/AppDelegate.m',
+        'ios/Runner/GeneratedPluginRegistrant.h',
+        'lib/main.dart',
+      ],
+    );
+    return _runFlutterTest(projectDir);
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('can create a default project if empty directory exists', () async {
+    await projectDir.create(recursive: true);
+    await _createAndAnalyzeProject(
+      projectDir,
+      <String>[],
+      <String>[
+        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+        'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+        'flutter_project.iml',
+        'ios/Flutter/AppFrameworkInfo.plist',
+        'ios/Runner/AppDelegate.m',
+        'ios/Runner/GeneratedPluginRegistrant.h',
+      ],
+    );
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('creates a module project correctly', () async {
+    await _createAndAnalyzeProject(projectDir, <String>[
+      '--template=module',
+    ], <String>[
+      '.android/app/',
+      '.gitignore',
+      '.ios/Flutter',
+      '.metadata',
+      'lib/main.dart',
+      'pubspec.yaml',
+      'README.md',
+      'test/widget_test.dart',
+    ], unexpectedPaths: <String>[
+      'android/',
+      'ios/',
+    ]);
+    return _runFlutterTest(projectDir);
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('cannot create a project if non-empty non-project directory exists with .metadata', () async {
+    await projectDir.absolute.childDirectory('blag').create(recursive: true);
+    await projectDir.absolute.childFile('.metadata').writeAsString('project_type: blag\n');
+    expect(
+        () async => await _createAndAnalyzeProject(projectDir, <String>[], <String>[], unexpectedPaths: <String>[
+              'android/',
+              'ios/',
+              '.android/',
+              '.ios/',
+            ]),
+        throwsToolExit(message: 'Sorry, unable to detect the type of project to recreate'));
+  }, timeout: allowForRemotePubInvocation, overrides: noColorTerminalOverride);
+
+  testUsingContext('Will create an app project if non-empty non-project directory exists without .metadata', () async {
+    await projectDir.absolute.childDirectory('blag').create(recursive: true);
+    await projectDir.absolute.childDirectory('.idea').create(recursive: true);
+    await _createAndAnalyzeProject(projectDir, <String>[], <String>[
+      'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+      'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+      'flutter_project.iml',
+      'ios/Flutter/AppFrameworkInfo.plist',
+      'ios/Runner/AppDelegate.m',
+      'ios/Runner/GeneratedPluginRegistrant.h',
+    ], unexpectedPaths: <String>[
+      '.android/',
+      '.ios/',
+    ]);
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('detects and recreates an app project correctly', () async {
+    await projectDir.absolute.childDirectory('lib').create(recursive: true);
+    await projectDir.absolute.childDirectory('ios').create(recursive: true);
+    await _createAndAnalyzeProject(projectDir, <String>[], <String>[
+      'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+      'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+      'flutter_project.iml',
+      'ios/Flutter/AppFrameworkInfo.plist',
+      'ios/Runner/AppDelegate.m',
+      'ios/Runner/GeneratedPluginRegistrant.h',
+    ], unexpectedPaths: <String>[
+      '.android/',
+      '.ios/',
+    ]);
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('detects and recreates a plugin project correctly', () async {
+    await projectDir.create(recursive: true);
+    await projectDir.absolute.childFile('.metadata').writeAsString('project_type: plugin\n');
+    return _createAndAnalyzeProject(
+      projectDir,
+      <String>[],
+      <String>[
+        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+        'example/ios/Runner/AppDelegate.h',
+        'example/ios/Runner/AppDelegate.m',
+        'example/ios/Runner/main.m',
+        'example/lib/main.dart',
+        'flutter_project.iml',
+        'ios/Classes/FlutterProjectPlugin.h',
+        'ios/Classes/FlutterProjectPlugin.m',
+        'lib/flutter_project.dart',
+      ],
+    );
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('detects and recreates a package project correctly', () async {
+    await projectDir.create(recursive: true);
+    await projectDir.absolute.childFile('.metadata').writeAsString('project_type: package\n');
+    return _createAndAnalyzeProject(
+      projectDir,
+      <String>[],
+      <String>[
+        'lib/flutter_project.dart',
+        'test/flutter_project_test.dart',
+      ],
+      unexpectedPaths: <String>[
+        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+        'example/ios/Runner/AppDelegate.h',
+        'example/ios/Runner/AppDelegate.m',
+        'example/ios/Runner/main.m',
+        'example/lib/main.dart',
+        'ios/Classes/FlutterProjectPlugin.h',
+        'ios/Classes/FlutterProjectPlugin.m',
+        'ios/Runner/AppDelegate.h',
+        'ios/Runner/AppDelegate.m',
+        'ios/Runner/main.m',
+        'lib/main.dart',
+        'test/widget_test.dart',
+      ],
+    );
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('kotlin/swift legacy app project', () async {
+    return _createProject(
+      projectDir,
+      <String>['--no-pub', '--template=app', '--android-language=kotlin', '--ios-language=swift'],
+      <String>[
+        'android/app/src/main/kotlin/com/example/flutter_project/MainActivity.kt',
+        'ios/Runner/AppDelegate.swift',
+        'ios/Runner/Runner-Bridging-Header.h',
+        'lib/main.dart',
+        '.idea/libraries/KotlinJavaRuntime.xml',
+      ],
+      unexpectedPaths: <String>[
+        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+        'ios/Runner/AppDelegate.h',
+        'ios/Runner/AppDelegate.m',
+        'ios/Runner/main.m',
+      ],
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('can create a package project', () async {
+    await _createAndAnalyzeProject(
+      projectDir,
+      <String>['--template=package'],
+      <String>[
+        'lib/flutter_project.dart',
+        'test/flutter_project_test.dart',
+      ],
+      unexpectedPaths: <String>[
+        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+        'example/ios/Runner/AppDelegate.h',
+        'example/ios/Runner/AppDelegate.m',
+        'example/ios/Runner/main.m',
+        'example/lib/main.dart',
+        'ios/Classes/FlutterProjectPlugin.h',
+        'ios/Classes/FlutterProjectPlugin.m',
+        'ios/Runner/AppDelegate.h',
+        'ios/Runner/AppDelegate.m',
+        'ios/Runner/main.m',
+        'lib/main.dart',
+        'test/widget_test.dart',
+      ],
+    );
+    return _runFlutterTest(projectDir);
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('can create a plugin project', () async {
+    await _createAndAnalyzeProject(
+      projectDir,
+      <String>['--template=plugin'],
+      <String>[
+        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+        'example/ios/Runner/AppDelegate.h',
+        'example/ios/Runner/AppDelegate.m',
+        'example/ios/Runner/main.m',
+        'example/lib/main.dart',
+        'flutter_project.iml',
+        'ios/Classes/FlutterProjectPlugin.h',
+        'ios/Classes/FlutterProjectPlugin.m',
+        'lib/flutter_project.dart',
+      ],
+    );
+    return _runFlutterTest(projectDir.childDirectory('example'));
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('kotlin/swift plugin project', () async {
+    return _createProject(
+      projectDir,
+      <String>['--no-pub', '--template=plugin', '-a', 'kotlin', '--ios-language', 'swift'],
+      <String>[
+        'android/src/main/kotlin/com/example/flutter_project/FlutterProjectPlugin.kt',
+        'example/android/app/src/main/kotlin/com/example/flutter_project_example/MainActivity.kt',
+        'example/ios/Runner/AppDelegate.swift',
+        'example/ios/Runner/Runner-Bridging-Header.h',
+        'example/lib/main.dart',
+        'ios/Classes/FlutterProjectPlugin.h',
+        'ios/Classes/FlutterProjectPlugin.m',
+        'ios/Classes/SwiftFlutterProjectPlugin.swift',
+        'lib/flutter_project.dart',
+      ],
+      unexpectedPaths: <String>[
+        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+        'example/ios/Runner/AppDelegate.h',
+        'example/ios/Runner/AppDelegate.m',
+        'example/ios/Runner/main.m',
+      ],
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('plugin project with custom org', () async {
+    return _createProject(
+      projectDir,
+      <String>['--no-pub', '--template=plugin', '--org', 'com.bar.foo'],
+      <String>[
+        'android/src/main/java/com/bar/foo/flutter_project/FlutterProjectPlugin.java',
+        'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java',
+      ],
+      unexpectedPaths: <String>[
+        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+      ],
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('plugin project with valid custom project name', () async {
+    return _createProject(
+      projectDir,
+      <String>['--no-pub', '--template=plugin', '--project-name', 'xyz'],
+      <String>[
+        'android/src/main/java/com/example/xyz/XyzPlugin.java',
+        'example/android/app/src/main/java/com/example/xyz_example/MainActivity.java',
+      ],
+      unexpectedPaths: <String>[
+        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+      ],
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('plugin project with invalid custom project name', () async {
+    expect(
+      () => _createProject(projectDir,
+        <String>['--no-pub', '--template=plugin', '--project-name', 'xyz.xyz'],
+        <String>[],
+      ),
+      throwsToolExit(message: '"xyz.xyz" is not a valid Dart package name.'),
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('legacy app project with-driver-test', () async {
+    return _createAndAnalyzeProject(
+      projectDir,
+      <String>['--with-driver-test', '--template=app'],
+      <String>['lib/main.dart'],
+    );
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('module project with pub', () async {
+    return _createProject(projectDir, <String>[
+      '--template=module',
+    ], <String>[
+      '.android/build.gradle',
+      '.android/Flutter/build.gradle',
+      '.android/Flutter/src/main/AndroidManifest.xml',
+      '.android/Flutter/src/main/java/io/flutter/facade/Flutter.java',
+      '.android/Flutter/src/main/java/io/flutter/facade/FlutterFragment.java',
+      '.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+      '.android/gradle.properties',
+      '.android/gradle/wrapper/gradle-wrapper.jar',
+      '.android/gradle/wrapper/gradle-wrapper.properties',
+      '.android/gradlew',
+      '.android/gradlew.bat',
+      '.android/include_flutter.groovy',
+      '.android/local.properties',
+      '.android/settings.gradle',
+      '.gitignore',
+      '.metadata',
+      '.packages',
+      'lib/main.dart',
+      'pubspec.lock',
+      'pubspec.yaml',
+      'README.md',
+      'test/widget_test.dart',
+    ], unexpectedPaths: <String>[
+      'android/',
+      'ios/',
+    ]);
+  }, timeout: allowForRemotePubInvocation);
+
+
+  testUsingContext('androidx app project', () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', '--androidx', projectDir.path]);
+
+    void expectExists(String relPath) {
+      expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
+    }
+
+    expectExists('android/gradle.properties');
+
+    final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
+
+    expect(actualContents.contains('useAndroidX'), true);
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('non androidx app project', () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', '--no-androidx', projectDir.path]);
+
+    void expectExists(String relPath) {
+      expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
+    }
+
+    expectExists('android/gradle.properties');
+
+    final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
+
+    expect(actualContents.contains('useAndroidX'), false);
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('androidx app module', () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--template=module', '--no-pub', '--androidx', projectDir.path]);
+
+    final FlutterProject project = FlutterProject.fromDirectory(projectDir);
+    expect(
+      project.usesAndroidX,
+      true,
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('non androidx app module', () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--template=module', '--no-pub', '--no-androidx', projectDir.path]);
+
+    final FlutterProject project = FlutterProject.fromDirectory(projectDir);
+    expect(
+      project.usesAndroidX,
+      false,
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('androidx plugin project', () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--androidx', projectDir.path]);
+
+    void expectExists(String relPath) {
+      expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
+    }
+
+    expectExists('android/gradle.properties');
+
+    final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
+
+    expect(actualContents.contains('useAndroidX'), true);
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('non androidx plugin project', () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--no-androidx', projectDir.path]);
+
+    void expectExists(String relPath) {
+      expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
+    }
+
+    expectExists('android/gradle.properties');
+
+    final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
+
+    expect(actualContents.contains('useAndroidX'), false);
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('has correct content and formatting with module template', () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--template=module', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);
+
+    void expectExists(String relPath, [bool expectation = true]) {
+      expect(fs.isFileSync('${projectDir.path}/$relPath'), expectation);
+    }
+
+    expectExists('lib/main.dart');
+    expectExists('test/widget_test.dart');
+
+    final String actualContents = await fs.file(projectDir.path + '/test/widget_test.dart').readAsString();
+
+    expect(actualContents.contains('flutter_test.dart'), true);
+
+    for (FileSystemEntity file in projectDir.listSync(recursive: true)) {
+      if (file is File && file.path.endsWith('.dart')) {
+        final String original = file.readAsStringSync();
+
+        final Process process = await Process.start(
+          sdkBinaryName('dartfmt'),
+          <String>[file.path],
+          workingDirectory: projectDir.path,
+        );
+        final String formatted = await process.stdout.transform(utf8.decoder).join();
+
+        expect(original, formatted, reason: file.path);
+      }
+    }
+
+    await _runFlutterTest(projectDir, target: fs.path.join(projectDir.path, 'test', 'widget_test.dart'));
+
+    // Generated Xcode settings
+    final String xcodeConfigPath = fs.path.join('.ios', 'Flutter', 'Generated.xcconfig');
+    expectExists(xcodeConfigPath);
+    final File xcodeConfigFile = fs.file(fs.path.join(projectDir.path, xcodeConfigPath));
+    final String xcodeConfig = xcodeConfigFile.readAsStringSync();
+    expect(xcodeConfig, contains('FLUTTER_ROOT='));
+    expect(xcodeConfig, contains('FLUTTER_APPLICATION_PATH='));
+    expect(xcodeConfig, contains('FLUTTER_TARGET='));
+    // App identification
+    final String xcodeProjectPath = fs.path.join('.ios', 'Runner.xcodeproj', 'project.pbxproj');
+    expectExists(xcodeProjectPath);
+    final File xcodeProjectFile = fs.file(fs.path.join(projectDir.path, xcodeProjectPath));
+    final String xcodeProject = xcodeProjectFile.readAsStringSync();
+    expect(xcodeProject, contains('PRODUCT_BUNDLE_IDENTIFIER = com.foo.bar.flutterProject'));
+    // Xcode build system
+    final String xcodeWorkspaceSettingsPath = fs.path.join('.ios', 'Runner.xcworkspace', 'xcshareddata', 'WorkspaceSettings.xcsettings');
+    expectExists(xcodeWorkspaceSettingsPath, false);
+
+    final String versionPath = fs.path.join('.metadata');
+    expectExists(versionPath);
+    final String version = fs.file(fs.path.join(projectDir.path, versionPath)).readAsStringSync();
+    expect(version, contains('version:'));
+    expect(version, contains('revision: 12345678'));
+    expect(version, contains('channel: omega'));
+
+    // IntelliJ metadata
+    final String intelliJSdkMetadataPath = fs.path.join('.idea', 'libraries', 'Dart_SDK.xml');
+    expectExists(intelliJSdkMetadataPath);
+    final String sdkMetaContents = fs
+        .file(fs.path.join(
+          projectDir.path,
+          intelliJSdkMetadataPath,
+        ))
+        .readAsStringSync();
+    expect(sdkMetaContents, contains('<root url="file:/'));
+    expect(sdkMetaContents, contains('/bin/cache/dart-sdk/lib/core"'));
+  }, overrides: <Type, Generator>{
+    FlutterVersion: () => mockFlutterVersion,
+    Platform: _kNoColorTerminalPlatform,
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('has correct content and formatting with app template', () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);
+
+    void expectExists(String relPath) {
+      expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
+    }
+
+    expectExists('lib/main.dart');
+    expectExists('test/widget_test.dart');
+
+    for (FileSystemEntity file in projectDir.listSync(recursive: true)) {
+      if (file is File && file.path.endsWith('.dart')) {
+        final String original = file.readAsStringSync();
+
+        final Process process = await Process.start(
+          sdkBinaryName('dartfmt'),
+          <String>[file.path],
+          workingDirectory: projectDir.path,
+        );
+        final String formatted = await process.stdout.transform(utf8.decoder).join();
+
+        expect(original, formatted, reason: file.path);
+      }
+    }
+
+    await _runFlutterTest(projectDir, target: fs.path.join(projectDir.path, 'test', 'widget_test.dart'));
+
+    // Generated Xcode settings
+    final String xcodeConfigPath = fs.path.join('ios', 'Flutter', 'Generated.xcconfig');
+    expectExists(xcodeConfigPath);
+    final File xcodeConfigFile = fs.file(fs.path.join(projectDir.path, xcodeConfigPath));
+    final String xcodeConfig = xcodeConfigFile.readAsStringSync();
+    expect(xcodeConfig, contains('FLUTTER_ROOT='));
+    expect(xcodeConfig, contains('FLUTTER_APPLICATION_PATH='));
+    // App identification
+    final String xcodeProjectPath = fs.path.join('ios', 'Runner.xcodeproj', 'project.pbxproj');
+    expectExists(xcodeProjectPath);
+    final File xcodeProjectFile = fs.file(fs.path.join(projectDir.path, xcodeProjectPath));
+    final String xcodeProject = xcodeProjectFile.readAsStringSync();
+    expect(xcodeProject, contains('PRODUCT_BUNDLE_IDENTIFIER = com.foo.bar.flutterProject'));
+
+    final String versionPath = fs.path.join('.metadata');
+    expectExists(versionPath);
+    final String version = fs.file(fs.path.join(projectDir.path, versionPath)).readAsStringSync();
+    expect(version, contains('version:'));
+    expect(version, contains('revision: 12345678'));
+    expect(version, contains('channel: omega'));
+
+    // IntelliJ metadata
+    final String intelliJSdkMetadataPath = fs.path.join('.idea', 'libraries', 'Dart_SDK.xml');
+    expectExists(intelliJSdkMetadataPath);
+    final String sdkMetaContents = fs
+        .file(fs.path.join(
+          projectDir.path,
+          intelliJSdkMetadataPath,
+        ))
+        .readAsStringSync();
+    expect(sdkMetaContents, contains('<root url="file:/'));
+    expect(sdkMetaContents, contains('/bin/cache/dart-sdk/lib/core"'));
+  }, overrides: <Type, Generator>{
+    FlutterVersion: () => mockFlutterVersion,
+    Platform: _kNoColorTerminalPlatform,
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('has correct application id for android and bundle id for ios', () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    String tmpProjectDir = fs.path.join(tempDir.path, 'hello_flutter');
+    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.example', tmpProjectDir]);
+    FlutterProject project = FlutterProject.fromDirectory(fs.directory(tmpProjectDir));
+    expect(
+        project.ios.productBundleIdentifier,
+        'com.example.helloFlutter',
+    );
+    expect(
+        project.android.applicationId,
+        'com.example.hello_flutter',
+    );
+
+    tmpProjectDir = fs.path.join(tempDir.path, 'test_abc');
+    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'abc^*.1#@', tmpProjectDir]);
+    project = FlutterProject.fromDirectory(fs.directory(tmpProjectDir));
+    expect(
+        project.ios.productBundleIdentifier,
+        'abc.1.testAbc',
+    );
+    expect(
+        project.android.applicationId,
+        'abc.u1.test_abc',
+    );
+
+    tmpProjectDir = fs.path.join(tempDir.path, 'flutter_project');
+    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', '#+^%', tmpProjectDir]);
+    project = FlutterProject.fromDirectory(fs.directory(tmpProjectDir));
+    expect(
+        project.ios.productBundleIdentifier,
+        'flutterProject.untitled',
+    );
+    expect(
+        project.android.applicationId,
+        'flutter_project.untitled',
+    );
+  }, overrides: <Type, Generator>{
+    FlutterVersion: () => mockFlutterVersion,
+    Platform: _kNoColorTerminalPlatform,
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('can re-gen default template over existing project', () async {
+    Cache.flutterRoot = '../..';
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+    await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+    final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+    expect(metadata, contains('project_type: app\n'));
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('can re-gen default template over existing app project with no metadta and detect the type', () async {
+    Cache.flutterRoot = '../..';
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', '--template=app', projectDir.path]);
+
+    // Remove the .metadata to simulate an older instantiation that didn't generate those.
+    fs.file(fs.path.join(projectDir.path, '.metadata')).deleteSync();
+
+    await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+    final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+    expect(metadata, contains('project_type: app\n'));
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('can re-gen app template over existing app project and detect the type', () async {
+    Cache.flutterRoot = '../..';
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', '--template=app', projectDir.path]);
+
+    await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+    final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+    expect(metadata, contains('project_type: app\n'));
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('can re-gen template over existing module project and detect the type', () async {
+    Cache.flutterRoot = '../..';
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', '--template=module', projectDir.path]);
+
+    await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+    final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+    expect(metadata, contains('project_type: module\n'));
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('can re-gen default template over existing plugin project and detect the type', () async {
+    Cache.flutterRoot = '../..';
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
+
+    await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+    final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+    expect(metadata, contains('project_type: plugin'));
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('can re-gen default template over existing package project and detect the type', () async {
+    Cache.flutterRoot = '../..';
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', '--template=package', projectDir.path]);
+
+    await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+    final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+    expect(metadata, contains('project_type: package'));
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('can re-gen module .android/ folder, reusing custom org', () async {
+    await _createProject(
+      projectDir,
+      <String>['--template=module', '--org', 'com.bar.foo'],
+      <String>[],
+    );
+    projectDir.childDirectory('.android').deleteSync(recursive: true);
+    return _createProject(
+      projectDir,
+      <String>[],
+      <String>[
+        '.android/app/src/main/java/com/bar/foo/flutter_project/host/MainActivity.java',
+      ],
+    );
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('can re-gen module .ios/ folder, reusing custom org', () async {
+    await _createProject(
+      projectDir,
+      <String>['--template=module', '--org', 'com.bar.foo'],
+      <String>[],
+    );
+    projectDir.childDirectory('.ios').deleteSync(recursive: true);
+    await _createProject(projectDir, <String>[], <String>[]);
+    final FlutterProject project = FlutterProject.fromDirectory(projectDir);
+    expect(
+      project.ios.productBundleIdentifier,
+      'com.bar.foo.flutterProject',
+    );
+  }, timeout: allowForRemotePubInvocation);
+
+  testUsingContext('can re-gen app android/ folder, reusing custom org', () async {
+    await _createProject(
+      projectDir,
+      <String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
+      <String>[],
+    );
+    projectDir.childDirectory('android').deleteSync(recursive: true);
+    return _createProject(
+      projectDir,
+      <String>['--no-pub'],
+      <String>[
+        'android/app/src/main/java/com/bar/foo/flutter_project/MainActivity.java',
+      ],
+      unexpectedPaths: <String>[
+        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+      ],
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('can re-gen app ios/ folder, reusing custom org', () async {
+    await _createProject(
+      projectDir,
+      <String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
+      <String>[],
+    );
+    projectDir.childDirectory('ios').deleteSync(recursive: true);
+    await _createProject(projectDir, <String>['--no-pub'], <String>[]);
+    final FlutterProject project = FlutterProject.fromDirectory(projectDir);
+    expect(
+      project.ios.productBundleIdentifier,
+      'com.bar.foo.flutterProject',
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('can re-gen plugin ios/ and example/ folders, reusing custom org', () async {
+    await _createProject(
+      projectDir,
+      <String>['--no-pub', '--template=plugin', '--org', 'com.bar.foo'],
+      <String>[],
+    );
+    projectDir.childDirectory('example').deleteSync(recursive: true);
+    projectDir.childDirectory('ios').deleteSync(recursive: true);
+    await _createProject(
+      projectDir,
+      <String>['--no-pub', '--template=plugin'],
+      <String>[
+        'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java',
+        'ios/Classes/FlutterProjectPlugin.h',
+      ],
+      unexpectedPaths: <String>[
+        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+      ],
+    );
+    final FlutterProject project = FlutterProject.fromDirectory(projectDir);
+    expect(
+      project.example.ios.productBundleIdentifier,
+      'com.bar.foo.flutterProjectExample',
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  testUsingContext('fails to re-gen without specified org when org is ambiguous', () async {
+    await _createProject(
+      projectDir,
+      <String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
+      <String>[],
+    );
+    fs.directory(fs.path.join(projectDir.path, 'ios')).deleteSync(recursive: true);
+    await _createProject(
+      projectDir,
+      <String>['--no-pub', '--template=app', '--org', 'com.bar.baz'],
+      <String>[],
+    );
+    expect(
+      () => _createProject(projectDir, <String>[], <String>[]),
+      throwsToolExit(message: 'Ambiguous organization'),
+    );
+  }, timeout: allowForCreateFlutterProject);
+
+  // Verify that we help the user correct an option ordering issue
+  testUsingContext('produces sensible error message', () async {
+    Cache.flutterRoot = '../..';
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    expect(
+      runner.run(<String>['create', projectDir.path, '--pub']),
+      throwsToolExit(exitCode: 2, message: 'Try moving --pub'),
+    );
+  });
+
+  testUsingContext('fails when file exists where output directory should be', () async {
+    Cache.flutterRoot = '../..';
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+    final File existingFile = fs.file(fs.path.join(projectDir.path, 'bad'));
+    if (!existingFile.existsSync()) {
+      existingFile.createSync(recursive: true);
+    }
+    expect(
+      runner.run(<String>['create', existingFile.path]),
+      throwsToolExit(message: 'existing file'),
+    );
+  });
+
+  testUsingContext('fails overwrite when file exists where output directory should be', () async {
+    Cache.flutterRoot = '../..';
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+    final File existingFile = fs.file(fs.path.join(projectDir.path, 'bad'));
+    if (!existingFile.existsSync()) {
+      existingFile.createSync(recursive: true);
+    }
+    expect(
+      runner.run(<String>['create', '--overwrite', existingFile.path]),
+      throwsToolExit(message: 'existing file'),
+    );
+  });
+
+  testUsingContext('overwrites existing directory when requested', () async {
+    Cache.flutterRoot = '../..';
+    final Directory existingDirectory = fs.directory(fs.path.join(projectDir.path, 'bad'));
+    if (!existingDirectory.existsSync()) {
+      existingDirectory.createSync(recursive: true);
+    }
+    final File existingFile = fs.file(fs.path.join(existingDirectory.path, 'lib', 'main.dart'));
+    existingFile.createSync(recursive: true);
+    await _createProject(
+      fs.directory(existingDirectory.path),
+      <String>['--overwrite'],
+      <String>[
+        'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+        'lib/main.dart',
+        'ios/Flutter/AppFrameworkInfo.plist',
+        'ios/Runner/AppDelegate.m',
+        'ios/Runner/GeneratedPluginRegistrant.h',
+      ],
+    );
+  });
+
+  testUsingContext('fails when invalid package name', () async {
+    Cache.flutterRoot = '../..';
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+    expect(
+      runner.run(<String>['create', fs.path.join(projectDir.path, 'invalidName')]),
+      throwsToolExit(message: '"invalidName" is not a valid Dart package name.'),
+    );
+  });
+
+  testUsingContext(
+    'invokes pub offline when requested',
+    () async {
+      Cache.flutterRoot = '../..';
+
+      final CreateCommand command = CreateCommand();
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+
+      await runner.run(<String>['create', '--pub', '--offline', projectDir.path]);
+      expect(loggingProcessManager.commands.first, contains(matches(r'dart-sdk[\\/]bin[\\/]pub')));
+      expect(loggingProcessManager.commands.first, contains('--offline'));
+    },
+    timeout: allowForCreateFlutterProject,
+    overrides: <Type, Generator>{
+      ProcessManager: () => loggingProcessManager,
+    },
+  );
+
+  testUsingContext(
+    'invokes pub online when offline not requested',
+    () async {
+      Cache.flutterRoot = '../..';
+
+      final CreateCommand command = CreateCommand();
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+
+      await runner.run(<String>['create', '--pub', projectDir.path]);
+      expect(loggingProcessManager.commands.first, contains(matches(r'dart-sdk[\\/]bin[\\/]pub')));
+      expect(loggingProcessManager.commands.first, isNot(contains('--offline')));
+    },
+    timeout: allowForCreateFlutterProject,
+    overrides: <Type, Generator>{
+      ProcessManager: () => loggingProcessManager,
+    },
+  );
+
+  testUsingContext('can create a sample-based project', () async {
+    await _createAndAnalyzeProject(
+      projectDir,
+      <String>['--no-pub', '--sample=foo.bar.Baz'],
+      <String>[
+        'lib/main.dart',
+        'flutter_project.iml',
+        'android/app/src/main/AndroidManifest.xml',
+        'ios/Flutter/AppFrameworkInfo.plist',
+      ],
+      unexpectedPaths: <String>['test'],
+    );
+    expect(projectDir.childDirectory('lib').childFile('main.dart').readAsStringSync(),
+      contains('void main() {}'));
+  }, timeout: allowForRemotePubInvocation, overrides: <Type, Generator>{
+    HttpClientFactory: () => () => MockHttpClient(200, result: 'void main() {}'),
+  });
+
+  testUsingContext('can write samples index to disk', () async {
+    final String outputFile = fs.path.join(tempDir.path, 'flutter_samples.json');
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+    final List<String> args = <String>[
+      'create',
+      '--list-samples',
+      outputFile,
+    ];
+
+    await runner.run(args);
+    final File expectedFile = fs.file(outputFile);
+    expect(expectedFile.existsSync(), isTrue);
+    expect(expectedFile.readAsStringSync(), equals(samplesIndexJson));
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () =>
+        () => MockHttpClient(200, result: samplesIndexJson),
+  });
+  testUsingContext('provides an error to the user if samples json download fails', () async {
+    final String outputFile = fs.path.join(tempDir.path, 'flutter_samples.json');
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+    final List<String> args = <String>[
+      'create',
+      '--list-samples',
+      outputFile,
+    ];
+
+    await expectLater(runner.run(args), throwsToolExit(exitCode: 2, message: 'Failed to write samples'));
+    expect(fs.file(outputFile).existsSync(), isFalse);
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () =>
+        () => MockHttpClient(404, result: 'not found'),
+  });
+}
+
+
+Future<void> _createProject(
+  Directory dir,
+  List<String> createArgs,
+  List<String> expectedPaths, {
+  List<String> unexpectedPaths = const <String>[],
+}) async {
+  Cache.flutterRoot = '../../..';
+  final CreateCommand command = CreateCommand();
+  final CommandRunner<void> runner = createTestCommandRunner(command);
+  await runner.run(<String>[
+    'create',
+    ...createArgs,
+    dir.path,
+  ]);
+
+  bool pathExists(String path) {
+    final String fullPath = fs.path.join(dir.path, path);
+    return fs.typeSync(fullPath) != FileSystemEntityType.notFound;
+  }
+
+  final List<String> failures = <String>[];
+  for (String path in expectedPaths) {
+    if (!pathExists(path)) {
+      failures.add('Path "$path" does not exist.');
+    }
+  }
+  for (String path in unexpectedPaths) {
+    if (pathExists(path)) {
+      failures.add('Path "$path" exists when it shouldn\'t.');
+    }
+  }
+  expect(failures, isEmpty, reason: failures.join('\n'));
+}
+
+Future<void> _createAndAnalyzeProject(
+  Directory dir,
+  List<String> createArgs,
+  List<String> expectedPaths, {
+  List<String> unexpectedPaths = const <String>[],
+}) async {
+  await _createProject(dir, createArgs, expectedPaths, unexpectedPaths: unexpectedPaths);
+  await _analyzeProject(dir.path);
+}
+
+Future<void> _analyzeProject(String workingDir) async {
+  final String flutterToolsPath = fs.path.absolute(fs.path.join(
+    'bin',
+    'flutter_tools.dart',
+  ));
+
+  final List<String> args = <String>[
+    ...dartVmFlags,
+    flutterToolsPath,
+    'analyze',
+  ];
+
+  final ProcessResult exec = await Process.run(
+    '$dartSdkPath/bin/dart',
+    args,
+    workingDirectory: workingDir,
+  );
+  if (exec.exitCode != 0) {
+    print(exec.stdout);
+    print(exec.stderr);
+  }
+  expect(exec.exitCode, 0);
+}
+
+Future<void> _runFlutterTest(Directory workingDir, { String target }) async {
+  final String flutterToolsPath = fs.path.absolute(fs.path.join(
+    'bin',
+    'flutter_tools.dart',
+  ));
+
+  // While flutter test does get packages, it doesn't write version
+  // files anymore.
+  await Process.run(
+    '$dartSdkPath/bin/dart',
+    <String>[
+      ...dartVmFlags,
+      flutterToolsPath,
+      'packages',
+      'get',
+    ],
+    workingDirectory: workingDir.path,
+  );
+
+  final List<String> args = <String>[
+    ...dartVmFlags,
+    flutterToolsPath,
+    'test',
+    '--no-color',
+    if (target != null) target,
+  ];
+
+  final ProcessResult exec = await Process.run(
+    '$dartSdkPath/bin/dart',
+    args,
+    workingDirectory: workingDir.path,
+  );
+  if (exec.exitCode != 0) {
+    print(exec.stdout);
+    print(exec.stderr);
+  }
+  expect(exec.exitCode, 0);
+}
+
+class MockFlutterVersion extends Mock implements FlutterVersion {}
+
+/// A ProcessManager that invokes a real process manager, but keeps
+/// track of all commands sent to it.
+class LoggingProcessManager extends LocalProcessManager {
+  List<List<String>> commands = <List<String>>[];
+
+  @override
+  Future<Process> start(
+    List<dynamic> command, {
+    String workingDirectory,
+    Map<String, String> environment,
+    bool includeParentEnvironment = true,
+    bool runInShell = false,
+    ProcessStartMode mode = ProcessStartMode.normal,
+  }) {
+    commands.add(command);
+    return super.start(
+      command,
+      workingDirectory: workingDirectory,
+      environment: environment,
+      includeParentEnvironment: includeParentEnvironment,
+      runInShell: runInShell,
+      mode: mode,
+    );
+  }
+}
+
+class MockHttpClient implements HttpClient {
+  MockHttpClient(this.statusCode, {this.result});
+
+  final int statusCode;
+  final String result;
+
+  @override
+  Future<HttpClientRequest> getUrl(Uri url) async {
+    return MockHttpClientRequest(statusCode, result: result);
+  }
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) {
+    throw 'io.HttpClient - $invocation';
+  }
+}
+
+class MockHttpClientRequest implements HttpClientRequest {
+  MockHttpClientRequest(this.statusCode, {this.result});
+
+  final int statusCode;
+  final String result;
+
+  @override
+  Future<HttpClientResponse> close() async {
+    return MockHttpClientResponse(statusCode, result: result);
+  }
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) {
+    throw 'io.HttpClientRequest - $invocation';
+  }
+}
+
+class MockHttpClientResponse implements HttpClientResponse {
+  MockHttpClientResponse(this.statusCode, {this.result});
+
+  @override
+  final int statusCode;
+
+  final String result;
+
+  @override
+  String get reasonPhrase => '<reason phrase>';
+
+  @override
+  HttpClientResponseCompressionState get compressionState {
+    return HttpClientResponseCompressionState.decompressed;
+  }
+
+  @override
+  StreamSubscription<Uint8List> listen(
+    void onData(Uint8List event), {
+    Function onError,
+    void onDone(),
+    bool cancelOnError,
+  }) {
+    return Stream<Uint8List>.fromIterable(<Uint8List>[Uint8List.fromList(result.codeUnits)])
+      .listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+  }
+
+  @override
+  Future<dynamic> forEach(void Function(Uint8List element) action) {
+    action(Uint8List.fromList(result.codeUnits));
+    return Future<void>.value();
+  }
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) {
+    throw 'io.HttpClientResponse - $invocation';
+  }
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart b/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart
new file mode 100644
index 0000000..735cee5
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart
@@ -0,0 +1,104 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/create.dart';
+import 'package:flutter_tools/src/doctor.dart';
+import 'package:flutter_tools/src/usage.dart';
+
+import '../../src/common.dart';
+import '../../src/testbed.dart';
+
+
+void main() {
+  group('usageValues', () {
+    Testbed testbed;
+
+    setUpAll(() {
+      Cache.disableLocking();
+    });
+
+    setUp(() {
+      testbed = Testbed(setup: () {
+        final List<String> paths = <String>[
+          fs.path.join('flutter', 'packages', 'flutter', 'pubspec.yaml'),
+          fs.path.join('flutter', 'packages', 'flutter_driver', 'pubspec.yaml'),
+          fs.path.join('flutter', 'packages', 'flutter_test', 'pubspec.yaml'),
+          fs.path.join('flutter', 'bin', 'cache', 'artifacts', 'gradle_wrapper', 'wrapper'),
+          fs.path.join('usr', 'local', 'bin', 'adb'),
+          fs.path.join('Android', 'platform-tools', 'foo'),
+        ];
+        for (String path in paths) {
+          fs.file(path).createSync(recursive: true);
+        }
+      }, overrides: <Type, Generator>{
+        DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
+      });
+    });
+
+    test('set template type as usage value', () => testbed.run(() async {
+      final CreateCommand command = CreateCommand();
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+
+      await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=module', 'testy']);
+      expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'module'));
+
+      await runner.run(<String>['create',  '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']);
+      expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'app'));
+
+      await runner.run(<String>['create',  '--flutter-root=flutter', '--no-pub', '--template=package', 'testy']);
+      expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'package'));
+
+      await runner.run(<String>['create',  '--flutter-root=flutter', '--no-pub', '--template=plugin', 'testy']);
+      expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'plugin'));
+    }));
+
+    test('set iOS host language type as usage value', () => testbed.run(() async {
+      final CreateCommand command = CreateCommand();
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+
+      await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']);
+      expect(await command.usageValues, containsPair(kCommandCreateIosLanguage, 'objc'));
+
+      await runner.run(<String>[
+        'create',
+        '--flutter-root=flutter',
+        '--no-pub',
+        '--template=app',
+        '--ios-language=swift',
+        'testy',
+      ]);
+      expect(await command.usageValues, containsPair(kCommandCreateIosLanguage, 'swift'));
+
+    }));
+
+    test('set Android host language type as usage value', () => testbed.run(() async {
+      final CreateCommand command = CreateCommand();
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+
+      await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']);
+      expect(await command.usageValues, containsPair(kCommandCreateAndroidLanguage, 'java'));
+
+      await runner.run(<String>[
+        'create',
+        '--flutter-root=flutter',
+        '--no-pub',
+        '--template=app',
+        '--android-language=kotlin',
+        'testy',
+      ]);
+      expect(await command.usageValues, containsPair(kCommandCreateAndroidLanguage, 'kotlin'));
+    }));
+  });
+}
+
+class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider {
+  @override
+  List<DoctorValidator> get validators => <DoctorValidator>[];
+
+  @override
+  List<Workflow> get workflows => <Workflow>[];
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/daemon_test.dart b/packages/flutter_tools/test/general.shard/commands/daemon_test.dart
new file mode 100644
index 0000000..54aebdb
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/daemon_test.dart
@@ -0,0 +1,323 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter_tools/src/android/android_workflow.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/commands/daemon.dart';
+import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
+import 'package:flutter_tools/src/globals.dart';
+import 'package:flutter_tools/src/ios/ios_workflow.dart';
+import 'package:flutter_tools/src/resident_runner.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  Daemon daemon;
+  NotifyingLogger notifyingLogger;
+
+  group('daemon', () {
+    setUp(() {
+      notifyingLogger = NotifyingLogger();
+    });
+
+    tearDown(() {
+      if (daemon != null)
+        return daemon.shutdown();
+      notifyingLogger.dispose();
+    });
+
+    testUsingContext('daemon.version command should succeed', () async {
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+        commands.stream,
+        responses.add,
+        notifyingLogger: notifyingLogger,
+      );
+      commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.version'});
+      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+      expect(response['id'], 0);
+      expect(response['result'], isNotEmpty);
+      expect(response['result'] is String, true);
+      await responses.close();
+      await commands.close();
+    });
+
+    testUsingContext('printError should send daemon.logMessage event', () async {
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+        commands.stream,
+        responses.add,
+        notifyingLogger: notifyingLogger,
+      );
+      printError('daemon.logMessage test');
+      final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> map) {
+        return map['event'] == 'daemon.logMessage' && map['params']['level'] == 'error';
+      });
+      expect(response['id'], isNull);
+      expect(response['event'], 'daemon.logMessage');
+      final Map<String, String> logMessage = response['params'].cast<String, String>();
+      expect(logMessage['level'], 'error');
+      expect(logMessage['message'], 'daemon.logMessage test');
+      await responses.close();
+      await commands.close();
+    }, overrides: <Type, Generator>{
+      Logger: () => notifyingLogger,
+    });
+
+    testUsingContext('printStatus should log to stdout when logToStdout is enabled', () async {
+      final StringBuffer buffer = StringBuffer();
+
+      await runZoned<Future<void>>(() async {
+        final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+        final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+        daemon = Daemon(
+          commands.stream,
+          responses.add,
+          notifyingLogger: notifyingLogger,
+          logToStdout: true,
+        );
+        printStatus('daemon.logMessage test');
+        // Service the event loop.
+        await Future<void>.value();
+      }, zoneSpecification: ZoneSpecification(print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
+        buffer.writeln(line);
+      }));
+
+      expect(buffer.toString().trim(), 'daemon.logMessage test');
+    }, overrides: <Type, Generator>{
+      Logger: () => notifyingLogger,
+    });
+
+    testUsingContext('daemon.shutdown command should stop daemon', () async {
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+        commands.stream,
+        responses.add,
+        notifyingLogger: notifyingLogger,
+      );
+      commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.shutdown'});
+      return daemon.onExit.then<void>((int code) async {
+        await commands.close();
+        expect(code, 0);
+      });
+    });
+
+    testUsingContext('app.restart without an appId should report an error', () async {
+      final DaemonCommand command = DaemonCommand();
+      applyMocksToCommand(command);
+
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+        commands.stream,
+        responses.add,
+        daemonCommand: command,
+        notifyingLogger: notifyingLogger,
+      );
+
+      commands.add(<String, dynamic>{'id': 0, 'method': 'app.restart'});
+      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+      expect(response['id'], 0);
+      expect(response['error'], contains('appId is required'));
+      await responses.close();
+      await commands.close();
+    });
+
+    testUsingContext('ext.flutter.debugPaint via service extension without an appId should report an error', () async {
+      final DaemonCommand command = DaemonCommand();
+      applyMocksToCommand(command);
+
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+          commands.stream,
+          responses.add,
+          daemonCommand: command,
+          notifyingLogger: notifyingLogger,
+      );
+
+      commands.add(<String, dynamic>{
+        'id': 0,
+        'method': 'app.callServiceExtension',
+        'params': <String, String>{
+          'methodName': 'ext.flutter.debugPaint',
+        },
+      });
+      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+      expect(response['id'], 0);
+      expect(response['error'], contains('appId is required'));
+      await responses.close();
+      await commands.close();
+    });
+
+    testUsingContext('app.stop without appId should report an error', () async {
+      final DaemonCommand command = DaemonCommand();
+      applyMocksToCommand(command);
+
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+        commands.stream,
+        responses.add,
+        daemonCommand: command,
+        notifyingLogger: notifyingLogger,
+      );
+
+      commands.add(<String, dynamic>{'id': 0, 'method': 'app.stop'});
+      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+      expect(response['id'], 0);
+      expect(response['error'], contains('appId is required'));
+      await responses.close();
+      await commands.close();
+    });
+
+    testUsingContext('device.getDevices should respond with list', () async {
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+        commands.stream,
+        responses.add,
+        notifyingLogger: notifyingLogger,
+      );
+      commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
+      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+      expect(response['id'], 0);
+      expect(response['result'], isList);
+      await responses.close();
+      await commands.close();
+    });
+
+    testUsingContext('device.getDevices reports available devices', () async {
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+        commands.stream,
+        responses.add,
+        notifyingLogger: notifyingLogger,
+      );
+      final MockPollingDeviceDiscovery discoverer = MockPollingDeviceDiscovery();
+      daemon.deviceDomain.addDeviceDiscoverer(discoverer);
+      discoverer.addDevice(MockAndroidDevice());
+      commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
+      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+      expect(response['id'], 0);
+      final dynamic result = response['result'];
+      expect(result, isList);
+      expect(result, isNotEmpty);
+      await responses.close();
+      await commands.close();
+    });
+
+    testUsingContext('should send device.added event when device is discovered', () async {
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+          commands.stream,
+          responses.add,
+          notifyingLogger: notifyingLogger,
+      );
+
+      final MockPollingDeviceDiscovery discoverer = MockPollingDeviceDiscovery();
+      daemon.deviceDomain.addDeviceDiscoverer(discoverer);
+      discoverer.addDevice(MockAndroidDevice());
+
+      return await responses.stream.skipWhile(_isConnectedEvent).first.then<void>((Map<String, dynamic> response) async {
+        expect(response['event'], 'device.added');
+        expect(response['params'], isMap);
+
+        final Map<String, dynamic> params = response['params'];
+        expect(params['platform'], isNotEmpty); // the mock device has a platform of 'android-arm'
+
+        await responses.close();
+        await commands.close();
+      });
+    }, overrides: <Type, Generator>{
+      AndroidWorkflow: () => MockAndroidWorkflow(),
+      IOSWorkflow: () => MockIOSWorkflow(),
+      FuchsiaWorkflow: () => MockFuchsiaWorkflow(),
+    });
+
+    testUsingContext('emulator.launch without an emulatorId should report an error', () async {
+      final DaemonCommand command = DaemonCommand();
+      applyMocksToCommand(command);
+
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+        commands.stream,
+        responses.add,
+        daemonCommand: command,
+        notifyingLogger: notifyingLogger,
+      );
+
+      commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.launch'});
+      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+      expect(response['id'], 0);
+      expect(response['error'], contains('emulatorId is required'));
+      await responses.close();
+      await commands.close();
+    });
+
+    testUsingContext('emulator.getEmulators should respond with list', () async {
+      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+      daemon = Daemon(
+        commands.stream,
+        responses.add,
+        notifyingLogger: notifyingLogger,
+      );
+      commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.getEmulators'});
+      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+      expect(response['id'], 0);
+      expect(response['result'], isList);
+      await responses.close();
+      await commands.close();
+    });
+  });
+
+  group('daemon serialization', () {
+    test('OperationResult', () {
+      expect(
+        jsonEncodeObject(OperationResult.ok),
+        '{"code":0,"message":""}',
+      );
+      expect(
+        jsonEncodeObject(OperationResult(1, 'foo')),
+        '{"code":1,"message":"foo"}',
+      );
+    });
+  });
+}
+
+bool _notEvent(Map<String, dynamic> map) => map['event'] == null;
+
+bool _isConnectedEvent(Map<String, dynamic> map) => map['event'] == 'daemon.connected';
+
+class MockFuchsiaWorkflow extends FuchsiaWorkflow {
+  MockFuchsiaWorkflow({ this.canListDevices = true });
+
+  @override
+  final bool canListDevices;
+}
+
+class MockAndroidWorkflow extends AndroidWorkflow {
+  MockAndroidWorkflow({ this.canListDevices = true });
+
+  @override
+  final bool canListDevices;
+}
+
+class MockIOSWorkflow extends IOSWorkflow {
+  MockIOSWorkflow({ this.canListDevices =true });
+
+  @override
+  final bool canListDevices;
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/devices_test.dart b/packages/flutter_tools/test/general.shard/commands/devices_test.dart
new file mode 100644
index 0000000..01bbb9a
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/devices_test.dart
@@ -0,0 +1,71 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:flutter_tools/src/android/android_sdk.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/devices.dart';
+import 'package:flutter_tools/src/device.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  group('devices', () {
+    setUpAll(() {
+      Cache.disableLocking();
+      // TODO(jonahwilliams): adjust the individual tests so they do not
+      // depend on the host environment.
+      debugDisableWebAndDesktop = true;
+    });
+
+    testUsingContext('returns 0 when called', () async {
+      final DevicesCommand command = DevicesCommand();
+      await createTestCommandRunner(command).run(<String>['devices']);
+    });
+
+    testUsingContext('no error when no connected devices', () async {
+      final DevicesCommand command = DevicesCommand();
+      await createTestCommandRunner(command).run(<String>['devices']);
+      expect(testLogger.statusText, contains('No devices detected'));
+    }, overrides: <Type, Generator>{
+      AndroidSdk: () => null,
+      DeviceManager: () => DeviceManager(),
+      ProcessManager: () => MockProcessManager(),
+    });
+  });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {
+  @override
+  Future<ProcessResult> run(
+    List<dynamic> command, {
+    String workingDirectory,
+    Map<String, String> environment,
+    bool includeParentEnvironment = true,
+    bool runInShell = false,
+    Encoding stdoutEncoding = systemEncoding,
+    Encoding stderrEncoding = systemEncoding,
+  }) async {
+    return ProcessResult(0, 0, '', '');
+  }
+
+  @override
+  ProcessResult runSync(
+    List<dynamic> command, {
+    String workingDirectory,
+    Map<String, String> environment,
+    bool includeParentEnvironment = true,
+    bool runInShell = false,
+    Encoding stdoutEncoding = systemEncoding,
+    Encoding stderrEncoding = systemEncoding,
+  }) {
+    return ProcessResult(0, 0, '', '');
+  }
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/doctor_test.dart b/packages/flutter_tools/test/general.shard/commands/doctor_test.dart
new file mode 100644
index 0000000..5aa2a89
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/doctor_test.dart
@@ -0,0 +1,831 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import 'package:flutter_tools/src/artifacts.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/base/terminal.dart';
+import 'package:flutter_tools/src/base/user_messages.dart';
+import 'package:flutter_tools/src/doctor.dart';
+import 'package:flutter_tools/src/globals.dart';
+import 'package:flutter_tools/src/proxy_validator.dart';
+import 'package:flutter_tools/src/vscode/vscode.dart';
+import 'package:flutter_tools/src/vscode/vscode_validator.dart';
+import 'package:flutter_tools/src/usage.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+final Generator _kNoColorOutputPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
+final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
+  Platform: _kNoColorOutputPlatform,
+};
+
+void main() {
+  MockProcessManager mockProcessManager;
+
+  setUp(() {
+    mockProcessManager = MockProcessManager();
+  });
+
+  group('doctor', () {
+    testUsingContext('intellij validator', () async {
+      const String installPath = '/path/to/intelliJ';
+      final ValidationResult result = await IntelliJValidatorTestTarget('Test', installPath).validate();
+      expect(result.type, ValidationType.partial);
+      expect(result.statusInfo, 'version test.test.test');
+      expect(result.messages, hasLength(4));
+
+      ValidationMessage message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('IntelliJ '));
+      expect(message.message, 'IntelliJ at $installPath');
+
+      message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('Dart '));
+      expect(message.message, 'Dart plugin version 162.2485');
+
+      message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
+      expect(message.message, contains('Flutter plugin version 0.1.3'));
+      expect(message.message, contains('recommended minimum version'));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('vs code validator when both installed', () async {
+      final ValidationResult result = await VsCodeValidatorTestTargets.installedWithExtension.validate();
+      expect(result.type, ValidationType.installed);
+      expect(result.statusInfo, 'version 1.2.3');
+      expect(result.messages, hasLength(2));
+
+      ValidationMessage message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
+      expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
+
+      message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
+      expect(message.message, 'Flutter extension version 4.5.6');
+      expect(message.isError, isFalse);
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('vs code validator when 64bit installed', () async {
+      expect(VsCodeValidatorTestTargets.installedWithExtension64bit.title, 'VS Code, 64-bit edition');
+      final ValidationResult result = await VsCodeValidatorTestTargets.installedWithExtension64bit.validate();
+      expect(result.type, ValidationType.installed);
+      expect(result.statusInfo, 'version 1.2.3');
+      expect(result.messages, hasLength(2));
+
+      ValidationMessage message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
+      expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
+
+      message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
+      expect(message.message, 'Flutter extension version 4.5.6');
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('vs code validator when extension missing', () async {
+      final ValidationResult result = await VsCodeValidatorTestTargets.installedWithoutExtension.validate();
+      expect(result.type, ValidationType.partial);
+      expect(result.statusInfo, 'version 1.2.3');
+      expect(result.messages, hasLength(2));
+
+      ValidationMessage message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
+      expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
+
+      message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
+      expect(message.message, startsWith('Flutter extension not installed'));
+      expect(message.isError, isTrue);
+    }, overrides: noColorTerminalOverride);
+  });
+
+  group('proxy validator', () {
+    testUsingContext('does not show if HTTP_PROXY is not set', () {
+      expect(ProxyValidator.shouldShow, isFalse);
+    }, overrides: <Type, Generator>{
+      Platform: () => FakePlatform()..environment = <String, String>{},
+    });
+
+    testUsingContext('does not show if HTTP_PROXY is only whitespace', () {
+      expect(ProxyValidator.shouldShow, isFalse);
+    }, overrides: <Type, Generator>{
+      Platform: () =>
+          FakePlatform()..environment = <String, String>{'HTTP_PROXY': ' '},
+    });
+
+    testUsingContext('shows when HTTP_PROXY is set', () {
+      expect(ProxyValidator.shouldShow, isTrue);
+    }, overrides: <Type, Generator>{
+      Platform: () => FakePlatform()
+        ..environment = <String, String>{'HTTP_PROXY': 'fakeproxy.local'},
+    });
+
+    testUsingContext('shows when http_proxy is set', () {
+      expect(ProxyValidator.shouldShow, isTrue);
+    }, overrides: <Type, Generator>{
+      Platform: () => FakePlatform()
+        ..environment = <String, String>{'http_proxy': 'fakeproxy.local'},
+    });
+
+    testUsingContext('reports success when NO_PROXY is configured correctly', () async {
+      final ValidationResult results = await ProxyValidator().validate();
+      final List<ValidationMessage> issues = results.messages
+          .where((ValidationMessage msg) => msg.isError || msg.isHint)
+          .toList();
+      expect(issues, hasLength(0));
+    }, overrides: <Type, Generator>{
+      Platform: () => FakePlatform()
+        ..environment = <String, String>{
+          'HTTP_PROXY': 'fakeproxy.local',
+          'NO_PROXY': 'localhost,127.0.0.1',
+        },
+    });
+
+    testUsingContext('reports success when no_proxy is configured correctly', () async {
+      final ValidationResult results = await ProxyValidator().validate();
+      final List<ValidationMessage> issues = results.messages
+          .where((ValidationMessage msg) => msg.isError || msg.isHint)
+          .toList();
+      expect(issues, hasLength(0));
+    }, overrides: <Type, Generator>{
+      Platform: () => FakePlatform()
+        ..environment = <String, String>{
+          'http_proxy': 'fakeproxy.local',
+          'no_proxy': 'localhost,127.0.0.1',
+        },
+    });
+
+    testUsingContext('reports issues when NO_PROXY is missing localhost', () async {
+      final ValidationResult results = await ProxyValidator().validate();
+      final List<ValidationMessage> issues = results.messages
+          .where((ValidationMessage msg) => msg.isError || msg.isHint)
+          .toList();
+      expect(issues, isNot(hasLength(0)));
+    }, overrides: <Type, Generator>{
+      Platform: () => FakePlatform()
+        ..environment = <String, String>{
+          'HTTP_PROXY': 'fakeproxy.local',
+          'NO_PROXY': '127.0.0.1',
+        },
+    });
+
+    testUsingContext('reports issues when NO_PROXY is missing 127.0.0.1', () async {
+      final ValidationResult results = await ProxyValidator().validate();
+      final List<ValidationMessage> issues = results.messages
+          .where((ValidationMessage msg) => msg.isError || msg.isHint)
+          .toList();
+      expect(issues, isNot(hasLength(0)));
+    }, overrides: <Type, Generator>{
+      Platform: () => FakePlatform()
+        ..environment = <String, String>{
+          'HTTP_PROXY': 'fakeproxy.local',
+          'NO_PROXY': 'localhost',
+        },
+    });
+  });
+
+  group('doctor with overridden validators', () {
+    testUsingContext('validate non-verbose output format for run without issues', () async {
+      expect(await doctor.diagnose(verbose: false), isTrue);
+      expect(testLogger.statusText, equals(
+              'Doctor summary (to see all details, run flutter doctor -v):\n'
+              '[✓] Passing Validator (with statusInfo)\n'
+              '[✓] Another Passing Validator (with statusInfo)\n'
+              '[✓] Providing validators is fun (with statusInfo)\n'
+              '\n'
+              '• No issues found!\n'
+      ));
+    }, overrides: <Type, Generator>{
+      DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
+      Platform: _kNoColorOutputPlatform,
+    });
+  });
+
+  group('doctor usage params', () {
+    Usage mockUsage;
+
+    setUp(() {
+      mockUsage = MockUsage();
+      when(mockUsage.isFirstRun).thenReturn(true);
+    });
+
+    testUsingContext('contains installed', () async {
+      await doctor.diagnose(verbose: false);
+
+      expect(
+        verify(mockUsage.sendEvent('doctorResult.PassingValidator', captureAny)).captured,
+        <dynamic>['installed', 'installed', 'installed'],
+      );
+    }, overrides: <Type, Generator>{
+      DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
+      Platform: _kNoColorOutputPlatform,
+      Usage: () => mockUsage,
+    });
+
+    testUsingContext('contains installed and partial', () async {
+      await FakePassingDoctor().diagnose(verbose: false);
+
+      expect(
+        verify(mockUsage.sendEvent('doctorResult.PassingValidator', captureAny)).captured,
+        <dynamic>['installed', 'installed'],
+      );
+      expect(
+        verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithHintsOnly', captureAny)).captured,
+        <dynamic>['partial'],
+      );
+      expect(
+        verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithErrors', captureAny)).captured,
+        <dynamic>['partial'],
+      );
+    }, overrides: <Type, Generator>{
+      Platform: _kNoColorOutputPlatform,
+      Usage: () => mockUsage,
+    });
+
+    testUsingContext('contains installed, missing and partial', () async {
+      await FakeDoctor().diagnose(verbose: false);
+
+      expect(
+        verify(mockUsage.sendEvent('doctorResult.PassingValidator', captureAny)).captured,
+        <dynamic>['installed'],
+      );
+      expect(
+        verify(mockUsage.sendEvent('doctorResult.MissingValidator', captureAny)).captured,
+        <dynamic>['missing'],
+      );
+      expect(
+        verify(mockUsage.sendEvent('doctorResult.NotAvailableValidator', captureAny)).captured,
+        <dynamic>['notAvailable'],
+      );
+      expect(
+        verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithHintsOnly', captureAny)).captured,
+        <dynamic>['partial'],
+      );
+      expect(
+        verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithErrors', captureAny)).captured,
+        <dynamic>['partial'],
+      );
+    }, overrides: <Type, Generator>{
+      Platform: _kNoColorOutputPlatform,
+      Usage: () => mockUsage,
+    });
+  });
+
+  group('doctor with fake validators', () {
+    testUsingContext('validate non-verbose output format for run without issues', () async {
+      expect(await FakeQuietDoctor().diagnose(verbose: false), isTrue);
+      expect(testLogger.statusText, equals(
+              'Doctor summary (to see all details, run flutter doctor -v):\n'
+              '[✓] Passing Validator (with statusInfo)\n'
+              '[✓] Another Passing Validator (with statusInfo)\n'
+              '[✓] Validators are fun (with statusInfo)\n'
+              '[✓] Four score and seven validators ago (with statusInfo)\n'
+              '\n'
+              '• No issues found!\n'
+      ));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate non-verbose output format when only one category fails', () async {
+      expect(await FakeSinglePassingDoctor().diagnose(verbose: false), isTrue);
+      expect(testLogger.statusText, equals(
+              'Doctor summary (to see all details, run flutter doctor -v):\n'
+              '[!] Partial Validator with only a Hint\n'
+              '    ! There is a hint here\n'
+              '\n'
+              '! Doctor found issues in 1 category.\n'
+      ));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate non-verbose output format for a passing run', () async {
+      expect(await FakePassingDoctor().diagnose(verbose: false), isTrue);
+      expect(testLogger.statusText, equals(
+              'Doctor summary (to see all details, run flutter doctor -v):\n'
+              '[✓] Passing Validator (with statusInfo)\n'
+              '[!] Partial Validator with only a Hint\n'
+              '    ! There is a hint here\n'
+              '[!] Partial Validator with Errors\n'
+              '    ✗ An error message indicating partial installation\n'
+              '    ! Maybe a hint will help the user\n'
+              '[✓] Another Passing Validator (with statusInfo)\n'
+              '\n'
+              '! Doctor found issues in 2 categories.\n'
+      ));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate non-verbose output format', () async {
+      expect(await FakeDoctor().diagnose(verbose: false), isFalse);
+      expect(testLogger.statusText, equals(
+              'Doctor summary (to see all details, run flutter doctor -v):\n'
+              '[✓] Passing Validator (with statusInfo)\n'
+              '[✗] Missing Validator\n'
+              '    ✗ A useful error message\n'
+              '    ! A hint message\n'
+              '[!] Not Available Validator\n'
+              '    ✗ A useful error message\n'
+              '    ! A hint message\n'
+              '[!] Partial Validator with only a Hint\n'
+              '    ! There is a hint here\n'
+              '[!] Partial Validator with Errors\n'
+              '    ✗ An error message indicating partial installation\n'
+              '    ! Maybe a hint will help the user\n'
+              '\n'
+              '! Doctor found issues in 4 categories.\n'
+      ));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate verbose output format', () async {
+      expect(await FakeDoctor().diagnose(verbose: true), isFalse);
+      expect(testLogger.statusText, equals(
+              '[✓] Passing Validator (with statusInfo)\n'
+              '    • A helpful message\n'
+              '    • A second, somewhat longer helpful message\n'
+              '\n'
+              '[✗] Missing Validator\n'
+              '    ✗ A useful error message\n'
+              '    • A message that is not an error\n'
+              '    ! A hint message\n'
+              '\n'
+              '[!] Not Available Validator\n'
+              '    ✗ A useful error message\n'
+              '    • A message that is not an error\n'
+              '    ! A hint message\n'
+              '\n'
+              '[!] Partial Validator with only a Hint\n'
+              '    ! There is a hint here\n'
+              '    • But there is no error\n'
+              '\n'
+              '[!] Partial Validator with Errors\n'
+              '    ✗ An error message indicating partial installation\n'
+              '    ! Maybe a hint will help the user\n'
+              '    • An extra message with some verbose details\n'
+              '\n'
+              '! Doctor found issues in 4 categories.\n'
+      ));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('gen_snapshot does not work', () async {
+      when(mockProcessManager.runSync(
+        <String>[artifacts.getArtifactPath(Artifact.genSnapshot)],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenReturn(ProcessResult(101, 1, '', ''));
+
+      expect(await FlutterValidatorDoctor().diagnose(verbose: false), isTrue);
+      final List<String> statusLines = testLogger.statusText.split('\n');
+      for (String msg in userMessages.flutterBinariesDoNotRun.split('\n')) {
+        expect(statusLines, contains(contains(msg)));
+      }
+      if (platform.isLinux) {
+        for (String msg in userMessages.flutterBinariesLinuxRepairCommands.split('\n')) {
+          expect(statusLines, contains(contains(msg)));
+        }
+      }
+    }, overrides: <Type, Generator>{
+      OutputPreferences: () => OutputPreferences(wrapText: false),
+      ProcessManager: () => mockProcessManager,
+      Platform: _kNoColorOutputPlatform,
+    });
+  });
+
+  testUsingContext('validate non-verbose output wrapping', () async {
+    expect(await FakeDoctor().diagnose(verbose: false), isFalse);
+    expect(testLogger.statusText, equals(
+        'Doctor summary (to see all\n'
+        'details, run flutter doctor\n'
+        '-v):\n'
+        '[✓] Passing Validator (with\n'
+        '    statusInfo)\n'
+        '[✗] Missing Validator\n'
+        '    ✗ A useful error message\n'
+        '    ! A hint message\n'
+        '[!] Not Available Validator\n'
+        '    ✗ A useful error message\n'
+        '    ! A hint message\n'
+        '[!] Partial Validator with\n'
+        '    only a Hint\n'
+        '    ! There is a hint here\n'
+        '[!] Partial Validator with\n'
+        '    Errors\n'
+        '    ✗ An error message\n'
+        '      indicating partial\n'
+        '      installation\n'
+        '    ! Maybe a hint will help\n'
+        '      the user\n'
+        '\n'
+        '! Doctor found issues in 4\n'
+        '  categories.\n'
+        ''
+    ));
+  }, overrides: <Type, Generator>{
+    OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 30),
+    Platform: _kNoColorOutputPlatform,
+  });
+
+  testUsingContext('validate verbose output wrapping', () async {
+    expect(await FakeDoctor().diagnose(verbose: true), isFalse);
+    expect(testLogger.statusText, equals(
+        '[✓] Passing Validator (with\n'
+        '    statusInfo)\n'
+        '    • A helpful message\n'
+        '    • A second, somewhat\n'
+        '      longer helpful message\n'
+        '\n'
+        '[✗] Missing Validator\n'
+        '    ✗ A useful error message\n'
+        '    • A message that is not an\n'
+        '      error\n'
+        '    ! A hint message\n'
+        '\n'
+        '[!] Not Available Validator\n'
+        '    ✗ A useful error message\n'
+        '    • A message that is not an\n'
+        '      error\n'
+        '    ! A hint message\n'
+        '\n'
+        '[!] Partial Validator with\n'
+        '    only a Hint\n'
+        '    ! There is a hint here\n'
+        '    • But there is no error\n'
+        '\n'
+        '[!] Partial Validator with\n'
+        '    Errors\n'
+        '    ✗ An error message\n'
+        '      indicating partial\n'
+        '      installation\n'
+        '    ! Maybe a hint will help\n'
+        '      the user\n'
+        '    • An extra message with\n'
+        '      some verbose details\n'
+        '\n'
+        '! Doctor found issues in 4\n'
+        '  categories.\n'
+        ''
+    ));
+  }, overrides: <Type, Generator>{
+    OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 30),
+    Platform: _kNoColorOutputPlatform,
+  });
+
+
+  group('doctor with grouped validators', () {
+    testUsingContext('validate diagnose combines validator output', () async {
+      expect(await FakeGroupedDoctor().diagnose(), isTrue);
+      expect(testLogger.statusText, equals(
+              '[✓] Category 1\n'
+              '    • A helpful message\n'
+              '    • A helpful message\n'
+              '\n'
+              '[!] Category 2\n'
+              '    • A helpful message\n'
+              '    ✗ A useful error message\n'
+              '\n'
+              '! Doctor found issues in 1 category.\n'
+      ));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate merging assigns statusInfo and title', () async {
+      // There are two subvalidators. Only the second contains statusInfo.
+      expect(await FakeGroupedDoctorWithStatus().diagnose(), isTrue);
+      expect(testLogger.statusText, equals(
+              '[✓] First validator title (A status message)\n'
+              '    • A helpful message\n'
+              '    • A different message\n'
+              '\n'
+              '• No issues found!\n'
+      ));
+    }, overrides: noColorTerminalOverride);
+  });
+
+
+  group('grouped validator merging results', () {
+    final PassingGroupedValidator installed = PassingGroupedValidator('Category');
+    final PartialGroupedValidator partial = PartialGroupedValidator('Category');
+    final MissingGroupedValidator missing = MissingGroupedValidator('Category');
+
+    testUsingContext('validate installed + installed = installed', () async {
+      expect(await FakeSmallGroupDoctor(installed, installed).diagnose(), isTrue);
+      expect(testLogger.statusText, startsWith('[✓]'));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate installed + partial = partial', () async {
+      expect(await FakeSmallGroupDoctor(installed, partial).diagnose(), isTrue);
+      expect(testLogger.statusText, startsWith('[!]'));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate installed + missing = partial', () async {
+      expect(await FakeSmallGroupDoctor(installed, missing).diagnose(), isTrue);
+      expect(testLogger.statusText, startsWith('[!]'));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate partial + installed = partial', () async {
+      expect(await FakeSmallGroupDoctor(partial, installed).diagnose(), isTrue);
+      expect(testLogger.statusText, startsWith('[!]'));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate partial + partial = partial', () async {
+      expect(await FakeSmallGroupDoctor(partial, partial).diagnose(), isTrue);
+      expect(testLogger.statusText, startsWith('[!]'));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate partial + missing = partial', () async {
+      expect(await FakeSmallGroupDoctor(partial, missing).diagnose(), isTrue);
+      expect(testLogger.statusText, startsWith('[!]'));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate missing + installed = partial', () async {
+      expect(await FakeSmallGroupDoctor(missing, installed).diagnose(), isTrue);
+      expect(testLogger.statusText, startsWith('[!]'));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate missing + partial = partial', () async {
+      expect(await FakeSmallGroupDoctor(missing, partial).diagnose(), isTrue);
+      expect(testLogger.statusText, startsWith('[!]'));
+    }, overrides: noColorTerminalOverride);
+
+    testUsingContext('validate missing + missing = missing', () async {
+      expect(await FakeSmallGroupDoctor(missing, missing).diagnose(), isFalse);
+      expect(testLogger.statusText, startsWith('[✗]'));
+    }, overrides: noColorTerminalOverride);
+  });
+}
+
+class MockUsage extends Mock implements Usage {}
+
+class IntelliJValidatorTestTarget extends IntelliJValidator {
+  IntelliJValidatorTestTarget(String title, String installPath) : super(title, installPath);
+
+  @override
+  String get pluginsPath => fs.path.join('test', 'data', 'intellij', 'plugins');
+
+  @override
+  String get version => 'test.test.test';
+}
+
+class PassingValidator extends DoctorValidator {
+  PassingValidator(String name) : super(name);
+
+  @override
+  Future<ValidationResult> validate() async {
+    final List<ValidationMessage> messages = <ValidationMessage>[];
+    messages.add(ValidationMessage('A helpful message'));
+    messages.add(ValidationMessage('A second, somewhat longer helpful message'));
+    return ValidationResult(ValidationType.installed, messages, statusInfo: 'with statusInfo');
+  }
+}
+
+class MissingValidator extends DoctorValidator {
+  MissingValidator() : super('Missing Validator');
+
+  @override
+  Future<ValidationResult> validate() async {
+    final List<ValidationMessage> messages = <ValidationMessage>[];
+    messages.add(ValidationMessage.error('A useful error message'));
+    messages.add(ValidationMessage('A message that is not an error'));
+    messages.add(ValidationMessage.hint('A hint message'));
+    return ValidationResult(ValidationType.missing, messages);
+  }
+}
+
+class NotAvailableValidator extends DoctorValidator {
+  NotAvailableValidator() : super('Not Available Validator');
+
+  @override
+  Future<ValidationResult> validate() async {
+    final List<ValidationMessage> messages = <ValidationMessage>[];
+    messages.add(ValidationMessage.error('A useful error message'));
+    messages.add(ValidationMessage('A message that is not an error'));
+    messages.add(ValidationMessage.hint('A hint message'));
+    return ValidationResult(ValidationType.notAvailable, messages);
+  }
+}
+
+class PartialValidatorWithErrors extends DoctorValidator {
+  PartialValidatorWithErrors() : super('Partial Validator with Errors');
+
+  @override
+  Future<ValidationResult> validate() async {
+    final List<ValidationMessage> messages = <ValidationMessage>[];
+    messages.add(ValidationMessage.error('An error message indicating partial installation'));
+    messages.add(ValidationMessage.hint('Maybe a hint will help the user'));
+    messages.add(ValidationMessage('An extra message with some verbose details'));
+    return ValidationResult(ValidationType.partial, messages);
+  }
+}
+
+class PartialValidatorWithHintsOnly extends DoctorValidator {
+  PartialValidatorWithHintsOnly() : super('Partial Validator with only a Hint');
+
+  @override
+  Future<ValidationResult> validate() async {
+    final List<ValidationMessage> messages = <ValidationMessage>[];
+    messages.add(ValidationMessage.hint('There is a hint here'));
+    messages.add(ValidationMessage('But there is no error'));
+    return ValidationResult(ValidationType.partial, messages);
+  }
+}
+
+/// A doctor that fails with a missing [ValidationResult].
+class FakeDoctor extends Doctor {
+  List<DoctorValidator> _validators;
+
+  @override
+  List<DoctorValidator> get validators {
+    if (_validators == null) {
+      _validators = <DoctorValidator>[];
+      _validators.add(PassingValidator('Passing Validator'));
+      _validators.add(MissingValidator());
+      _validators.add(NotAvailableValidator());
+      _validators.add(PartialValidatorWithHintsOnly());
+      _validators.add(PartialValidatorWithErrors());
+    }
+    return _validators;
+  }
+}
+
+/// A doctor that should pass, but still has issues in some categories.
+class FakePassingDoctor extends Doctor {
+  List<DoctorValidator> _validators;
+  @override
+  List<DoctorValidator> get validators {
+    if (_validators == null) {
+      _validators = <DoctorValidator>[];
+      _validators.add(PassingValidator('Passing Validator'));
+      _validators.add(PartialValidatorWithHintsOnly());
+      _validators.add(PartialValidatorWithErrors());
+      _validators.add(PassingValidator('Another Passing Validator'));
+    }
+    return _validators;
+  }
+}
+
+/// A doctor that should pass, but still has 1 issue to test the singular of
+/// categories.
+class FakeSinglePassingDoctor extends Doctor {
+  List<DoctorValidator> _validators;
+  @override
+  List<DoctorValidator> get validators {
+    if (_validators == null) {
+      _validators = <DoctorValidator>[];
+      _validators.add(PartialValidatorWithHintsOnly());
+    }
+    return _validators;
+  }
+}
+
+/// A doctor that passes and has no issues anywhere.
+class FakeQuietDoctor extends Doctor {
+  List<DoctorValidator> _validators;
+  @override
+  List<DoctorValidator> get validators {
+    if (_validators == null) {
+      _validators = <DoctorValidator>[];
+      _validators.add(PassingValidator('Passing Validator'));
+      _validators.add(PassingValidator('Another Passing Validator'));
+      _validators.add(PassingValidator('Validators are fun'));
+      _validators.add(PassingValidator('Four score and seven validators ago'));
+    }
+    return _validators;
+  }
+}
+
+/// A DoctorValidatorsProvider that overrides the default validators without
+/// overriding the doctor.
+class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider {
+  @override
+  List<DoctorValidator> get validators {
+    return <DoctorValidator>[
+      PassingValidator('Passing Validator'),
+      PassingValidator('Another Passing Validator'),
+      PassingValidator('Providing validators is fun'),
+    ];
+  }
+
+  @override
+  List<Workflow> get workflows => <Workflow>[];
+}
+
+class PassingGroupedValidator extends DoctorValidator {
+  PassingGroupedValidator(String name) : super(name);
+
+  @override
+  Future<ValidationResult> validate() async {
+    final List<ValidationMessage> messages = <ValidationMessage>[];
+    messages.add(ValidationMessage('A helpful message'));
+    return ValidationResult(ValidationType.installed, messages);
+  }
+}
+
+class MissingGroupedValidator extends DoctorValidator {
+  MissingGroupedValidator(String name) : super(name);
+
+  @override
+  Future<ValidationResult> validate() async {
+    final List<ValidationMessage> messages = <ValidationMessage>[];
+    messages.add(ValidationMessage.error('A useful error message'));
+    return ValidationResult(ValidationType.missing, messages);
+  }
+}
+
+class PartialGroupedValidator extends DoctorValidator {
+  PartialGroupedValidator(String name) : super(name);
+
+  @override
+  Future<ValidationResult> validate() async {
+    final List<ValidationMessage> messages = <ValidationMessage>[];
+    messages.add(ValidationMessage.error('An error message for partial installation'));
+    return ValidationResult(ValidationType.partial, messages);
+  }
+}
+
+class PassingGroupedValidatorWithStatus extends DoctorValidator {
+  PassingGroupedValidatorWithStatus(String name) : super(name);
+
+  @override
+  Future<ValidationResult> validate() async {
+    final List<ValidationMessage> messages = <ValidationMessage>[];
+    messages.add(ValidationMessage('A different message'));
+    return ValidationResult(ValidationType.installed, messages, statusInfo: 'A status message');
+  }
+}
+
+/// A doctor that has two groups of two validators each.
+class FakeGroupedDoctor extends Doctor {
+  List<DoctorValidator> _validators;
+  @override
+  List<DoctorValidator> get validators {
+    if (_validators == null) {
+      _validators = <DoctorValidator>[];
+      _validators.add(GroupedValidator(<DoctorValidator>[
+        PassingGroupedValidator('Category 1'),
+        PassingGroupedValidator('Category 1'),
+      ]));
+      _validators.add(GroupedValidator(<DoctorValidator>[
+        PassingGroupedValidator('Category 2'),
+        MissingGroupedValidator('Category 2'),
+      ]));
+    }
+    return _validators;
+  }
+}
+
+class FakeGroupedDoctorWithStatus extends Doctor {
+  List<DoctorValidator> _validators;
+  @override
+  List<DoctorValidator> get validators {
+    _validators ??= <DoctorValidator>[
+      GroupedValidator(<DoctorValidator>[
+        PassingGroupedValidator('First validator title'),
+        PassingGroupedValidatorWithStatus('Second validator title'),
+    ])];
+    return _validators;
+  }
+}
+
+class FlutterValidatorDoctor extends Doctor {
+  List<DoctorValidator> _validators;
+  @override
+  List<DoctorValidator> get validators {
+    _validators ??= <DoctorValidator>[FlutterValidator()];
+    return _validators;
+  }
+}
+
+/// A doctor that takes any two validators. Used to check behavior when
+/// merging ValidationTypes (installed, missing, partial).
+class FakeSmallGroupDoctor extends Doctor {
+  FakeSmallGroupDoctor(DoctorValidator val1, DoctorValidator val2) {
+    _validators = <DoctorValidator>[GroupedValidator(<DoctorValidator>[val1, val2])];
+  }
+
+  List<DoctorValidator> _validators;
+
+  @override
+  List<DoctorValidator> get validators => _validators;
+}
+
+class VsCodeValidatorTestTargets extends VsCodeValidator {
+  VsCodeValidatorTestTargets._(String installDirectory, String extensionDirectory, {String edition})
+    : super(VsCode.fromDirectory(installDirectory, extensionDirectory, edition: edition));
+
+  static VsCodeValidatorTestTargets get installedWithExtension =>
+      VsCodeValidatorTestTargets._(validInstall, validExtensions);
+
+  static VsCodeValidatorTestTargets get installedWithExtension64bit =>
+      VsCodeValidatorTestTargets._(validInstall, validExtensions, edition: '64-bit edition');
+
+  static VsCodeValidatorTestTargets get installedWithoutExtension =>
+      VsCodeValidatorTestTargets._(validInstall, missingExtensions);
+
+  static final String validInstall = fs.path.join('test', 'data', 'vscode', 'application');
+  static final String validExtensions = fs.path.join('test', 'data', 'vscode', 'extensions');
+  static final String missingExtensions = fs.path.join('test', 'data', 'vscode', 'notExtensions');
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
diff --git a/packages/flutter_tools/test/general.shard/commands/drive_test.dart b/packages/flutter_tools/test/general.shard/commands/drive_test.dart
new file mode 100644
index 0000000..f6e39ed
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/drive_test.dart
@@ -0,0 +1,429 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/android/android_device.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/drive.dart';
+import 'package:flutter_tools/src/device.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  group('drive', () {
+    DriveCommand command;
+    Device mockDevice;
+    MemoryFileSystem fs;
+    Directory tempDir;
+
+    void withMockDevice([ Device mock ]) {
+      mockDevice = mock ?? MockDevice();
+      targetDeviceFinder = () async => mockDevice;
+      testDeviceManager.addDevice(mockDevice);
+    }
+
+    setUpAll(() {
+      Cache.disableLocking();
+    });
+
+    setUp(() {
+      command = DriveCommand();
+      applyMocksToCommand(command);
+      fs = MemoryFileSystem();
+      tempDir = fs.systemTempDirectory.createTempSync('flutter_drive_test.');
+      fs.currentDirectory = tempDir;
+      fs.directory('test').createSync();
+      fs.directory('test_driver').createSync();
+      fs.file('pubspec.yaml')..createSync();
+      fs.file('.packages').createSync();
+      setExitFunctionForTests();
+      targetDeviceFinder = () {
+        throw 'Unexpected call to targetDeviceFinder';
+      };
+      appStarter = (DriveCommand command) {
+        throw 'Unexpected call to appStarter';
+      };
+      testRunner = (List<String> testArgs, String observatoryUri) {
+        throw 'Unexpected call to testRunner';
+      };
+      appStopper = (DriveCommand command) {
+        throw 'Unexpected call to appStopper';
+      };
+    });
+
+    tearDown(() {
+      command = null;
+      restoreExitFunction();
+      restoreAppStarter();
+      restoreAppStopper();
+      restoreTestRunner();
+      restoreTargetDeviceFinder();
+      tryToDelete(tempDir);
+    });
+
+    testUsingContext('returns 1 when test file is not found', () async {
+      withMockDevice();
+
+      final String testApp = fs.path.join(tempDir.path, 'test', 'e2e.dart');
+      final String testFile = fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
+      fs.file(testApp).createSync(recursive: true);
+
+      final List<String> args = <String>[
+        'drive',
+        '--target=$testApp',
+      ];
+      try {
+        await createTestCommandRunner(command).run(args);
+        fail('Expect exception');
+      } on ToolExit catch (e) {
+        expect(e.exitCode ?? 1, 1);
+        expect(e.message, contains('Test file not found: $testFile'));
+      }
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+
+    testUsingContext('returns 1 when app fails to run', () async {
+      withMockDevice();
+      appStarter = expectAsync1((DriveCommand command) async => null);
+
+      final String testApp = fs.path.join(tempDir.path, 'test_driver', 'e2e.dart');
+      final String testFile = fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
+
+      final MemoryFileSystem memFs = fs;
+      await memFs.file(testApp).writeAsString('main() { }');
+      await memFs.file(testFile).writeAsString('main() { }');
+
+      final List<String> args = <String>[
+        'drive',
+        '--target=$testApp',
+      ];
+      try {
+        await createTestCommandRunner(command).run(args);
+        fail('Expect exception');
+      } on ToolExit catch (e) {
+        expect(e.exitCode, 1);
+        expect(e.message, contains('Application failed to start. Will not run test. Quitting.'));
+      }
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+
+    testUsingContext('returns 1 when app file is outside package', () async {
+      final String appFile = fs.path.join(tempDir.dirname, 'other_app', 'app.dart');
+      fs.file(appFile).createSync(recursive: true);
+      final List<String> args = <String>[
+        '--no-wrap',
+        'drive',
+        '--target=$appFile',
+      ];
+      try {
+        await createTestCommandRunner(command).run(args);
+        fail('Expect exception');
+      } on ToolExit catch (e) {
+        expect(e.exitCode ?? 1, 1);
+        expect(testLogger.errorText, contains(
+            'Application file $appFile is outside the package directory ${tempDir.path}',
+        ));
+      }
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+
+    testUsingContext('returns 1 when app file is in the root dir', () async {
+      final String appFile = fs.path.join(tempDir.path, 'main.dart');
+      fs.file(appFile).createSync(recursive: true);
+      final List<String> args = <String>[
+        '--no-wrap',
+        'drive',
+        '--target=$appFile',
+      ];
+      try {
+        await createTestCommandRunner(command).run(args);
+        fail('Expect exception');
+      } on ToolExit catch (e) {
+        expect(e.exitCode ?? 1, 1);
+        expect(testLogger.errorText, contains(
+            'Application file main.dart must reside in one of the '
+            'sub-directories of the package structure, not in the root directory.',
+        ));
+      }
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+
+    testUsingContext('returns 0 when test ends successfully', () async {
+      withMockDevice();
+
+      final String testApp = fs.path.join(tempDir.path, 'test', 'e2e.dart');
+      final String testFile = fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
+
+      appStarter = expectAsync1((DriveCommand command) async {
+        return LaunchResult.succeeded();
+      });
+      testRunner = expectAsync2((List<String> testArgs, String observatoryUri) async {
+        expect(testArgs, <String>[testFile]);
+        return null;
+      });
+      appStopper = expectAsync1((DriveCommand command) async {
+        return true;
+      });
+
+      final MemoryFileSystem memFs = fs;
+      await memFs.file(testApp).writeAsString('main() {}');
+      await memFs.file(testFile).writeAsString('main() {}');
+
+      final List<String> args = <String>[
+        'drive',
+        '--target=$testApp',
+      ];
+      await createTestCommandRunner(command).run(args);
+      expect(testLogger.errorText, isEmpty);
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+
+    testUsingContext('returns exitCode set by test runner', () async {
+      withMockDevice();
+
+      final String testApp = fs.path.join(tempDir.path, 'test', 'e2e.dart');
+      final String testFile = fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
+
+      appStarter = expectAsync1((DriveCommand command) async {
+        return LaunchResult.succeeded();
+      });
+      testRunner = (List<String> testArgs, String observatoryUri) async {
+        throwToolExit(null, exitCode: 123);
+      };
+      appStopper = expectAsync1((DriveCommand command) async {
+        return true;
+      });
+
+      final MemoryFileSystem memFs = fs;
+      await memFs.file(testApp).writeAsString('main() {}');
+      await memFs.file(testFile).writeAsString('main() {}');
+
+      final List<String> args = <String>[
+        'drive',
+        '--target=$testApp',
+      ];
+      try {
+        await createTestCommandRunner(command).run(args);
+        fail('Expect exception');
+      } on ToolExit catch (e) {
+        expect(e.exitCode ?? 1, 123);
+        expect(e.message, isNull);
+      }
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+
+    group('findTargetDevice', () {
+      testUsingContext('uses specified device', () async {
+        testDeviceManager.specifiedDeviceId = '123';
+        withMockDevice();
+        when(mockDevice.name).thenReturn('specified-device');
+        when(mockDevice.id).thenReturn('123');
+
+        final Device device = await findTargetDevice();
+        expect(device.name, 'specified-device');
+      }, overrides: <Type, Generator>{
+        FileSystem: () => fs,
+      });
+    });
+
+    void findTargetDeviceOnOperatingSystem(String operatingSystem) {
+      Platform platform() => FakePlatform(operatingSystem: operatingSystem);
+
+      testUsingContext('returns null if no devices found', () async {
+        expect(await findTargetDevice(), isNull);
+      }, overrides: <Type, Generator>{
+        FileSystem: () => fs,
+        Platform: platform,
+      });
+
+      testUsingContext('uses existing Android device', () async {
+        mockDevice = MockAndroidDevice();
+        when(mockDevice.name).thenReturn('mock-android-device');
+        withMockDevice(mockDevice);
+
+        final Device device = await findTargetDevice();
+        expect(device.name, 'mock-android-device');
+      }, overrides: <Type, Generator>{
+        FileSystem: () => fs,
+        Platform: platform,
+      });
+    }
+
+    group('findTargetDevice on Linux', () {
+      findTargetDeviceOnOperatingSystem('linux');
+    });
+
+    group('findTargetDevice on Windows', () {
+      findTargetDeviceOnOperatingSystem('windows');
+    });
+
+    group('findTargetDevice on macOS', () {
+      findTargetDeviceOnOperatingSystem('macos');
+
+      Platform macOsPlatform() => FakePlatform(operatingSystem: 'macos');
+
+      testUsingContext('uses existing simulator', () async {
+        withMockDevice();
+        when(mockDevice.name).thenReturn('mock-simulator');
+        when(mockDevice.isLocalEmulator)
+            .thenAnswer((Invocation invocation) => Future<bool>.value(true));
+
+        final Device device = await findTargetDevice();
+        expect(device.name, 'mock-simulator');
+      }, overrides: <Type, Generator>{
+        FileSystem: () => fs,
+        Platform: macOsPlatform,
+      });
+    });
+
+    group('build arguments', () {
+      String testApp, testFile;
+
+      setUp(() {
+        restoreAppStarter();
+      });
+
+      Future<void> appStarterSetup() async {
+        withMockDevice();
+
+        final MockDeviceLogReader mockDeviceLogReader = MockDeviceLogReader();
+        when(mockDevice.getLogReader()).thenReturn(mockDeviceLogReader);
+        final MockLaunchResult mockLaunchResult = MockLaunchResult();
+        when(mockLaunchResult.started).thenReturn(true);
+        when(mockDevice.startApp(
+            null,
+            mainPath: anyNamed('mainPath'),
+            route: anyNamed('route'),
+            debuggingOptions: anyNamed('debuggingOptions'),
+            platformArgs: anyNamed('platformArgs'),
+            prebuiltApplication: anyNamed('prebuiltApplication'),
+            usesTerminalUi: false,
+        )).thenAnswer((_) => Future<LaunchResult>.value(mockLaunchResult));
+        when(mockDevice.isAppInstalled(any)).thenAnswer((_) => Future<bool>.value(false));
+
+        testApp = fs.path.join(tempDir.path, 'test', 'e2e.dart');
+        testFile = fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
+
+        testRunner = (List<String> testArgs, String observatoryUri) async {
+          throwToolExit(null, exitCode: 123);
+        };
+        appStopper = expectAsync1(
+            (DriveCommand command) async {
+              return true;
+            },
+            count: 2,
+        );
+
+        final MemoryFileSystem memFs = fs;
+        await memFs.file(testApp).writeAsString('main() {}');
+        await memFs.file(testFile).writeAsString('main() {}');
+      }
+
+      testUsingContext('does not use pre-built app if no build arg provided', () async {
+        await appStarterSetup();
+
+        final List<String> args = <String>[
+          'drive',
+          '--target=$testApp',
+        ];
+        try {
+          await createTestCommandRunner(command).run(args);
+        } on ToolExit catch (e) {
+          expect(e.exitCode, 123);
+          expect(e.message, null);
+        }
+        verify(mockDevice.startApp(
+                null,
+                mainPath: anyNamed('mainPath'),
+                route: anyNamed('route'),
+                debuggingOptions: anyNamed('debuggingOptions'),
+                platformArgs: anyNamed('platformArgs'),
+                prebuiltApplication: false,
+                usesTerminalUi: false,
+        ));
+      }, overrides: <Type, Generator>{
+        FileSystem: () => fs,
+      });
+
+      testUsingContext('does not use pre-built app if --build arg provided', () async {
+        await appStarterSetup();
+
+        final List<String> args = <String>[
+          'drive',
+          '--build',
+          '--target=$testApp',
+        ];
+        try {
+          await createTestCommandRunner(command).run(args);
+        } on ToolExit catch (e) {
+          expect(e.exitCode, 123);
+          expect(e.message, null);
+        }
+        verify(mockDevice.startApp(
+                null,
+                mainPath: anyNamed('mainPath'),
+                route: anyNamed('route'),
+                debuggingOptions: anyNamed('debuggingOptions'),
+                platformArgs: anyNamed('platformArgs'),
+                prebuiltApplication: false,
+                usesTerminalUi: false,
+        ));
+      }, overrides: <Type, Generator>{
+        FileSystem: () => fs,
+      });
+
+      testUsingContext('uses prebuilt app if --no-build arg provided', () async {
+        await appStarterSetup();
+
+        final List<String> args = <String>[
+          'drive',
+          '--no-build',
+          '--target=$testApp',
+        ];
+        try {
+          await createTestCommandRunner(command).run(args);
+        } on ToolExit catch (e) {
+          expect(e.exitCode, 123);
+          expect(e.message, null);
+        }
+        verify(mockDevice.startApp(
+                null,
+                mainPath: anyNamed('mainPath'),
+                route: anyNamed('route'),
+                debuggingOptions: anyNamed('debuggingOptions'),
+                platformArgs: anyNamed('platformArgs'),
+                prebuiltApplication: true,
+                usesTerminalUi: false,
+        ));
+      }, overrides: <Type, Generator>{
+        FileSystem: () => fs,
+      });
+    });
+  });
+}
+
+class MockDevice extends Mock implements Device {
+  MockDevice() {
+    when(isSupported()).thenReturn(true);
+  }
+}
+
+class MockAndroidDevice extends Mock implements AndroidDevice { }
+
+class MockLaunchResult extends Mock implements LaunchResult { }
diff --git a/packages/flutter_tools/test/general.shard/commands/format_test.dart b/packages/flutter_tools/test/general.shard/commands/format_test.dart
new file mode 100644
index 0000000..1817bbe
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/format_test.dart
@@ -0,0 +1,78 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/format.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  group('format', () {
+    Directory tempDir;
+
+    setUp(() {
+      Cache.disableLocking();
+      tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_format_test.');
+    });
+
+    tearDown(() {
+      tryToDelete(tempDir);
+    });
+
+    testUsingContext('a file', () async {
+      final String projectPath = await createProject(tempDir);
+
+      final File srcFile = fs.file(fs.path.join(projectPath, 'lib', 'main.dart'));
+      final String original = srcFile.readAsStringSync();
+      srcFile.writeAsStringSync(original.replaceFirst('main()', 'main(  )'));
+
+      final FormatCommand command = FormatCommand();
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+      await runner.run(<String>['format', srcFile.path]);
+
+      final String formatted = srcFile.readAsStringSync();
+      expect(formatted, original);
+    });
+
+    testUsingContext('dry-run', () async {
+      final String projectPath = await createProject(tempDir);
+
+      final File srcFile = fs.file(
+          fs.path.join(projectPath, 'lib', 'main.dart'));
+      final String nonFormatted = srcFile.readAsStringSync().replaceFirst(
+          'main()', 'main(  )');
+      srcFile.writeAsStringSync(nonFormatted);
+
+      final FormatCommand command = FormatCommand();
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+      await runner.run(<String>['format', '--dry-run', srcFile.path]);
+
+      final String shouldNotFormatted = srcFile.readAsStringSync();
+      expect(shouldNotFormatted, nonFormatted);
+    });
+
+    testUsingContext('dry-run with set-exit-if-changed', () async {
+      final String projectPath = await createProject(tempDir);
+
+      final File srcFile = fs.file(
+          fs.path.join(projectPath, 'lib', 'main.dart'));
+      final String nonFormatted = srcFile.readAsStringSync().replaceFirst(
+          'main()', 'main(  )');
+      srcFile.writeAsStringSync(nonFormatted);
+
+      final FormatCommand command = FormatCommand();
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+
+      expect(runner.run(<String>[
+        'format', '--dry-run', '--set-exit-if-changed', srcFile.path,
+      ]), throwsException);
+
+      final String shouldNotFormatted = srcFile.readAsStringSync();
+      expect(shouldNotFormatted, nonFormatted);
+    });
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/ide_config_test.dart b/packages/flutter_tools/test/general.shard/commands/ide_config_test.dart
new file mode 100644
index 0000000..dd6defa
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/ide_config_test.dart
@@ -0,0 +1,324 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/template.dart';
+import 'package:flutter_tools/src/commands/ide_config.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  group('ide_config', () {
+    Directory tempDir;
+    Directory templateDir;
+    Directory intellijDir;
+    Directory toolsDir;
+
+    Map<String, String> _getFilesystemContents([ Directory root ]) {
+      final String tempPath = tempDir.absolute.path;
+      final List<String> paths =
+        (root ?? tempDir).listSync(recursive: true).map((FileSystemEntity entity) {
+          final String relativePath = fs.path.relative(entity.path, from: tempPath);
+          return relativePath;
+        }).toList();
+      final Map<String, String> contents = <String, String>{};
+      for (String path in paths) {
+        final String absPath = fs.path.join(tempPath, path);
+        if (fs.isDirectorySync(absPath)) {
+          contents[path] = 'dir';
+        } else if (fs.isFileSync(absPath)) {
+          contents[path] = fs.file(absPath).readAsStringSync();
+        }
+      }
+      return contents;
+    }
+
+    Map<String, String> _getManifest(Directory base, String marker, { bool isTemplate = false }) {
+      final String basePath = fs.path.relative(base.path, from: tempDir.absolute.path);
+      final String suffix = isTemplate ? Template.copyTemplateExtension : '';
+      return <String, String>{
+        fs.path.join(basePath, '.idea'): 'dir',
+        fs.path.join(basePath, '.idea', 'modules.xml$suffix'): 'modules $marker',
+        fs.path.join(basePath, '.idea', 'vcs.xml$suffix'): 'vcs $marker',
+        fs.path.join(basePath, '.idea', '.name$suffix'):
+            'codeStyleSettings $marker',
+        fs.path.join(basePath, '.idea', 'runConfigurations'): 'dir',
+        fs.path.join(basePath, '.idea', 'runConfigurations', 'hello_world.xml$suffix'):
+            'hello_world $marker',
+        fs.path.join(basePath, 'flutter.iml$suffix'): 'flutter $marker',
+        fs.path.join(basePath, 'packages', 'new', 'deep.iml$suffix'): 'deep $marker',
+      };
+    }
+
+    void _populateDir(Map<String, String> manifest) {
+      for (String key in manifest.keys) {
+        if (manifest[key] == 'dir') {
+          tempDir.childDirectory(key)..createSync(recursive: true);
+        }
+      }
+      for (String key in manifest.keys) {
+        if (manifest[key] != 'dir') {
+          tempDir.childFile(key)
+            ..createSync(recursive: true)
+            ..writeAsStringSync(manifest[key]);
+        }
+      }
+    }
+
+    bool _fileOrDirectoryExists(String path) {
+      final String absPath = fs.path.join(tempDir.absolute.path, path);
+      return fs.file(absPath).existsSync() || fs.directory(absPath).existsSync();
+    }
+
+    Future<void> _updateIdeConfig({
+      Directory dir,
+      List<String> args = const <String>[],
+      Map<String, String> expectedContents = const <String, String>{},
+      List<String> unexpectedPaths = const <String>[],
+    }) async {
+      dir ??= tempDir;
+      final IdeConfigCommand command = IdeConfigCommand();
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+      await runner.run(<String>[
+        '--flutter-root=${tempDir.absolute.path}',
+        'ide-config',
+        ...args,
+      ]);
+
+      for (String path in expectedContents.keys) {
+        final String absPath = fs.path.join(tempDir.absolute.path, path);
+        expect(_fileOrDirectoryExists(fs.path.join(dir.path, path)), true,
+            reason: "$path doesn't exist");
+        if (fs.file(absPath).existsSync()) {
+          expect(fs.file(absPath).readAsStringSync(), equals(expectedContents[path]),
+              reason: "$path contents don't match");
+        }
+      }
+      for (String path in unexpectedPaths) {
+        expect(_fileOrDirectoryExists(fs.path.join(dir.path, path)), false, reason: '$path exists');
+      }
+    }
+
+    setUpAll(() {
+      Cache.disableLocking();
+    });
+
+    setUp(() {
+      tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_ide_config_test.');
+      final Directory packagesDir = tempDir.childDirectory('packages')..createSync(recursive: true);
+      toolsDir = packagesDir.childDirectory('flutter_tools')..createSync();
+      templateDir = toolsDir.childDirectory('ide_templates')..createSync();
+      intellijDir = templateDir.childDirectory('intellij')..createSync();
+    });
+
+    tearDown(() {
+      tryToDelete(tempDir);
+    });
+
+    testUsingContext("doesn't touch existing files without --overwrite", () async {
+      final Map<String, String> templateManifest = _getManifest(
+        intellijDir,
+        'template',
+        isTemplate: true,
+      );
+      final Map<String, String> flutterManifest = _getManifest(
+        tempDir,
+        'existing',
+      );
+      _populateDir(templateManifest);
+      _populateDir(flutterManifest);
+      final Map<String, String> expectedContents = _getFilesystemContents();
+      return _updateIdeConfig(
+        expectedContents: expectedContents,
+      );
+    }, timeout: const Timeout.factor(2.0));
+
+    testUsingContext('creates non-existent files', () async {
+      final Map<String, String> templateManifest = _getManifest(
+        intellijDir,
+        'template',
+        isTemplate: true,
+      );
+      final Map<String, String> flutterManifest = _getManifest(
+        tempDir,
+        'template',
+      );
+      _populateDir(templateManifest);
+      final Map<String, String> expectedContents = <String, String>{
+        ...templateManifest,
+        ...flutterManifest,
+      };
+      return _updateIdeConfig(
+        expectedContents: expectedContents,
+      );
+    }, timeout: const Timeout.factor(2.0));
+
+    testUsingContext('overwrites existing files with --overwrite', () async {
+      final Map<String, String> templateManifest = _getManifest(
+        intellijDir,
+        'template',
+        isTemplate: true,
+      );
+      final Map<String, String> flutterManifest = _getManifest(
+        tempDir,
+        'existing',
+      );
+      _populateDir(templateManifest);
+      _populateDir(flutterManifest);
+      final Map<String, String> overwrittenManifest = _getManifest(
+        tempDir,
+        'template',
+      );
+      final Map<String, String> expectedContents = <String, String>{
+        ...templateManifest,
+        ...overwrittenManifest,
+      };
+      return _updateIdeConfig(
+        args: <String>['--overwrite'],
+        expectedContents: expectedContents,
+      );
+    }, timeout: const Timeout.factor(2.0));
+
+    testUsingContext('only adds new templates without --overwrite', () async {
+      final Map<String, String> templateManifest = _getManifest(
+        intellijDir,
+        'template',
+        isTemplate: true,
+      );
+      final String flutterIml = fs.path.join(
+        'packages',
+        'flutter_tools',
+        'ide_templates',
+        'intellij',
+        'flutter.iml${Template.copyTemplateExtension}',
+      );
+      templateManifest.remove(flutterIml);
+      _populateDir(templateManifest);
+      templateManifest[flutterIml] = 'flutter existing';
+      final Map<String, String> flutterManifest = _getManifest(
+        tempDir,
+        'existing',
+      );
+      _populateDir(flutterManifest);
+      final Map<String, String> expectedContents = <String, String>{
+        ...flutterManifest,
+        ...templateManifest,
+      };
+      return _updateIdeConfig(
+        args: <String>['--update-templates'],
+        expectedContents: expectedContents,
+      );
+    }, timeout: const Timeout.factor(2.0));
+
+    testUsingContext('update all templates with --overwrite', () async {
+      final Map<String, String> templateManifest = _getManifest(
+        intellijDir,
+        'template',
+        isTemplate: true,
+      );
+      _populateDir(templateManifest);
+      final Map<String, String> flutterManifest = _getManifest(
+        tempDir,
+        'existing',
+      );
+      _populateDir(flutterManifest);
+      final Map<String, String> updatedTemplates = _getManifest(
+        intellijDir,
+        'existing',
+        isTemplate: true,
+      );
+      final Map<String, String> expectedContents = <String, String>{
+        ...flutterManifest,
+        ...updatedTemplates,
+      };
+      return _updateIdeConfig(
+        args: <String>['--update-templates', '--overwrite'],
+        expectedContents: expectedContents,
+      );
+    }, timeout: const Timeout.factor(2.0));
+
+    testUsingContext('removes deleted imls with --overwrite', () async {
+      final Map<String, String> templateManifest = _getManifest(
+        intellijDir,
+        'template',
+        isTemplate: true,
+      );
+      _populateDir(templateManifest);
+      final Map<String, String> flutterManifest = _getManifest(
+        tempDir,
+        'existing',
+      );
+      flutterManifest.remove('flutter.iml');
+      _populateDir(flutterManifest);
+      final Map<String, String> updatedTemplates = _getManifest(
+        intellijDir,
+        'existing',
+        isTemplate: true,
+      );
+      final String flutterIml = fs.path.join(
+        'packages',
+        'flutter_tools',
+        'ide_templates',
+        'intellij',
+        'flutter.iml${Template.copyTemplateExtension}',
+      );
+      updatedTemplates.remove(flutterIml);
+      final Map<String, String> expectedContents = <String, String>{
+        ...flutterManifest,
+        ...updatedTemplates,
+      };
+      return _updateIdeConfig(
+        args: <String>['--update-templates', '--overwrite'],
+        expectedContents: expectedContents,
+      );
+    }, timeout: const Timeout.factor(2.0));
+
+    testUsingContext('removes deleted imls with --overwrite, including empty parent dirs', () async {
+      final Map<String, String> templateManifest = _getManifest(
+        intellijDir,
+        'template',
+        isTemplate: true,
+      );
+      _populateDir(templateManifest);
+      final Map<String, String> flutterManifest = _getManifest(
+        tempDir,
+        'existing',
+      );
+      flutterManifest.remove(fs.path.join('packages', 'new', 'deep.iml'));
+      _populateDir(flutterManifest);
+      final Map<String, String> updatedTemplates = _getManifest(
+        intellijDir,
+        'existing',
+        isTemplate: true,
+      );
+      String deepIml = fs.path.join(
+        'packages',
+        'flutter_tools',
+        'ide_templates',
+        'intellij');
+      // Remove the all the dir entries too.
+      updatedTemplates.remove(deepIml);
+      deepIml = fs.path.join(deepIml, 'packages');
+      updatedTemplates.remove(deepIml);
+      deepIml = fs.path.join(deepIml, 'new');
+      updatedTemplates.remove(deepIml);
+      deepIml = fs.path.join(deepIml, 'deep.iml');
+      updatedTemplates.remove(deepIml);
+      final Map<String, String> expectedContents = <String, String>{
+        ...flutterManifest,
+        ...updatedTemplates,
+      };
+      return _updateIdeConfig(
+        args: <String>['--update-templates', '--overwrite'],
+        expectedContents: expectedContents,
+      );
+    }, timeout: const Timeout.factor(2.0));
+
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/install_test.dart b/packages/flutter_tools/test/general.shard/commands/install_test.dart
new file mode 100644
index 0000000..2883259
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/install_test.dart
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/install.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  group('install', () {
+    setUpAll(() {
+      Cache.disableLocking();
+    });
+
+    testUsingContext('returns 0 when Android is connected and ready for an install', () async {
+      final InstallCommand command = InstallCommand();
+      applyMocksToCommand(command);
+
+      final MockAndroidDevice device = MockAndroidDevice();
+      when(device.isAppInstalled(any)).thenAnswer((_) async => false);
+      when(device.installApp(any)).thenAnswer((_) async => true);
+      testDeviceManager.addDevice(device);
+
+      await createTestCommandRunner(command).run(<String>['install']);
+    }, overrides: <Type, Generator>{
+      Cache: () => MockCache(),
+    });
+
+    testUsingContext('returns 0 when iOS is connected and ready for an install', () async {
+      final InstallCommand command = InstallCommand();
+      applyMocksToCommand(command);
+
+      final MockIOSDevice device = MockIOSDevice();
+      when(device.isAppInstalled(any)).thenAnswer((_) async => false);
+      when(device.installApp(any)).thenAnswer((_) async => true);
+      testDeviceManager.addDevice(device);
+
+      await createTestCommandRunner(command).run(<String>['install']);
+    }, overrides: <Type, Generator>{
+      Cache: () => MockCache(),
+    });
+  });
+}
+
+class MockCache extends Mock implements Cache {}
diff --git a/packages/flutter_tools/test/general.shard/commands/packages_test.dart b/packages/flutter_tools/test/general.shard/commands/packages_test.dart
new file mode 100644
index 0000000..8e0269b
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/packages_test.dart
@@ -0,0 +1,448 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart' hide IOSink;
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/utils.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/packages.dart';
+import 'package:flutter_tools/src/usage.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart' show MockProcessManager, MockStdio, PromptingProcess;
+
+class AlwaysTrueBotDetector implements BotDetector {
+  const AlwaysTrueBotDetector();
+
+  @override
+  bool get isRunningOnBot => true;
+}
+
+
+class AlwaysFalseBotDetector implements BotDetector {
+  const AlwaysFalseBotDetector();
+
+  @override
+  bool get isRunningOnBot => false;
+}
+
+
+void main() {
+  Cache.disableLocking();
+  group('packages get/upgrade', () {
+    Directory tempDir;
+
+    setUp(() {
+      tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
+    });
+
+    tearDown(() {
+      tryToDelete(tempDir);
+    });
+
+    Future<String> createProjectWithPlugin(String plugin, { List<String> arguments }) async {
+      final String projectPath = await createProject(tempDir, arguments: arguments);
+      final File pubspec = fs.file(fs.path.join(projectPath, 'pubspec.yaml'));
+      String content = await pubspec.readAsString();
+      content = content.replaceFirst(
+        '\ndependencies:\n',
+        '\ndependencies:\n  $plugin:\n',
+      );
+      await pubspec.writeAsString(content, flush: true);
+      return projectPath;
+    }
+
+    Future<PackagesCommand> runCommandIn(String projectPath, String verb, { List<String> args }) async {
+      final PackagesCommand command = PackagesCommand();
+      final CommandRunner<void> runner = createTestCommandRunner(command);
+      await runner.run(<String>[
+        'packages',
+        verb,
+        ...?args,
+        projectPath,
+      ]);
+      return command;
+    }
+
+    void expectExists(String projectPath, String relPath) {
+      expect(
+        fs.isFileSync(fs.path.join(projectPath, relPath)),
+        true,
+        reason: '$projectPath/$relPath should exist, but does not',
+      );
+    }
+
+    void expectContains(String projectPath, String relPath, String substring) {
+      expectExists(projectPath, relPath);
+      expect(
+        fs.file(fs.path.join(projectPath, relPath)).readAsStringSync(),
+        contains(substring),
+        reason: '$projectPath/$relPath has unexpected content',
+      );
+    }
+
+    void expectNotExists(String projectPath, String relPath) {
+      expect(
+        fs.isFileSync(fs.path.join(projectPath, relPath)),
+        false,
+        reason: '$projectPath/$relPath should not exist, but does',
+      );
+    }
+
+    void expectNotContains(String projectPath, String relPath, String substring) {
+      expectExists(projectPath, relPath);
+      expect(
+        fs.file(fs.path.join(projectPath, relPath)).readAsStringSync(),
+        isNot(contains(substring)),
+        reason: '$projectPath/$relPath has unexpected content',
+      );
+    }
+
+    const List<String> pubOutput = <String>[
+      '.packages',
+      'pubspec.lock',
+    ];
+
+    const List<String> pluginRegistrants = <String>[
+      'ios/Runner/GeneratedPluginRegistrant.h',
+      'ios/Runner/GeneratedPluginRegistrant.m',
+      'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+    ];
+
+    const List<String> modulePluginRegistrants = <String>[
+      '.ios/Flutter/FlutterPluginRegistrant/Classes/GeneratedPluginRegistrant.h',
+      '.ios/Flutter/FlutterPluginRegistrant/Classes/GeneratedPluginRegistrant.m',
+      '.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+    ];
+
+    const List<String> pluginWitnesses = <String>[
+      '.flutter-plugins',
+      'ios/Podfile',
+    ];
+
+    const List<String> modulePluginWitnesses = <String>[
+      '.flutter-plugins',
+      '.ios/Podfile',
+    ];
+
+    const Map<String, String> pluginContentWitnesses = <String, String>{
+      'ios/Flutter/Debug.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
+      'ios/Flutter/Release.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"',
+    };
+
+    const Map<String, String> modulePluginContentWitnesses = <String, String>{
+      '.ios/Config/Debug.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
+      '.ios/Config/Release.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"',
+    };
+
+    void expectDependenciesResolved(String projectPath) {
+      for (String output in pubOutput) {
+        expectExists(projectPath, output);
+      }
+    }
+
+    void expectZeroPluginsInjected(String projectPath) {
+      for (final String registrant in modulePluginRegistrants) {
+        expectExists(projectPath, registrant);
+      }
+      for (final String witness in pluginWitnesses) {
+        expectNotExists(projectPath, witness);
+      }
+      modulePluginContentWitnesses.forEach((String witness, String content) {
+        expectNotContains(projectPath, witness, content);
+      });
+    }
+
+    void expectPluginInjected(String projectPath) {
+      for (final String registrant in pluginRegistrants) {
+        expectExists(projectPath, registrant);
+      }
+      for (final String witness in pluginWitnesses) {
+        expectExists(projectPath, witness);
+      }
+      pluginContentWitnesses.forEach((String witness, String content) {
+        expectContains(projectPath, witness, content);
+      });
+    }
+
+    void expectModulePluginInjected(String projectPath) {
+      for (final String registrant in modulePluginRegistrants) {
+        expectExists(projectPath, registrant);
+      }
+      for (final String witness in modulePluginWitnesses) {
+        expectExists(projectPath, witness);
+      }
+      modulePluginContentWitnesses.forEach((String witness, String content) {
+        expectContains(projectPath, witness, content);
+      });
+    }
+
+    void removeGeneratedFiles(String projectPath) {
+      final Iterable<String> allFiles = <List<String>>[
+        pubOutput,
+        modulePluginRegistrants,
+        pluginWitnesses,
+      ].expand<String>((List<String> list) => list);
+      for (String path in allFiles) {
+        final File file = fs.file(fs.path.join(projectPath, path));
+        if (file.existsSync())
+          file.deleteSync();
+      }
+    }
+
+    testUsingContext('get fetches packages', () async {
+      final String projectPath = await createProject(tempDir,
+        arguments: <String>['--no-pub', '--template=module']);
+      removeGeneratedFiles(projectPath);
+
+      await runCommandIn(projectPath, 'get');
+
+      expectDependenciesResolved(projectPath);
+      expectZeroPluginsInjected(projectPath);
+    }, timeout: allowForRemotePubInvocation);
+
+    testUsingContext('get --offline fetches packages', () async {
+      final String projectPath = await createProject(tempDir,
+        arguments: <String>['--no-pub', '--template=module']);
+      removeGeneratedFiles(projectPath);
+
+      await runCommandIn(projectPath, 'get', args: <String>['--offline']);
+
+      expectDependenciesResolved(projectPath);
+      expectZeroPluginsInjected(projectPath);
+    }, timeout: allowForCreateFlutterProject);
+
+    testUsingContext('set the number of plugins as usage value', () async {
+      final String projectPath = await createProject(tempDir,
+        arguments: <String>['--no-pub', '--template=module']);
+      removeGeneratedFiles(projectPath);
+
+      final PackagesCommand command = await runCommandIn(projectPath, 'get');
+      final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
+
+      expect(await getCommand.usageValues, containsPair(kCommandPackagesNumberPlugins, '0'));
+    }, timeout: allowForCreateFlutterProject);
+
+    testUsingContext('indicate that the project is not a module in usage value', () async {
+      final String projectPath = await createProject(tempDir,
+        arguments: <String>['--no-pub']);
+      removeGeneratedFiles(projectPath);
+
+      final PackagesCommand command = await runCommandIn(projectPath, 'get');
+      final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
+
+      expect(await getCommand.usageValues, containsPair(kCommandPackagesProjectModule, 'false'));
+    }, timeout: allowForCreateFlutterProject);
+
+    testUsingContext('indicate that the project is a module in usage value', () async {
+      final String projectPath = await createProject(tempDir,
+        arguments: <String>['--no-pub', '--template=module']);
+      removeGeneratedFiles(projectPath);
+
+      final PackagesCommand command = await runCommandIn(projectPath, 'get');
+      final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
+
+      expect(await getCommand.usageValues, containsPair(kCommandPackagesProjectModule, 'true'));
+    }, timeout: allowForCreateFlutterProject);
+
+    testUsingContext('upgrade fetches packages', () async {
+      final String projectPath = await createProject(tempDir,
+        arguments: <String>['--no-pub', '--template=module']);
+      removeGeneratedFiles(projectPath);
+
+      await runCommandIn(projectPath, 'upgrade');
+
+      expectDependenciesResolved(projectPath);
+      expectZeroPluginsInjected(projectPath);
+    }, timeout: allowForRemotePubInvocation);
+
+    testUsingContext('get fetches packages and injects plugin', () async {
+      final String projectPath = await createProjectWithPlugin('path_provider',
+        arguments: <String>['--no-pub', '--template=module']);
+      removeGeneratedFiles(projectPath);
+
+      await runCommandIn(projectPath, 'get');
+
+      expectDependenciesResolved(projectPath);
+      expectModulePluginInjected(projectPath);
+    }, timeout: allowForRemotePubInvocation);
+
+    testUsingContext('get fetches packages and injects plugin in plugin project', () async {
+      final String projectPath = await createProject(
+        tempDir,
+        arguments: <String>['--template=plugin', '--no-pub'],
+      );
+      final String exampleProjectPath = fs.path.join(projectPath, 'example');
+      removeGeneratedFiles(projectPath);
+      removeGeneratedFiles(exampleProjectPath);
+
+      await runCommandIn(projectPath, 'get');
+
+      expectDependenciesResolved(projectPath);
+
+      await runCommandIn(exampleProjectPath, 'get');
+
+      expectDependenciesResolved(exampleProjectPath);
+      expectPluginInjected(exampleProjectPath);
+    }, timeout: allowForRemotePubInvocation);
+  });
+
+  group('packages test/pub', () {
+    MockProcessManager mockProcessManager;
+    MockStdio mockStdio;
+
+    setUp(() {
+      mockProcessManager = MockProcessManager();
+      mockStdio = MockStdio();
+    });
+
+    testUsingContext('test without bot', () async {
+      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'test']);
+      final List<String> commands = mockProcessManager.commands;
+      expect(commands, hasLength(3));
+      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+      expect(commands[1], 'run');
+      expect(commands[2], 'test');
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Stdio: () => mockStdio,
+      BotDetector: () => const AlwaysFalseBotDetector(),
+    });
+
+    testUsingContext('test with bot', () async {
+      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'test']);
+      final List<String> commands = mockProcessManager.commands;
+      expect(commands, hasLength(4));
+      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+      expect(commands[1], '--trace');
+      expect(commands[2], 'run');
+      expect(commands[3], 'test');
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Stdio: () => mockStdio,
+      BotDetector: () => const AlwaysTrueBotDetector(),
+    });
+
+    testUsingContext('run', () async {
+      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', '--verbose', 'pub', 'run', '--foo', 'bar']);
+      final List<String> commands = mockProcessManager.commands;
+      expect(commands, hasLength(4));
+      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+      expect(commands[1], 'run');
+      expect(commands[2], '--foo');
+      expect(commands[3], 'bar');
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Stdio: () => mockStdio,
+    });
+
+    testUsingContext('pub publish', () async {
+      final PromptingProcess process = PromptingProcess();
+      mockProcessManager.processFactory = (List<String> commands) => process;
+      final Future<void> runPackages = createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'pub', 'publish']);
+      final Future<void> runPrompt = process.showPrompt('Proceed (y/n)? ', <String>['hello', 'world']);
+      final Future<void> simulateUserInput = Future<void>(() {
+        mockStdio.simulateStdin('y');
+      });
+      await Future.wait<void>(<Future<void>>[runPackages, runPrompt, simulateUserInput]);
+      final List<String> commands = mockProcessManager.commands;
+      expect(commands, hasLength(2));
+      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+      expect(commands[1], 'publish');
+      final List<String> stdout = mockStdio.writtenToStdout;
+      expect(stdout, hasLength(4));
+      expect(stdout.sublist(0, 2), contains('Proceed (y/n)? '));
+      expect(stdout.sublist(0, 2), contains('y\n'));
+      expect(stdout[2], 'hello\n');
+      expect(stdout[3], 'world\n');
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Stdio: () => mockStdio,
+    });
+
+    testUsingContext('publish', () async {
+      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'publish']);
+      final List<String> commands = mockProcessManager.commands;
+      expect(commands, hasLength(3));
+      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+      expect(commands[1], '--trace');
+      expect(commands[2], 'publish');
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Stdio: () => mockStdio,
+      BotDetector: () => const AlwaysTrueBotDetector(),
+    });
+
+    testUsingContext('deps', () async {
+      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'deps']);
+      final List<String> commands = mockProcessManager.commands;
+      expect(commands, hasLength(3));
+      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+      expect(commands[1], '--trace');
+      expect(commands[2], 'deps');
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Stdio: () => mockStdio,
+      BotDetector: () => const AlwaysTrueBotDetector(),
+    });
+
+    testUsingContext('cache', () async {
+      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'cache']);
+      final List<String> commands = mockProcessManager.commands;
+      expect(commands, hasLength(3));
+      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+      expect(commands[1], '--trace');
+      expect(commands[2], 'cache');
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Stdio: () => mockStdio,
+      BotDetector: () => const AlwaysTrueBotDetector(),
+    });
+
+    testUsingContext('version', () async {
+      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'version']);
+      final List<String> commands = mockProcessManager.commands;
+      expect(commands, hasLength(3));
+      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+      expect(commands[1], '--trace');
+      expect(commands[2], 'version');
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Stdio: () => mockStdio,
+      BotDetector: () => const AlwaysTrueBotDetector(),
+    });
+
+    testUsingContext('uploader', () async {
+      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'uploader']);
+      final List<String> commands = mockProcessManager.commands;
+      expect(commands, hasLength(3));
+      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+      expect(commands[1], '--trace');
+      expect(commands[2], 'uploader');
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Stdio: () => mockStdio,
+      BotDetector: () => const AlwaysTrueBotDetector(),
+    });
+
+    testUsingContext('global', () async {
+      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'global', 'list']);
+      final List<String> commands = mockProcessManager.commands;
+      expect(commands, hasLength(4));
+      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+      expect(commands[1], '--trace');
+      expect(commands[2], 'global');
+      expect(commands[3], 'list');
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Stdio: () => mockStdio,
+      BotDetector: () => const AlwaysTrueBotDetector(),
+    });
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/precache_test.dart b/packages/flutter_tools/test/general.shard/commands/precache_test.dart
new file mode 100644
index 0000000..0308da6
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/precache_test.dart
@@ -0,0 +1,87 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/precache.dart';
+import 'package:flutter_tools/src/runner/flutter_command.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  group('precache', () {
+    final MockCache cache = MockCache();
+    Set<DevelopmentArtifact> artifacts;
+
+    when(cache.isUpToDate()).thenReturn(false);
+    when(cache.updateAll(any)).thenAnswer((Invocation invocation) {
+      artifacts = invocation.positionalArguments.first;
+      return Future<void>.value(null);
+    });
+
+    testUsingContext('Adds artifact flags to requested artifacts', () async {
+      final PrecacheCommand command = PrecacheCommand();
+      applyMocksToCommand(command);
+      await createTestCommandRunner(command).run(
+        const <String>['precache', '--ios', '--android', '--web', '--macos', '--linux', '--windows', '--fuchsia']
+      );
+      expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
+        DevelopmentArtifact.universal,
+        DevelopmentArtifact.iOS,
+        DevelopmentArtifact.android,
+        DevelopmentArtifact.web,
+        DevelopmentArtifact.macOS,
+        DevelopmentArtifact.linux,
+        DevelopmentArtifact.windows,
+        DevelopmentArtifact.fuchsia,
+      }));
+    }, overrides: <Type, Generator>{
+      Cache: () => cache,
+    });
+
+    final MockFlutterVersion flutterVersion = MockFlutterVersion();
+    when(flutterVersion.isMaster).thenReturn(false);
+
+    testUsingContext('Adds artifact flags to requested artifacts on stable', () async {
+      // Release lock between test cases.
+      Cache.releaseLockEarly();
+      final PrecacheCommand command = PrecacheCommand();
+      applyMocksToCommand(command);
+      await createTestCommandRunner(command).run(
+       const <String>['precache', '--ios', '--android', '--web', '--macos', '--linux', '--windows', '--fuchsia']
+      );
+     expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
+       DevelopmentArtifact.universal,
+       DevelopmentArtifact.iOS,
+       DevelopmentArtifact.android,
+     }));
+    }, overrides: <Type, Generator>{
+      Cache: () => cache,
+      FlutterVersion: () => flutterVersion,
+    });
+
+    testUsingContext('Downloads artifacts when --force is provided', () async {
+      when(cache.isUpToDate()).thenReturn(true);
+      // Release lock between test cases.
+      Cache.releaseLockEarly();
+      final PrecacheCommand command = PrecacheCommand();
+      applyMocksToCommand(command);
+      await createTestCommandRunner(command).run(const <String>['precache', '--force']);
+      expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
+       DevelopmentArtifact.universal,
+       DevelopmentArtifact.iOS,
+       DevelopmentArtifact.android,
+     }));
+    }, overrides: <Type, Generator>{
+      Cache: () => cache,
+      FlutterVersion: () => flutterVersion,
+    });
+  });
+}
+
+class MockFlutterVersion extends Mock implements FlutterVersion {}
+class MockCache extends Mock implements Cache {}
diff --git a/packages/flutter_tools/test/general.shard/commands/run_test.dart b/packages/flutter_tools/test/general.shard/commands/run_test.dart
new file mode 100644
index 0000000..b743a36
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/run_test.dart
@@ -0,0 +1,262 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/application_package.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/run.dart';
+import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/runner/flutter_command.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  group('run', () {
+    MockApplicationPackageFactory mockApplicationPackageFactory;
+    MockDeviceManager mockDeviceManager;
+    MockFlutterVersion mockStableFlutterVersion;
+    MockFlutterVersion mockUnstableFlutterVersion;
+
+    setUpAll(() {
+      Cache.disableLocking();
+      mockApplicationPackageFactory = MockApplicationPackageFactory();
+      mockDeviceManager = MockDeviceManager();
+      mockStableFlutterVersion = MockFlutterVersion(isStable: true);
+      mockUnstableFlutterVersion = MockFlutterVersion(isStable: false);
+    });
+
+    testUsingContext('fails when target not found', () async {
+      final RunCommand command = RunCommand();
+      applyMocksToCommand(command);
+      try {
+        await createTestCommandRunner(command).run(<String>['run', '-t', 'abc123']);
+        fail('Expect exception');
+      } on ToolExit catch (e) {
+        expect(e.exitCode ?? 1, 1);
+      }
+    });
+
+
+    group('dart-flags option', () {
+      setUpAll(() {
+        when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+          return Stream<Device>.fromIterable(<Device>[
+            FakeDevice(),
+          ]);
+        });
+      });
+
+      RunCommand command;
+      List<String> args;
+      setUp(() {
+        command = TestRunCommand();
+        args = <String> [
+          'run',
+          '--dart-flags', '"--observe"',
+          '--no-hot',
+        ];
+      });
+
+      testUsingContext('is not available on stable channel', () async {
+        // Stable branch.
+        try {
+          await createTestCommandRunner(command).run(args);
+          fail('Expect exception');
+        // ignore: unused_catch_clause
+        } on UsageException catch(e) {
+          // Not available while on stable branch.
+        }
+      }, overrides: <Type, Generator>{
+        DeviceManager: () => mockDeviceManager,
+        FlutterVersion: () => mockStableFlutterVersion,
+      });
+
+      testUsingContext('is populated in debug mode', () async {
+        // FakeDevice.startApp checks that --dart-flags doesn't get dropped and
+        // throws ToolExit with FakeDevice.kSuccess if the flag is populated.
+        try {
+          await createTestCommandRunner(command).run(args);
+          fail('Expect exception');
+        } on ToolExit catch (e) {
+          expect(e.exitCode, FakeDevice.kSuccess);
+        }
+      }, overrides: <Type, Generator>{
+        ApplicationPackageFactory: () => mockApplicationPackageFactory,
+        DeviceManager: () => mockDeviceManager,
+        FlutterVersion: () => mockUnstableFlutterVersion,
+      });
+
+      testUsingContext('is populated in profile mode', () async {
+        args.add('--profile');
+
+        // FakeDevice.startApp checks that --dart-flags doesn't get dropped and
+        // throws ToolExit with FakeDevice.kSuccess if the flag is populated.
+        try {
+          await createTestCommandRunner(command).run(args);
+          fail('Expect exception');
+        } on ToolExit catch (e) {
+          expect(e.exitCode, FakeDevice.kSuccess);
+        }
+      }, overrides: <Type, Generator>{
+        ApplicationPackageFactory: () => mockApplicationPackageFactory,
+        DeviceManager: () => mockDeviceManager,
+        FlutterVersion: () => mockUnstableFlutterVersion,
+      });
+
+      testUsingContext('is not populated in release mode', () async {
+        args.add('--release');
+
+        // FakeDevice.startApp checks that --dart-flags *does* get dropped and
+        // throws ToolExit with FakeDevice.kSuccess if the flag is set to the
+        // empty string.
+        try {
+          await createTestCommandRunner(command).run(args);
+          fail('Expect exception');
+        } on ToolExit catch (e) {
+          expect(e.exitCode, FakeDevice.kSuccess);
+        }
+      }, overrides: <Type, Generator>{
+        ApplicationPackageFactory: () => mockApplicationPackageFactory,
+        DeviceManager: () => mockDeviceManager,
+        FlutterVersion: () => mockUnstableFlutterVersion,
+      });
+    });
+
+    testUsingContext('should only request artifacts corresponding to connected devices', () async {
+      when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+        return Stream<Device>.fromIterable(<Device>[
+          MockDevice(TargetPlatform.android_arm),
+        ]);
+      });
+
+      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
+        DevelopmentArtifact.universal,
+        DevelopmentArtifact.android,
+      }));
+
+      when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+        return Stream<Device>.fromIterable(<Device>[
+          MockDevice(TargetPlatform.ios),
+        ]);
+      });
+
+      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
+        DevelopmentArtifact.universal,
+        DevelopmentArtifact.iOS,
+      }));
+
+      when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+        return Stream<Device>.fromIterable(<Device>[
+          MockDevice(TargetPlatform.ios),
+          MockDevice(TargetPlatform.android_arm),
+        ]);
+      });
+
+      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
+        DevelopmentArtifact.universal,
+        DevelopmentArtifact.iOS,
+        DevelopmentArtifact.android,
+      }));
+
+      when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+        return Stream<Device>.fromIterable(<Device>[
+          MockDevice(TargetPlatform.web_javascript),
+        ]);
+      });
+
+      expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
+        DevelopmentArtifact.universal,
+        DevelopmentArtifact.web,
+      }));
+    }, overrides: <Type, Generator>{
+      DeviceManager: () => mockDeviceManager,
+    });
+  });
+}
+
+class MockDeviceManager extends Mock implements DeviceManager {}
+class MockDevice extends Mock implements Device {
+  MockDevice(this._targetPlatform);
+
+  final TargetPlatform _targetPlatform;
+
+  @override
+  Future<TargetPlatform> get targetPlatform async => _targetPlatform;
+}
+
+class TestRunCommand extends RunCommand {
+  @override
+  // ignore: must_call_super
+  Future<void> validateCommand() async {
+    devices = await deviceManager.getDevices().toList();
+  }
+}
+
+class MockStableFlutterVersion extends MockFlutterVersion {
+  @override
+  bool get isMaster => false;
+}
+
+class FakeDevice extends Fake implements Device {
+  static const int kSuccess = 1;
+  static const int kFailure = -1;
+  final TargetPlatform _targetPlatform = TargetPlatform.ios;
+
+  void _throwToolExit(int code) => throwToolExit(null, exitCode: code);
+
+  @override
+  Future<bool> get isLocalEmulator => Future<bool>.value(false);
+
+  @override
+  bool get supportsHotReload => false;
+
+  @override
+  Future<String> get sdkNameAndVersion => Future<String>.value('');
+
+  @override
+  DeviceLogReader getLogReader({ ApplicationPackage app }) {
+    return MockDeviceLogReader();
+  }
+
+  @override
+  String get name => 'FakeDevice';
+
+  @override
+  Future<TargetPlatform> get targetPlatform async => _targetPlatform;
+
+  @override
+  Future<LaunchResult> startApp(
+    ApplicationPackage package, {
+    String mainPath,
+    String route,
+    DebuggingOptions debuggingOptions,
+    Map<String, dynamic> platformArgs,
+    bool prebuiltApplication = false,
+    bool usesTerminalUi = true,
+    bool ipv6 = false,
+  }) async {
+    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);
+    }
+    return null;
+  }
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/shell_completion_test.dart b/packages/flutter_tools/test/general.shard/commands/shell_completion_test.dart
new file mode 100644
index 0000000..00b4aac
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/shell_completion_test.dart
@@ -0,0 +1,91 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/shell_completion.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  group('shell_completion', () {
+    MockStdio mockStdio;
+
+    setUp(() {
+      Cache.disableLocking();
+      mockStdio = MockStdio();
+    });
+
+    testUsingContext('generates bash initialization script to stdout', () async {
+      final ShellCompletionCommand command = ShellCompletionCommand();
+      await createTestCommandRunner(command).run(<String>['bash-completion']);
+      expect(mockStdio.writtenToStdout.length, equals(1));
+      expect(mockStdio.writtenToStdout.first, contains('__flutter_completion'));
+    }, overrides: <Type, Generator>{
+      Stdio: () => mockStdio,
+    });
+
+    testUsingContext('generates bash initialization script to stdout with arg', () async {
+      final ShellCompletionCommand command = ShellCompletionCommand();
+      await createTestCommandRunner(command).run(<String>['bash-completion', '-']);
+      expect(mockStdio.writtenToStdout.length, equals(1));
+      expect(mockStdio.writtenToStdout.first, contains('__flutter_completion'));
+    }, overrides: <Type, Generator>{
+      Stdio: () => mockStdio,
+    });
+
+    testUsingContext('generates bash initialization script to output file', () async {
+      final ShellCompletionCommand command = ShellCompletionCommand();
+      const String outputFile = 'bash-setup.sh';
+      await createTestCommandRunner(command).run(
+        <String>['bash-completion', outputFile],
+      );
+      expect(fs.isFileSync(outputFile), isTrue);
+      expect(fs.file(outputFile).readAsStringSync(), contains('__flutter_completion'));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => MemoryFileSystem(),
+      Stdio: () => mockStdio,
+    });
+
+    testUsingContext("won't overwrite existing output file ", () async {
+      final ShellCompletionCommand command = ShellCompletionCommand();
+      const String outputFile = 'bash-setup.sh';
+      fs.file(outputFile).createSync();
+      try {
+        await createTestCommandRunner(command).run(
+          <String>['bash-completion', outputFile],
+        );
+        fail('Expect ToolExit exception');
+      } on ToolExit catch (error) {
+        expect(error.exitCode ?? 1, 1);
+        expect(error.message, contains('Use --overwrite'));
+      }
+      expect(fs.isFileSync(outputFile), isTrue);
+      expect(fs.file(outputFile).readAsStringSync(), isEmpty);
+    }, overrides: <Type, Generator>{
+      FileSystem: () => MemoryFileSystem(),
+      Stdio: () => mockStdio,
+    });
+
+    testUsingContext('will overwrite existing output file if given --overwrite', () async {
+      final ShellCompletionCommand command = ShellCompletionCommand();
+      const String outputFile = 'bash-setup.sh';
+      fs.file(outputFile).createSync();
+      await createTestCommandRunner(command).run(
+        <String>['bash-completion', '--overwrite', outputFile],
+      );
+      expect(fs.isFileSync(outputFile), isTrue);
+      expect(fs.file(outputFile).readAsStringSync(), contains('__flutter_completion'));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => MemoryFileSystem(),
+      Stdio: () => mockStdio,
+    });
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/test_test.dart b/packages/flutter_tools/test/general.shard/commands/test_test.dart
new file mode 100644
index 0000000..348ce99
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/test_test.dart
@@ -0,0 +1,221 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io' as io;
+
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/dart/sdk.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+// This test depends on some files in ///dev/automated_tests/flutter_test/*
+
+Future<void> _testExclusionLock;
+
+void main() {
+  group('flutter test should', () {
+
+    final String automatedTestsDirectory = fs.path.join('..', '..', 'dev', 'automated_tests');
+    final String flutterTestDirectory = fs.path.join(automatedTestsDirectory, 'flutter_test');
+
+    testUsingContext('not have extraneous error messages', () async {
+      Cache.flutterRoot = '../..';
+      return _testFile('trivial_widget', automatedTestsDirectory, flutterTestDirectory, exitCode: isZero);
+    }, skip: io.Platform.isLinux); // Flutter on Linux sometimes has problems with font resolution (#7224)
+
+    testUsingContext('report nice errors for exceptions thrown within testWidgets()', () async {
+      Cache.flutterRoot = '../..';
+      return _testFile('exception_handling', automatedTestsDirectory, flutterTestDirectory);
+    }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+    testUsingContext('report a nice error when a guarded function was called without await', () async {
+      Cache.flutterRoot = '../..';
+      return _testFile('test_async_utils_guarded', automatedTestsDirectory, flutterTestDirectory);
+    }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+    testUsingContext('report a nice error when an async function was called without await', () async {
+      Cache.flutterRoot = '../..';
+      return _testFile('test_async_utils_unguarded', automatedTestsDirectory, flutterTestDirectory);
+    }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+    testUsingContext('report a nice error when a Ticker is left running', () async {
+      Cache.flutterRoot = '../..';
+      return _testFile('ticker', automatedTestsDirectory, flutterTestDirectory);
+    }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+    testUsingContext('report a nice error when a pubspec.yaml is missing a flutter_test dependency', () async {
+      final String missingDependencyTests = fs.path.join('..', '..', 'dev', 'missing_dependency_tests');
+      Cache.flutterRoot = '../..';
+      return _testFile('trivial', missingDependencyTests, missingDependencyTests);
+    }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+    testUsingContext('report which user created widget caused the error', () async {
+      Cache.flutterRoot = '../..';
+      return _testFile('print_user_created_ancestor', automatedTestsDirectory, flutterTestDirectory,
+          extraArguments: const <String>['--track-widget-creation']);
+    }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+    testUsingContext('report which user created widget caused the error - no flag', () async {
+      Cache.flutterRoot = '../..';
+      return _testFile('print_user_created_ancestor_no_flag', automatedTestsDirectory, flutterTestDirectory);
+    }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+    testUsingContext('run a test when its name matches a regexp', () async {
+      Cache.flutterRoot = '../..';
+      final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
+        extraArguments: const <String>['--name', 'inc.*de']);
+      if (!result.stdout.contains('+1: All tests passed'))
+        fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
+      expect(result.exitCode, 0);
+    });
+
+    testUsingContext('run a test when its name contains a string', () async {
+      Cache.flutterRoot = '../..';
+      final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
+        extraArguments: const <String>['--plain-name', 'include']);
+      if (!result.stdout.contains('+1: All tests passed'))
+        fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
+      expect(result.exitCode, 0);
+    });
+
+    testUsingContext('test runs to completion', () async {
+      Cache.flutterRoot = '../..';
+      final ProcessResult result = await _runFlutterTest('trivial', automatedTestsDirectory, flutterTestDirectory,
+        extraArguments: const <String>['--verbose']);
+      if ((!result.stdout.contains('+1: All tests passed')) ||
+          (!result.stdout.contains('test 0: starting shell process')) ||
+          (!result.stdout.contains('test 0: deleting temporary directory')) ||
+          (!result.stdout.contains('test 0: finished')) ||
+          (!result.stdout.contains('test package returned with exit code 0')))
+        fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
+      if (result.stderr.isNotEmpty)
+        fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n');
+      expect(result.exitCode, 0);
+    });
+
+  });
+}
+
+Future<void> _testFile(
+    String testName,
+    String workingDirectory,
+    String testDirectory, {
+      Matcher exitCode,
+      List<String> extraArguments = const <String>[],
+    }) async {
+  exitCode ??= isNonZero;
+  final String fullTestExpectation = fs.path.join(testDirectory, '${testName}_expectation.txt');
+  final File expectationFile = fs.file(fullTestExpectation);
+  if (!expectationFile.existsSync())
+    fail('missing expectation file: $expectationFile');
+
+  while (_testExclusionLock != null)
+    await _testExclusionLock;
+
+  final ProcessResult exec = await _runFlutterTest(
+    testName,
+    workingDirectory,
+    testDirectory,
+    extraArguments: extraArguments,
+  );
+
+  expect(exec.exitCode, exitCode);
+  final List<String> output = exec.stdout.split('\n');
+  if (output.first == 'Waiting for another flutter command to release the startup lock...')
+    output.removeAt(0);
+  if (output.first.startsWith('Running "flutter pub get" in'))
+    output.removeAt(0);
+  output.add('<<stderr>>');
+  output.addAll(exec.stderr.split('\n'));
+  final List<String> expectations = fs.file(fullTestExpectation).readAsLinesSync();
+  bool allowSkip = false;
+  int expectationLineNumber = 0;
+  int outputLineNumber = 0;
+  bool haveSeenStdErrMarker = false;
+  while (expectationLineNumber < expectations.length) {
+    expect(
+      output,
+      hasLength(greaterThan(outputLineNumber)),
+      reason: 'Failure in $testName to compare to $fullTestExpectation',
+    );
+    final String expectationLine = expectations[expectationLineNumber];
+    String outputLine = output[outputLineNumber];
+    if (expectationLine == '<<skip until matching line>>') {
+      allowSkip = true;
+      expectationLineNumber += 1;
+      continue;
+    }
+    if (allowSkip) {
+      if (!RegExp(expectationLine).hasMatch(outputLine)) {
+        outputLineNumber += 1;
+        continue;
+      }
+      allowSkip = false;
+    }
+    if (expectationLine == '<<stderr>>') {
+      expect(haveSeenStdErrMarker, isFalse);
+      haveSeenStdErrMarker = true;
+    }
+    if (!RegExp(expectationLine).hasMatch(outputLine) && outputLineNumber + 1 < output.length) {
+      // Check if the RegExp can match the next two lines in the output so
+      // that it is possible to write expectations that still hold even if a
+      // line is wrapped slightly differently due to for example a file name
+      // being longer on one platform than another.
+      final String mergedLines = '$outputLine\n${output[outputLineNumber+1]}';
+      if (RegExp(expectationLine).hasMatch(mergedLines)) {
+        outputLineNumber += 1;
+        outputLine = mergedLines;
+      }
+    }
+
+    expect(outputLine, matches(expectationLine), reason: 'Full output:\n- - - -----8<----- - - -\n${output.join("\n")}\n- - - -----8<----- - - -');
+    expectationLineNumber += 1;
+    outputLineNumber += 1;
+  }
+  expect(allowSkip, isFalse);
+  if (!haveSeenStdErrMarker)
+    expect(exec.stderr, '');
+}
+
+Future<ProcessResult> _runFlutterTest(
+  String testName,
+  String workingDirectory,
+  String testDirectory, {
+  List<String> extraArguments = const <String>[],
+}) async {
+
+  final String testFilePath = fs.path.join(testDirectory, '${testName}_test.dart');
+  final File testFile = fs.file(testFilePath);
+  if (!testFile.existsSync())
+    fail('missing test file: $testFile');
+
+  final List<String> args = <String>[
+    ...dartVmFlags,
+    fs.path.absolute(fs.path.join('bin', 'flutter_tools.dart')),
+    'test',
+    '--no-color',
+    ...extraArguments,
+    testFilePath
+  ];
+
+  while (_testExclusionLock != null)
+    await _testExclusionLock;
+
+  final Completer<void> testExclusionCompleter = Completer<void>();
+  _testExclusionLock = testExclusionCompleter.future;
+  try {
+    return await Process.run(
+      fs.path.join(dartSdkPath, 'bin', 'dart'),
+      args,
+      workingDirectory: workingDirectory,
+    );
+  } finally {
+    _testExclusionLock = null;
+    testExclusionCompleter.complete();
+  }
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/update_packages_test.dart b/packages/flutter_tools/test/general.shard/commands/update_packages_test.dart
new file mode 100644
index 0000000..6097ba0
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/update_packages_test.dart
@@ -0,0 +1,18 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_tools/src/commands/update_packages.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  group('UpdatePackagesCommand', () {
+    // Marking it as experimental breaks bots tests and packaging scripts on stable branches.
+    testUsingContext('is not marked as experimental', () async {
+      final UpdatePackagesCommand command = UpdatePackagesCommand();
+      expect(command.isExperimental, isFalse);
+    });
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart b/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart
new file mode 100644
index 0000000..ac9879d
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart
@@ -0,0 +1,192 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/os.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/upgrade.dart';
+import 'package:flutter_tools/src/runner/flutter_command.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  group('UpgradeCommandRunner', () {
+    FakeUpgradeCommandRunner fakeCommandRunner;
+    UpgradeCommandRunner realCommandRunner;
+    MockProcessManager processManager;
+    final MockFlutterVersion flutterVersion = MockFlutterVersion();
+    const GitTagVersion gitTagVersion = GitTagVersion(1, 2, 3, 4, 5, 'asd');
+    when(flutterVersion.channel).thenReturn('dev');
+
+    setUp(() {
+      fakeCommandRunner = FakeUpgradeCommandRunner();
+      realCommandRunner = UpgradeCommandRunner();
+      processManager = MockProcessManager();
+      fakeCommandRunner.willHaveUncomittedChanges = false;
+    });
+
+    test('throws on unknown tag, official branch,  noforce', () async {
+      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+        false,
+        const GitTagVersion.unknown(),
+        flutterVersion,
+      );
+      expect(result, throwsA(isInstanceOf<ToolExit>()));
+    });
+
+    test('does not throw on unknown tag, official branch, force', () async {
+      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+        true,
+        const GitTagVersion.unknown(),
+        flutterVersion,
+      );
+      expect(await result, null);
+    });
+
+    test('throws tool exit with uncommitted changes', () async {
+      fakeCommandRunner.willHaveUncomittedChanges = true;
+      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+        false,
+        gitTagVersion,
+        flutterVersion,
+      );
+      expect(result, throwsA(isA<ToolExit>()));
+    });
+
+    test('does not throw tool exit with uncommitted changes and force', () async {
+      fakeCommandRunner.willHaveUncomittedChanges = true;
+      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+        true,
+        gitTagVersion,
+        flutterVersion,
+      );
+      expect(await result, null);
+    });
+
+    test('Doesn\'t throw on known tag, dev branch, no force', () async {
+      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+        false,
+        gitTagVersion,
+        flutterVersion,
+      );
+      expect(await result, null);
+    });
+
+    testUsingContext('verifyUpstreamConfigured', () async {
+      when(processManager.run(
+        <String>['git', 'rev-parse', '@{u}'],
+        environment:anyNamed('environment'),
+        workingDirectory: anyNamed('workingDirectory'))
+      ).thenAnswer((Invocation invocation) async {
+        return FakeProcessResult()
+          ..exitCode = 0;
+      });
+      await realCommandRunner.verifyUpstreamConfigured();
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => processManager,
+    });
+  });
+
+  group('matchesGitLine', () {
+    setUpAll(() {
+      Cache.disableLocking();
+    });
+
+    bool _match(String line) => UpgradeCommandRunner.matchesGitLine(line);
+
+    test('regex match', () {
+      expect(_match(' .../flutter_gallery/lib/demo/buttons_demo.dart    | 10 +--'), true);
+      expect(_match(' dev/benchmarks/complex_layout/lib/main.dart        |  24 +-'), true);
+
+      expect(_match(' rename {packages/flutter/doc => dev/docs}/styles.html (92%)'), true);
+      expect(_match(' delete mode 100644 doc/index.html'), true);
+      expect(_match(' create mode 100644 examples/flutter_gallery/lib/gallery/demo.dart'), true);
+
+      expect(_match('Fast-forward'), true);
+    });
+
+    test('regex doesn\'t match', () {
+      expect(_match('Updating 79cfe1e..5046107'), false);
+      expect(_match('229 files changed, 6179 insertions(+), 3065 deletions(-)'), false);
+    });
+
+    group('findProjectRoot', () {
+      Directory tempDir;
+
+      setUp(() async {
+        tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_upgrade_test.');
+      });
+
+      tearDown(() {
+        tryToDelete(tempDir);
+      });
+
+      testUsingContext('in project', () async {
+        final String projectPath = await createProject(tempDir);
+        expect(findProjectRoot(projectPath), projectPath);
+        expect(findProjectRoot(fs.path.join(projectPath, 'lib')), projectPath);
+
+        final String hello = fs.path.join(Cache.flutterRoot, 'examples', 'hello_world');
+        expect(findProjectRoot(hello), hello);
+        expect(findProjectRoot(fs.path.join(hello, 'lib')), hello);
+      });
+
+      testUsingContext('outside project', () async {
+        final String projectPath = await createProject(tempDir);
+        expect(findProjectRoot(fs.directory(projectPath).parent.path), null);
+        expect(findProjectRoot(Cache.flutterRoot), null);
+      });
+    });
+  });
+}
+
+class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
+  bool willHaveUncomittedChanges = false;
+
+  @override
+  Future<void> verifyUpstreamConfigured() async {}
+
+  @override
+  Future<bool> hasUncomittedChanges() async => willHaveUncomittedChanges;
+
+  @override
+  Future<void> resetChanges(GitTagVersion gitTagVersion) async {}
+
+  @override
+  Future<void> upgradeChannel(FlutterVersion flutterVersion) async {}
+
+  @override
+  Future<void> attemptFastForward() async {}
+
+  @override
+  Future<void> precacheArtifacts() async {}
+
+  @override
+  Future<void> updatePackages(FlutterVersion flutterVersion) async {}
+
+  @override
+  Future<void> runDoctor() async {}
+}
+
+class MockFlutterVersion extends Mock implements FlutterVersion {}
+class MockProcessManager extends Mock implements ProcessManager {}
+class FakeProcessResult implements ProcessResult {
+  @override
+  int exitCode;
+
+  @override
+  int pid = 0;
+
+  @override
+  String stderr = '';
+
+  @override
+  String stdout = '';
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/version_test.dart b/packages/flutter_tools/test/general.shard/commands/version_test.dart
new file mode 100644
index 0000000..1e48004
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/version_test.dart
@@ -0,0 +1,123 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/version.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart' show MockProcess;
+
+void main() {
+  group('version', () {
+    setUpAll(() {
+      Cache.disableLocking();
+    });
+
+    testUsingContext('version ls', () async {
+      final VersionCommand command = VersionCommand();
+      await createTestCommandRunner(command).run(<String>['version']);
+      expect(testLogger.statusText, equals('v10.0.0\r\nv20.0.0\n' ''));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => MockProcessManager(),
+    });
+
+    testUsingContext('version switch', () async {
+      const String version = '10.0.0';
+      final VersionCommand command = VersionCommand();
+      final Future<void> runCommand = createTestCommandRunner(command).run(<String>['version', version]);
+      await Future.wait<void>(<Future<void>>[runCommand]);
+      expect(testLogger.statusText, contains('Switching Flutter to version $version'));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => MockProcessManager(),
+    });
+
+    testUsingContext('switch to not supported version without force', () async {
+      const String version = '1.1.5';
+      final VersionCommand command = VersionCommand();
+      final Future<void> runCommand = createTestCommandRunner(command).run(<String>['version', version]);
+      await Future.wait<void>(<Future<void>>[runCommand]);
+      expect(testLogger.errorText, contains('Version command is not supported in'));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => MockProcessManager(),
+    });
+
+    testUsingContext('switch to not supported version with force', () async {
+      const String version = '1.1.5';
+      final VersionCommand command = VersionCommand();
+      final Future<void> runCommand = createTestCommandRunner(command).run(<String>['version', '--force', version]);
+      await Future.wait<void>(<Future<void>>[runCommand]);
+      expect(testLogger.statusText, contains('Switching Flutter to version $version with force'));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => MockProcessManager(),
+    });
+  });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {
+  String version = '';
+
+  @override
+  Future<ProcessResult> run(
+    List<dynamic> command, {
+    String workingDirectory,
+    Map<String, String> environment,
+    bool includeParentEnvironment = true,
+    bool runInShell = false,
+    Encoding stdoutEncoding = systemEncoding,
+    Encoding stderrEncoding = systemEncoding,
+  }) async {
+    if (command[0] == 'git' && command[1] == 'tag') {
+      return ProcessResult(0, 0, 'v10.0.0\r\nv20.0.0', '');
+    }
+    if (command[0] == 'git' && command[1] == 'checkout') {
+      version = command[2];
+    }
+    return ProcessResult(0, 0, '', '');
+  }
+
+  @override
+  ProcessResult runSync(
+    List<dynamic> command, {
+    String workingDirectory,
+    Map<String, String> environment,
+    bool includeParentEnvironment = true,
+    bool runInShell = false,
+    Encoding stdoutEncoding = systemEncoding,
+    Encoding stderrEncoding = systemEncoding,
+  }) {
+    final String commandStr = command.join(' ');
+    if (commandStr == 'git log -n 1 --pretty=format:%H') {
+      return ProcessResult(0, 0, '000000000000000000000', '');
+    }
+    if (commandStr ==
+        'git describe --match v*.*.* --first-parent --long --tags') {
+      if (version.isNotEmpty) {
+        return ProcessResult(0, 0, '$version-0-g00000000', '');
+      }
+    }
+    return ProcessResult(0, 0, '', '');
+  }
+
+  @override
+  Future<Process> start(
+    List<dynamic> command, {
+    String workingDirectory,
+    Map<String, String> environment,
+    bool includeParentEnvironment = true,
+    bool runInShell = false,
+    ProcessStartMode mode = ProcessStartMode.normal,
+  }) {
+    final Completer<Process> completer = Completer<Process>();
+    completer.complete(MockProcess());
+    return completer.future;
+  }
+}