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/base/build_test.dart b/packages/flutter_tools/test/general.shard/base/build_test.dart
new file mode 100644
index 0000000..13f96b9
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/build_test.dart
@@ -0,0 +1,691 @@
+// 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:file/memory.dart';
+import 'package:flutter_tools/src/android/android_sdk.dart';
+import 'package:flutter_tools/src/artifacts.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/base/build.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/base/logger.dart';
+import 'package:flutter_tools/src/base/process.dart';
+import 'package:flutter_tools/src/macos/xcode.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+class MockFlutterVersion extends Mock implements FlutterVersion {}
+class MockAndroidSdk extends Mock implements AndroidSdk {}
+class MockArtifacts extends Mock implements Artifacts {}
+class MockXcode extends Mock implements Xcode {}
+
+class _FakeGenSnapshot implements GenSnapshot {
+  _FakeGenSnapshot({
+    this.succeed = true,
+  });
+
+  final bool succeed;
+  Map<String, String> outputs = <String, String>{};
+  int _callCount = 0;
+  SnapshotType _snapshotType;
+  String _depfilePath;
+  List<String> _additionalArgs;
+
+  int get callCount => _callCount;
+
+  SnapshotType get snapshotType => _snapshotType;
+
+  String get depfilePath => _depfilePath;
+
+  List<String> get additionalArgs => _additionalArgs;
+
+  @override
+  Future<int> run({
+    SnapshotType snapshotType,
+    String depfilePath,
+    IOSArch iosArch,
+    Iterable<String> additionalArgs = const <String>[],
+  }) async {
+    _callCount += 1;
+    _snapshotType = snapshotType;
+    _depfilePath = depfilePath;
+    _additionalArgs = additionalArgs.toList();
+
+    if (!succeed)
+      return 1;
+    outputs.forEach((String filePath, String fileContent) {
+      fs.file(filePath).writeAsString(fileContent);
+    });
+    return 0;
+  }
+}
+
+void main() {
+  group('SnapshotType', () {
+    test('throws, if build mode is null', () {
+      expect(
+        () => SnapshotType(TargetPlatform.android_x64, null),
+        throwsA(anything),
+      );
+    });
+    test('does not throw, if target platform is null', () {
+      expect(SnapshotType(null, BuildMode.release), isNotNull);
+    });
+  });
+
+  group('Snapshotter - AOT', () {
+    const String kSnapshotDart = 'snapshot.dart';
+    String skyEnginePath;
+
+    _FakeGenSnapshot genSnapshot;
+    MemoryFileSystem fs;
+    AOTSnapshotter snapshotter;
+    AOTSnapshotter snapshotterWithTimings;
+    MockAndroidSdk mockAndroidSdk;
+    MockArtifacts mockArtifacts;
+    MockXcode mockXcode;
+    BufferLogger bufferLogger;
+
+    setUp(() async {
+      fs = MemoryFileSystem();
+      fs.file(kSnapshotDart).createSync();
+      fs.file('.packages').writeAsStringSync('sky_engine:file:///flutter/bin/cache/pkg/sky_engine/lib/');
+
+      skyEnginePath = fs.path.fromUri(Uri.file('/flutter/bin/cache/pkg/sky_engine'));
+      fs.directory(fs.path.join(skyEnginePath, 'lib', 'ui')).createSync(recursive: true);
+      fs.directory(fs.path.join(skyEnginePath, 'sdk_ext')).createSync(recursive: true);
+      fs.file(fs.path.join(skyEnginePath, '.packages')).createSync();
+      fs.file(fs.path.join(skyEnginePath, 'lib', 'ui', 'ui.dart')).createSync();
+      fs.file(fs.path.join(skyEnginePath, 'sdk_ext', 'vmservice_io.dart')).createSync();
+
+      genSnapshot = _FakeGenSnapshot();
+      snapshotter = AOTSnapshotter();
+      snapshotterWithTimings = AOTSnapshotter(reportTimings: true);
+      mockAndroidSdk = MockAndroidSdk();
+      mockArtifacts = MockArtifacts();
+      mockXcode = MockXcode();
+      bufferLogger = BufferLogger();
+      for (BuildMode mode in BuildMode.values) {
+        when(mockArtifacts.getArtifactPath(Artifact.snapshotDart,
+            platform: anyNamed('platform'), mode: mode)).thenReturn(kSnapshotDart);
+      }
+    });
+
+    final Map<Type, Generator> contextOverrides = <Type, Generator>{
+      AndroidSdk: () => mockAndroidSdk,
+      Artifacts: () => mockArtifacts,
+      FileSystem: () => fs,
+      GenSnapshot: () => genSnapshot,
+      Xcode: () => mockXcode,
+      Logger: () => bufferLogger,
+    };
+
+    testUsingContext('iOS debug AOT snapshot is invalid', () async {
+      final String outputPath = fs.path.join('build', 'foo');
+      expect(await snapshotter.build(
+        platform: TargetPlatform.ios,
+        buildMode: BuildMode.debug,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+      ), isNot(equals(0)));
+    }, overrides: contextOverrides);
+
+    testUsingContext('Android arm debug AOT snapshot is invalid', () async {
+      final String outputPath = fs.path.join('build', 'foo');
+      expect(await snapshotter.build(
+        platform: TargetPlatform.android_arm,
+        buildMode: BuildMode.debug,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+      ), isNot(0));
+    }, overrides: contextOverrides);
+
+    testUsingContext('Android arm64 debug AOT snapshot is invalid', () async {
+      final String outputPath = fs.path.join('build', 'foo');
+      expect(await snapshotter.build(
+        platform: TargetPlatform.android_arm64,
+        buildMode: BuildMode.debug,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+      ), isNot(0));
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds iOS armv7 profile AOT snapshot', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'snapshot_assembly.S'): '',
+      };
+
+      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
+      when(xcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+      when(xcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.ios,
+        buildMode: BuildMode.profile,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        iosArch: IOSArch.armv7,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
+      expect(genSnapshot.snapshotType.mode, BuildMode.profile);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-aot-assembly',
+        '--assembly=${fs.path.join(outputPath, 'snapshot_assembly.S')}',
+        '--no-sim-use-hardfp',
+        '--no-use-integer-division',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds iOS arm64 profile AOT snapshot', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'snapshot_assembly.S'): '',
+      };
+
+      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
+      when(xcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+      when(xcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.ios,
+        buildMode: BuildMode.profile,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        iosArch: IOSArch.arm64,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
+      expect(genSnapshot.snapshotType.mode, BuildMode.profile);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-aot-assembly',
+        '--assembly=${fs.path.join(outputPath, 'snapshot_assembly.S')}',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds iOS release armv7 AOT snapshot', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'snapshot_assembly.S'): '',
+      };
+
+      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
+      when(xcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+      when(xcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.ios,
+        buildMode: BuildMode.release,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        iosArch: IOSArch.armv7,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
+      expect(genSnapshot.snapshotType.mode, BuildMode.release);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-aot-assembly',
+        '--assembly=${fs.path.join(outputPath, 'snapshot_assembly.S')}',
+        '--no-sim-use-hardfp',
+        '--no-use-integer-division',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds iOS release arm64 AOT snapshot', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'snapshot_assembly.S'): '',
+      };
+
+      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
+      when(xcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+      when(xcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.ios,
+        buildMode: BuildMode.release,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        iosArch: IOSArch.arm64,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
+      expect(genSnapshot.snapshotType.mode, BuildMode.release);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-aot-assembly',
+        '--assembly=${fs.path.join(outputPath, 'snapshot_assembly.S')}',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds shared library for android-arm', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.android_arm,
+        buildMode: BuildMode.release,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm);
+      expect(genSnapshot.snapshotType.mode, BuildMode.release);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-aot-elf',
+        '--elf=build/foo/app.so',
+        '--strip',
+        '--no-sim-use-hardfp',
+        '--no-use-integer-division',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds shared library for android-arm64', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.android_arm64,
+        buildMode: BuildMode.release,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm64);
+      expect(genSnapshot.snapshotType.mode, BuildMode.release);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-aot-elf',
+        '--elf=build/foo/app.so',
+        '--strip',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('reports timing', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'app.so'): '',
+      };
+
+      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
+      when(xcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+      when(xcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+
+      final int genSnapshotExitCode = await snapshotterWithTimings.build(
+        platform: TargetPlatform.android_arm,
+        buildMode: BuildMode.release,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(bufferLogger.statusText, matches(RegExp(r'snapshot\(CompileTime\): \d+ ms.')));
+    }, overrides: contextOverrides);
+  });
+
+  group('Snapshotter - JIT', () {
+    const String kTrace = 'trace.txt';
+    const String kEngineVmSnapshotData = 'engine_vm_snapshot_data';
+    const String kEngineIsolateSnapshotData = 'engine_isolate_snapshot_data';
+
+    _FakeGenSnapshot genSnapshot;
+    MemoryFileSystem fs;
+    JITSnapshotter snapshotter;
+    MockAndroidSdk mockAndroidSdk;
+    MockArtifacts mockArtifacts;
+
+    setUp(() async {
+      fs = MemoryFileSystem();
+      fs.file(kTrace).createSync();
+      fs.file(kEngineVmSnapshotData).createSync();
+      fs.file(kEngineIsolateSnapshotData).createSync();
+
+      genSnapshot = _FakeGenSnapshot();
+      snapshotter = JITSnapshotter();
+      mockAndroidSdk = MockAndroidSdk();
+      mockArtifacts = MockArtifacts();
+
+      for (BuildMode mode in BuildMode.values) {
+        when(mockArtifacts.getArtifactPath(Artifact.vmSnapshotData,
+            platform: anyNamed('platform'), mode: mode))
+            .thenReturn(kEngineVmSnapshotData);
+        when(mockArtifacts.getArtifactPath(Artifact.isolateSnapshotData,
+            platform: anyNamed('platform'), mode: mode))
+            .thenReturn(kEngineIsolateSnapshotData);
+      }
+    });
+
+    final Map<Type, Generator> contextOverrides = <Type, Generator>{
+      AndroidSdk: () => mockAndroidSdk,
+      Artifacts: () => mockArtifacts,
+      FileSystem: () => fs,
+      GenSnapshot: () => genSnapshot,
+    };
+
+    testUsingContext('iOS debug JIT snapshot is invalid', () async {
+      final String outputPath = fs.path.join('build', 'foo');
+      expect(await snapshotter.build(
+        platform: TargetPlatform.ios,
+        buildMode: BuildMode.debug,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        compilationTraceFilePath: kTrace,
+      ), isNot(equals(0)));
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds Android arm debug JIT snapshot', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'isolate_snapshot_data'): '',
+        fs.path.join(outputPath, 'isolate_snapshot_instr'): '',
+      };
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.android_arm,
+        buildMode: BuildMode.debug,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        compilationTraceFilePath: kTrace,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm);
+      expect(genSnapshot.snapshotType.mode, BuildMode.debug);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--enable_asserts',
+        '--snapshot_kind=app-jit',
+        '--load_compilation_trace=$kTrace',
+        '--load_vm_snapshot_data=$kEngineVmSnapshotData',
+        '--load_isolate_snapshot_data=$kEngineIsolateSnapshotData',
+        '--isolate_snapshot_data=build/foo/isolate_snapshot_data',
+        '--isolate_snapshot_instructions=build/foo/isolate_snapshot_instr',
+        '--no-sim-use-hardfp',
+        '--no-use-integer-division',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds Android arm64 debug JIT snapshot', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'isolate_snapshot_data'): '',
+        fs.path.join(outputPath, 'isolate_snapshot_instr'): '',
+      };
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.android_arm64,
+        buildMode: BuildMode.debug,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        compilationTraceFilePath: kTrace,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm64);
+      expect(genSnapshot.snapshotType.mode, BuildMode.debug);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--enable_asserts',
+        '--snapshot_kind=app-jit',
+        '--load_compilation_trace=$kTrace',
+        '--load_vm_snapshot_data=$kEngineVmSnapshotData',
+        '--load_isolate_snapshot_data=$kEngineIsolateSnapshotData',
+        '--isolate_snapshot_data=build/foo/isolate_snapshot_data',
+        '--isolate_snapshot_instructions=build/foo/isolate_snapshot_instr',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('iOS release JIT snapshot is invalid', () async {
+      final String outputPath = fs.path.join('build', 'foo');
+      expect(await snapshotter.build(
+        platform: TargetPlatform.ios,
+        buildMode: BuildMode.profile,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        compilationTraceFilePath: kTrace,
+      ), isNot(equals(0)));
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds Android arm profile JIT snapshot', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'isolate_snapshot_data'): '',
+        fs.path.join(outputPath, 'isolate_snapshot_instr'): '',
+      };
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.android_arm,
+        buildMode: BuildMode.profile,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        compilationTraceFilePath: kTrace,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm);
+      expect(genSnapshot.snapshotType.mode, BuildMode.profile);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-jit',
+        '--load_compilation_trace=$kTrace',
+        '--load_vm_snapshot_data=$kEngineVmSnapshotData',
+        '--load_isolate_snapshot_data=$kEngineIsolateSnapshotData',
+        '--isolate_snapshot_data=build/foo/isolate_snapshot_data',
+        '--isolate_snapshot_instructions=build/foo/isolate_snapshot_instr',
+        '--no-sim-use-hardfp',
+        '--no-use-integer-division',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds Android arm64 profile JIT snapshot', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'isolate_snapshot_data'): '',
+        fs.path.join(outputPath, 'isolate_snapshot_instr'): '',
+      };
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.android_arm64,
+        buildMode: BuildMode.profile,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        compilationTraceFilePath: kTrace,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm64);
+      expect(genSnapshot.snapshotType.mode, BuildMode.profile);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-jit',
+        '--load_compilation_trace=$kTrace',
+        '--load_vm_snapshot_data=$kEngineVmSnapshotData',
+        '--load_isolate_snapshot_data=$kEngineIsolateSnapshotData',
+        '--isolate_snapshot_data=build/foo/isolate_snapshot_data',
+        '--isolate_snapshot_instructions=build/foo/isolate_snapshot_instr',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('iOS release JIT snapshot is invalid', () async {
+      final String outputPath = fs.path.join('build', 'foo');
+      expect(await snapshotter.build(
+        platform: TargetPlatform.ios,
+        buildMode: BuildMode.release,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        compilationTraceFilePath: kTrace,
+      ), isNot(equals(0)));
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds Android arm release JIT snapshot', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'isolate_snapshot_data'): '',
+        fs.path.join(outputPath, 'isolate_snapshot_instr'): '',
+      };
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.android_arm,
+        buildMode: BuildMode.release,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        compilationTraceFilePath: kTrace,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm);
+      expect(genSnapshot.snapshotType.mode, BuildMode.release);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-jit',
+        '--load_compilation_trace=$kTrace',
+        '--load_vm_snapshot_data=$kEngineVmSnapshotData',
+        '--load_isolate_snapshot_data=$kEngineIsolateSnapshotData',
+        '--isolate_snapshot_data=build/foo/isolate_snapshot_data',
+        '--isolate_snapshot_instructions=build/foo/isolate_snapshot_instr',
+        '--no-sim-use-hardfp',
+        '--no-use-integer-division',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+    testUsingContext('builds Android arm64 release JIT snapshot', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
+
+      genSnapshot.outputs = <String, String>{
+        fs.path.join(outputPath, 'isolate_snapshot_data'): '',
+        fs.path.join(outputPath, 'isolate_snapshot_instr'): '',
+      };
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.android_arm64,
+        buildMode: BuildMode.release,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        compilationTraceFilePath: kTrace,
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm64);
+      expect(genSnapshot.snapshotType.mode, BuildMode.release);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-jit',
+        '--load_compilation_trace=$kTrace',
+        '--load_vm_snapshot_data=$kEngineVmSnapshotData',
+        '--load_isolate_snapshot_data=$kEngineIsolateSnapshotData',
+        '--isolate_snapshot_data=build/foo/isolate_snapshot_data',
+        '--isolate_snapshot_instructions=build/foo/isolate_snapshot_instr',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/base/common_test.dart b/packages/flutter_tools/test/general.shard/base/common_test.dart
new file mode 100644
index 0000000..76f9fd7
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/common_test.dart
@@ -0,0 +1,27 @@
+// 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 'package:flutter_tools/src/base/common.dart';
+
+import '../../src/common.dart';
+
+void main() {
+  group('throwToolExit', () {
+    test('throws ToolExit', () {
+      expect(() => throwToolExit('message'), throwsToolExit());
+    });
+
+    test('throws ToolExit with exitCode', () {
+      expect(() => throwToolExit('message', exitCode: 42), throwsToolExit(exitCode: 42));
+    });
+
+    test('throws ToolExit with message', () {
+      expect(() => throwToolExit('message'), throwsToolExit(message: 'message'));
+    });
+
+    test('throws ToolExit with message and exit code', () {
+      expect(() => throwToolExit('message', exitCode: 42), throwsToolExit(exitCode: 42, message: 'message'));
+    });
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/base/context_test.dart b/packages/flutter_tools/test/general.shard/base/context_test.dart
new file mode 100644
index 0000000..c8b46ff
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/context_test.dart
@@ -0,0 +1,277 @@
+// 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/context.dart';
+
+import '../../src/common.dart';
+
+void main() {
+  group('AppContext', () {
+    group('global getter', () {
+      bool called;
+
+      setUp(() {
+        called = false;
+      });
+
+      test('returns non-null context in the root zone', () {
+        expect(context, isNotNull);
+      });
+
+      test('returns root context in child of root zone if zone was manually created', () {
+        final Zone rootZone = Zone.current;
+        final AppContext rootContext = context;
+        runZoned<void>(() {
+          expect(Zone.current, isNot(rootZone));
+          expect(Zone.current.parent, rootZone);
+          expect(context, rootContext);
+          called = true;
+        });
+        expect(called, isTrue);
+      });
+
+      test('returns child context after run', () async {
+        final AppContext rootContext = context;
+        await rootContext.run<void>(name: 'child', body: () {
+          expect(context, isNot(rootContext));
+          expect(context.name, 'child');
+          called = true;
+        });
+        expect(called, isTrue);
+      });
+
+      test('returns grandchild context after nested run', () async {
+        final AppContext rootContext = context;
+        await rootContext.run<void>(name: 'child', body: () async {
+          final AppContext childContext = context;
+          await childContext.run<void>(name: 'grandchild', body: () {
+            expect(context, isNot(rootContext));
+            expect(context, isNot(childContext));
+            expect(context.name, 'grandchild');
+            called = true;
+          });
+        });
+        expect(called, isTrue);
+      });
+
+      test('scans up zone hierarchy for first context', () async {
+        final AppContext rootContext = context;
+        await rootContext.run<void>(name: 'child', body: () {
+          final AppContext childContext = context;
+          runZoned<void>(() {
+            expect(context, isNot(rootContext));
+            expect(context, same(childContext));
+            expect(context.name, 'child');
+            called = true;
+          });
+        });
+        expect(called, isTrue);
+      });
+    });
+
+    group('operator[]', () {
+      test('still finds values if async code runs after body has finished', () async {
+        final Completer<void> outer = Completer<void>();
+        final Completer<void> inner = Completer<void>();
+        String value;
+        await context.run<void>(
+          body: () {
+            outer.future.then<void>((_) {
+              value = context.get<String>();
+              inner.complete();
+            });
+          },
+          fallbacks: <Type, Generator>{
+            String: () => 'value',
+          },
+        );
+        expect(value, isNull);
+        outer.complete();
+        await inner.future;
+        expect(value, 'value');
+      });
+
+      test('caches generated override values', () async {
+        int consultationCount = 0;
+        String value;
+        await context.run<void>(
+          body: () async {
+            final StringBuffer buf = StringBuffer(context.get<String>());
+            buf.write(context.get<String>());
+            await context.run<void>(body: () {
+              buf.write(context.get<String>());
+            });
+            value = buf.toString();
+          },
+          overrides: <Type, Generator>{
+            String: () {
+              consultationCount++;
+              return 'v';
+            },
+          },
+        );
+        expect(value, 'vvv');
+        expect(consultationCount, 1);
+      });
+
+      test('caches generated fallback values', () async {
+        int consultationCount = 0;
+        String value;
+        await context.run(
+          body: () async {
+            final StringBuffer buf = StringBuffer(context.get<String>());
+            buf.write(context.get<String>());
+            await context.run<void>(body: () {
+              buf.write(context.get<String>());
+            });
+            value = buf.toString();
+          },
+          fallbacks: <Type, Generator>{
+            String: () {
+              consultationCount++;
+              return 'v';
+            },
+          },
+        );
+        expect(value, 'vvv');
+        expect(consultationCount, 1);
+      });
+
+      test('returns null if generated value is null', () async {
+        final String value = await context.run<String>(
+          body: () => context.get<String>(),
+          overrides: <Type, Generator>{
+            String: () => null,
+          },
+        );
+        expect(value, isNull);
+      });
+
+      test('throws if generator has dependency cycle', () async {
+        final Future<String> value = context.run<String>(
+          body: () async {
+            return context.get<String>();
+          },
+          fallbacks: <Type, Generator>{
+            int: () => int.parse(context.get<String>()),
+            String: () => '${context.get<double>()}',
+            double: () => context.get<int>() * 1.0,
+          },
+        );
+        try {
+          await value;
+          fail('ContextDependencyCycleException expected but not thrown.');
+        } on ContextDependencyCycleException catch (e) {
+          expect(e.cycle, <Type>[String, double, int]);
+          expect(e.toString(), 'Dependency cycle detected: String -> double -> int');
+        }
+      });
+    });
+
+    group('run', () {
+      test('returns the value returned by body', () async {
+        expect(await context.run<int>(body: () => 123), 123);
+        expect(await context.run<String>(body: () => 'value'), 'value');
+        expect(await context.run<int>(body: () async => 456), 456);
+      });
+
+      test('passes name to child context', () async {
+        await context.run<void>(name: 'child', body: () {
+          expect(context.name, 'child');
+        });
+      });
+
+      group('fallbacks', () {
+        bool called;
+
+        setUp(() {
+          called = false;
+        });
+
+        test('are applied after parent context is consulted', () async {
+          final String value = await context.run<String>(
+            body: () {
+              return context.run<String>(
+                body: () {
+                  called = true;
+                  return context.get<String>();
+                },
+                fallbacks: <Type, Generator>{
+                  String: () => 'child',
+                },
+              );
+            },
+          );
+          expect(called, isTrue);
+          expect(value, 'child');
+        });
+
+        test('are not applied if parent context supplies value', () async {
+          bool childConsulted = false;
+          final String value = await context.run<String>(
+            body: () {
+              return context.run<String>(
+                body: () {
+                  called = true;
+                  return context.get<String>();
+                },
+                fallbacks: <Type, Generator>{
+                  String: () {
+                    childConsulted = true;
+                    return 'child';
+                  },
+                },
+              );
+            },
+            fallbacks: <Type, Generator>{
+              String: () => 'parent',
+            },
+          );
+          expect(called, isTrue);
+          expect(value, 'parent');
+          expect(childConsulted, isFalse);
+        });
+
+        test('may depend on one another', () async {
+          final String value = await context.run<String>(
+            body: () {
+              return context.get<String>();
+            },
+            fallbacks: <Type, Generator>{
+              int: () => 123,
+              String: () => '-${context.get<int>()}-',
+            },
+          );
+          expect(value, '-123-');
+        });
+      });
+
+      group('overrides', () {
+        test('intercept consultation of parent context', () async {
+          bool parentConsulted = false;
+          final String value = await context.run<String>(
+            body: () {
+              return context.run<String>(
+                body: () => context.get<String>(),
+                overrides: <Type, Generator>{
+                  String: () => 'child',
+                },
+              );
+            },
+            fallbacks: <Type, Generator>{
+              String: () {
+                parentConsulted = true;
+                return 'parent';
+              },
+            },
+          );
+          expect(value, 'child');
+          expect(parentConsulted, isFalse);
+        });
+      });
+    });
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/base/file_system_test.dart b/packages/flutter_tools/test/general.shard/base/file_system_test.dart
new file mode 100644
index 0000000..09126ac
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/file_system_test.dart
@@ -0,0 +1,106 @@
+// 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 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:platform/platform.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  group('ensureDirectoryExists', () {
+    MemoryFileSystem fs;
+
+    setUp(() {
+      fs = MemoryFileSystem();
+    });
+
+    testUsingContext('recursively creates a directory if it does not exist', () async {
+      ensureDirectoryExists('foo/bar/baz.flx');
+      expect(fs.isDirectorySync('foo/bar'), true);
+    }, overrides: <Type, Generator>{FileSystem: () => fs});
+
+    testUsingContext('throws tool exit on failure to create', () async {
+      fs.file('foo').createSync();
+      expect(() => ensureDirectoryExists('foo/bar.flx'), throwsToolExit());
+    }, overrides: <Type, Generator>{FileSystem: () => fs});
+  });
+
+  group('copyDirectorySync', () {
+    /// Test file_systems.copyDirectorySync() using MemoryFileSystem.
+    /// Copies between 2 instances of file systems which is also supported by copyDirectorySync().
+    test('test directory copy', () async {
+      final MemoryFileSystem sourceMemoryFs = MemoryFileSystem();
+      const String sourcePath = '/some/origin';
+      final Directory sourceDirectory = await sourceMemoryFs.directory(sourcePath).create(recursive: true);
+      sourceMemoryFs.currentDirectory = sourcePath;
+      final File sourceFile1 = sourceMemoryFs.file('some_file.txt')..writeAsStringSync('bleh');
+      final DateTime writeTime = sourceFile1.lastModifiedSync();
+      sourceMemoryFs.file('sub_dir/another_file.txt').createSync(recursive: true);
+      sourceMemoryFs.directory('empty_directory').createSync();
+
+      // Copy to another memory file system instance.
+      final MemoryFileSystem targetMemoryFs = MemoryFileSystem();
+      const String targetPath = '/some/non-existent/target';
+      final Directory targetDirectory = targetMemoryFs.directory(targetPath);
+      copyDirectorySync(sourceDirectory, targetDirectory);
+
+      expect(targetDirectory.existsSync(), true);
+      targetMemoryFs.currentDirectory = targetPath;
+      expect(targetMemoryFs.directory('empty_directory').existsSync(), true);
+      expect(targetMemoryFs.file('sub_dir/another_file.txt').existsSync(), true);
+      expect(targetMemoryFs.file('some_file.txt').readAsStringSync(), 'bleh');
+
+      // Assert that the copy operation hasn't modified the original file in some way.
+      expect(sourceMemoryFs.file('some_file.txt').lastModifiedSync(), writeTime);
+      // There's still 3 things in the original directory as there were initially.
+      expect(sourceMemoryFs.directory(sourcePath).listSync().length, 3);
+    });
+  });
+
+  group('canonicalizePath', () {
+    test('does not lowercase on Windows', () {
+      String path = 'C:\\Foo\\bAr\\cOOL.dart';
+      expect(canonicalizePath(path), path);
+      // fs.path.canonicalize does lowercase on Windows
+      expect(fs.path.canonicalize(path), isNot(path));
+
+      path = '..\\bar\\.\\\\Foo';
+      final String expected = fs.path.join(fs.currentDirectory.parent.absolute.path, 'bar', 'Foo');
+      expect(canonicalizePath(path), expected);
+      // fs.path.canonicalize should return the same result (modulo casing)
+      expect(fs.path.canonicalize(path), expected.toLowerCase());
+    }, testOn: 'windows');
+
+    test('does not lowercase on posix', () {
+      String path = '/Foo/bAr/cOOL.dart';
+      expect(canonicalizePath(path), path);
+      // fs.path.canonicalize and canonicalizePath should be the same on Posix
+      expect(fs.path.canonicalize(path), path);
+
+      path = '../bar/.//Foo';
+      final String expected = fs.path.join(fs.currentDirectory.parent.absolute.path, 'bar', 'Foo');
+      expect(canonicalizePath(path), expected);
+    }, testOn: 'posix');
+  });
+
+  group('escapePath', () {
+    testUsingContext('on Windows', () {
+      expect(escapePath('C:\\foo\\bar\\cool.dart'), 'C:\\\\foo\\\\bar\\\\cool.dart');
+      expect(escapePath('foo\\bar\\cool.dart'), 'foo\\\\bar\\\\cool.dart');
+      expect(escapePath('C:/foo/bar/cool.dart'), 'C:/foo/bar/cool.dart');
+    }, overrides: <Type, Generator>{
+      Platform: () => FakePlatform(operatingSystem: 'windows'),
+    });
+
+    testUsingContext('on Linux', () {
+      expect(escapePath('/foo/bar/cool.dart'), '/foo/bar/cool.dart');
+      expect(escapePath('foo/bar/cool.dart'), 'foo/bar/cool.dart');
+      expect(escapePath('foo\\cool.dart'), 'foo\\cool.dart');
+    }, overrides: <Type, Generator>{
+      Platform: () => FakePlatform(operatingSystem: 'linux'),
+    });
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/base/fingerprint_test.dart b/packages/flutter_tools/test/general.shard/base/fingerprint_test.dart
new file mode 100644
index 0000000..9c1f5a8
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/fingerprint_test.dart
@@ -0,0 +1,521 @@
+// 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:convert' show json;
+
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/fingerprint.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  group('Fingerprinter', () {
+    const String kVersion = '123456abcdef';
+
+    MemoryFileSystem fs;
+    MockFlutterVersion mockVersion;
+
+    setUp(() {
+      fs = MemoryFileSystem();
+      mockVersion = MockFlutterVersion();
+      when(mockVersion.frameworkRevision).thenReturn(kVersion);
+    });
+
+    final Map<Type, Generator> contextOverrides = <Type, Generator>{
+      FileSystem: () => fs,
+    };
+
+    testUsingContext('throws when depfile is malformed', () async {
+      await fs.file('a.dart').create();
+      await fs.file('b.dart').create();
+      await fs.file('depfile').create();
+
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart'],
+        depfilePaths: <String>['depfile'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'womble',
+        },
+      );
+      expect(() async => await fingerprinter.buildFingerprint(), throwsA(anything));
+    }, overrides: contextOverrides);
+
+    testUsingContext('creates fingerprint with specified properties and files', () async {
+      await fs.file('a.dart').create();
+
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart'],
+        properties: <String, String>{
+          'foo': 'bar',
+          'wibble': 'wobble',
+        },
+      );
+      final Fingerprint fingerprint = await fingerprinter.buildFingerprint();
+      expect(fingerprint, Fingerprint.fromBuildInputs(<String, String>{
+        'foo': 'bar',
+        'wibble': 'wobble',
+      }, <String>['a.dart']));
+    }, overrides: contextOverrides);
+
+    testUsingContext('creates fingerprint with file checksums', () async {
+      await fs.file('a.dart').create();
+      await fs.file('b.dart').create();
+      await fs.file('depfile').writeAsString('depfile : b.dart');
+
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart'],
+        depfilePaths: <String>['depfile'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'womble',
+        },
+      );
+      final Fingerprint fingerprint = await fingerprinter.buildFingerprint();
+      expect(fingerprint, Fingerprint.fromBuildInputs(<String, String>{
+        'bar': 'baz',
+        'wobble': 'womble',
+      }, <String>['a.dart', 'b.dart']));
+    }, overrides: contextOverrides);
+
+    testUsingContext('fingerprint does not match if not present', () async {
+      await fs.file('a.dart').create();
+      await fs.file('b.dart').create();
+
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart', 'b.dart'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'womble',
+        },
+      );
+      expect(await fingerprinter.doesFingerprintMatch(), isFalse);
+    }, overrides: contextOverrides);
+
+    testUsingContext('fingerprint does match if different', () async {
+      await fs.file('a.dart').create();
+      await fs.file('b.dart').create();
+
+      final Fingerprinter fingerprinter1 = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart', 'b.dart'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'womble',
+        },
+      );
+      await fingerprinter1.writeFingerprint();
+
+      final Fingerprinter fingerprinter2 = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart', 'b.dart'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'elbmow',
+        },
+      );
+      expect(await fingerprinter2.doesFingerprintMatch(), isFalse);
+    }, overrides: contextOverrides);
+
+    testUsingContext('fingerprint does not match if depfile is malformed', () async {
+      await fs.file('a.dart').create();
+      await fs.file('b.dart').create();
+      await fs.file('depfile').writeAsString('depfile : b.dart');
+
+      // Write a valid fingerprint
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart', 'b.dart'],
+        depfilePaths: <String>['depfile'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'womble',
+        },
+      );
+      await fingerprinter.writeFingerprint();
+
+      // Write a corrupt depfile.
+      await fs.file('depfile').writeAsString('');
+      final Fingerprinter badFingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart', 'b.dart'],
+        depfilePaths: <String>['depfile'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'womble',
+        },
+      );
+
+      expect(await badFingerprinter.doesFingerprintMatch(), isFalse);
+    }, overrides: contextOverrides);
+
+    testUsingContext('fingerprint does not match if previous fingerprint is malformed', () async {
+      await fs.file('a.dart').create();
+      await fs.file('b.dart').create();
+      await fs.file('out.fingerprint').writeAsString('** not JSON **');
+
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart', 'b.dart'],
+        depfilePaths: <String>['depfile'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'womble',
+        },
+      );
+      expect(await fingerprinter.doesFingerprintMatch(), isFalse);
+    }, overrides: contextOverrides);
+
+    testUsingContext('fingerprint does match if identical', () async {
+      await fs.file('a.dart').create();
+      await fs.file('b.dart').create();
+
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart', 'b.dart'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'womble',
+        },
+      );
+      await fingerprinter.writeFingerprint();
+      expect(await fingerprinter.doesFingerprintMatch(), isTrue);
+    }, overrides: contextOverrides);
+
+    final Platform mockPlatformDisabledCache = MockPlatform();
+    mockPlatformDisabledCache.environment['DISABLE_FLUTTER_BUILD_CACHE']  = 'true';
+    testUsingContext('can be disabled with an environment variable', () async {
+      await fs.file('a.dart').create();
+      await fs.file('b.dart').create();
+
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart', 'b.dart'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'womble',
+        },
+      );
+      await fingerprinter.writeFingerprint();
+      expect(await fingerprinter.doesFingerprintMatch(), isFalse);
+    }, overrides: <Type, Generator>{
+      Platform: () => mockPlatformDisabledCache,
+      ...contextOverrides,
+    });
+
+    final Platform mockPlatformEnabledCache = MockPlatform();
+    mockPlatformEnabledCache.environment['DISABLE_FLUTTER_BUILD_CACHE']  = 'false';
+    testUsingContext('can be not-disabled with an environment variable', () async {
+      await fs.file('a.dart').create();
+      await fs.file('b.dart').create();
+
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart', 'b.dart'],
+        properties: <String, String>{
+          'bar': 'baz',
+          'wobble': 'womble',
+        },
+      );
+      await fingerprinter.writeFingerprint();
+      expect(await fingerprinter.doesFingerprintMatch(), isTrue);
+    }, overrides: <Type, Generator>{
+      Platform: () => mockPlatformEnabledCache,
+      ...contextOverrides,
+    });
+
+    testUsingContext('fails to write fingerprint if inputs are missing', () async {
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart'],
+        properties: <String, String>{
+          'foo': 'bar',
+          'wibble': 'wobble',
+        },
+      );
+      await fingerprinter.writeFingerprint();
+      expect(fs.file('out.fingerprint').existsSync(), isFalse);
+    }, overrides: contextOverrides);
+
+    testUsingContext('applies path filter to inputs paths', () async {
+      await fs.file('a.dart').create();
+      await fs.file('ab.dart').create();
+      await fs.file('depfile').writeAsString('depfile : ab.dart c.dart');
+
+      final Fingerprinter fingerprinter = Fingerprinter(
+        fingerprintPath: 'out.fingerprint',
+        paths: <String>['a.dart'],
+        depfilePaths: <String>['depfile'],
+        properties: <String, String>{
+          'foo': 'bar',
+          'wibble': 'wobble',
+        },
+        pathFilter: (String path) => path.startsWith('a'),
+      );
+      await fingerprinter.writeFingerprint();
+      expect(fs.file('out.fingerprint').existsSync(), isTrue);
+    }, overrides: contextOverrides);
+  });
+
+  group('Fingerprint', () {
+    MockFlutterVersion mockVersion;
+    const String kVersion = '123456abcdef';
+
+    setUp(() {
+      mockVersion = MockFlutterVersion();
+      when(mockVersion.frameworkRevision).thenReturn(kVersion);
+    });
+
+    group('fromBuildInputs', () {
+      MemoryFileSystem fs;
+
+      setUp(() {
+        fs = MemoryFileSystem();
+      });
+
+      testUsingContext('throws if any input file does not exist', () async {
+        await fs.file('a.dart').create();
+        expect(
+          () => Fingerprint.fromBuildInputs(<String, String>{}, <String>['a.dart', 'b.dart']),
+          throwsArgumentError,
+        );
+      }, overrides: <Type, Generator>{FileSystem: () => fs});
+
+      testUsingContext('populates checksums for valid files', () async {
+        await fs.file('a.dart').writeAsString('This is a');
+        await fs.file('b.dart').writeAsString('This is b');
+        final Fingerprint fingerprint = Fingerprint.fromBuildInputs(<String, String>{}, <String>['a.dart', 'b.dart']);
+
+        final Map<String, dynamic> jsonObject = json.decode(fingerprint.toJson());
+        expect(jsonObject['files'], hasLength(2));
+        expect(jsonObject['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698');
+        expect(jsonObject['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c');
+      }, overrides: <Type, Generator>{FileSystem: () => fs});
+
+      testUsingContext('includes framework version', () {
+        final Fingerprint fingerprint = Fingerprint.fromBuildInputs(<String, String>{}, <String>[]);
+
+        final Map<String, dynamic> jsonObject = json.decode(fingerprint.toJson());
+        expect(jsonObject['version'], mockVersion.frameworkRevision);
+      }, overrides: <Type, Generator>{FlutterVersion: () => mockVersion});
+
+      testUsingContext('includes provided properties', () {
+        final Fingerprint fingerprint = Fingerprint.fromBuildInputs(<String, String>{'a': 'A', 'b': 'B'}, <String>[]);
+
+        final Map<String, dynamic> jsonObject = json.decode(fingerprint.toJson());
+        expect(jsonObject['properties'], hasLength(2));
+        expect(jsonObject['properties']['a'], 'A');
+        expect(jsonObject['properties']['b'], 'B');
+      }, overrides: <Type, Generator>{FlutterVersion: () => mockVersion});
+    });
+
+    group('fromJson', () {
+      testUsingContext('throws if JSON is invalid', () async {
+        expect(() => Fingerprint.fromJson('<xml></xml>'), throwsA(anything));
+      }, overrides: <Type, Generator>{
+        FlutterVersion: () => mockVersion,
+      });
+
+      testUsingContext('creates fingerprint from valid JSON', () async {
+        final String jsonString = json.encode(<String, dynamic>{
+          'version': kVersion,
+          'properties': <String, String>{
+            'buildMode': BuildMode.release.toString(),
+            'targetPlatform': TargetPlatform.ios.toString(),
+            'entryPoint': 'a.dart',
+          },
+          'files': <String, dynamic>{
+            'a.dart': '8a21a15fad560b799f6731d436c1b698',
+            'b.dart': '6f144e08b58cd0925328610fad7ac07c',
+          },
+        });
+        final Fingerprint fingerprint = Fingerprint.fromJson(jsonString);
+        final Map<String, dynamic> content = json.decode(fingerprint.toJson());
+        expect(content, hasLength(3));
+        expect(content['version'], mockVersion.frameworkRevision);
+        expect(content['properties'], hasLength(3));
+        expect(content['properties']['buildMode'], BuildMode.release.toString());
+        expect(content['properties']['targetPlatform'], TargetPlatform.ios.toString());
+        expect(content['properties']['entryPoint'], 'a.dart');
+        expect(content['files'], hasLength(2));
+        expect(content['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698');
+        expect(content['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c');
+      }, overrides: <Type, Generator>{
+        FlutterVersion: () => mockVersion,
+      });
+
+      testUsingContext('throws ArgumentError for unknown versions', () async {
+        final String jsonString = json.encode(<String, dynamic>{
+          'version': 'bad',
+          'properties': <String, String>{},
+          'files': <String, String>{},
+        });
+        expect(() => Fingerprint.fromJson(jsonString), throwsArgumentError);
+      }, overrides: <Type, Generator>{
+        FlutterVersion: () => mockVersion,
+      });
+
+      testUsingContext('throws ArgumentError if version is not present', () async {
+        final String jsonString = json.encode(<String, dynamic>{
+          'properties': <String, String>{},
+          'files': <String, String>{},
+        });
+        expect(() => Fingerprint.fromJson(jsonString), throwsArgumentError);
+      }, overrides: <Type, Generator>{
+        FlutterVersion: () => mockVersion,
+      });
+
+      testUsingContext('treats missing properties and files entries as if empty', () async {
+        final String jsonString = json.encode(<String, dynamic>{
+          'version': kVersion,
+        });
+        expect(Fingerprint.fromJson(jsonString), Fingerprint.fromBuildInputs(<String, String>{}, <String>[]));
+      }, overrides: <Type, Generator>{
+        FlutterVersion: () => mockVersion,
+      });
+    });
+
+    group('operator ==', () {
+      testUsingContext('reports not equal if properties do not match', () async {
+        final Map<String, dynamic> a = <String, dynamic>{
+          'version': kVersion,
+          'properties': <String, String>{
+            'buildMode': BuildMode.debug.toString(),
+          },
+          'files': <String, dynamic>{},
+        };
+        final Map<String, dynamic> b = Map<String, dynamic>.from(a);
+        b['properties'] = <String, String>{
+          'buildMode': BuildMode.release.toString(),
+        };
+        expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(b)), isFalse);
+      }, overrides: <Type, Generator>{
+        FlutterVersion: () => mockVersion,
+      });
+
+      testUsingContext('reports not equal if file checksums do not match', () async {
+        final Map<String, dynamic> a = <String, dynamic>{
+          'version': kVersion,
+          'properties': <String, String>{},
+          'files': <String, dynamic>{
+            'a.dart': '8a21a15fad560b799f6731d436c1b698',
+            'b.dart': '6f144e08b58cd0925328610fad7ac07c',
+          },
+        };
+        final Map<String, dynamic> b = Map<String, dynamic>.from(a);
+        b['files'] = <String, dynamic>{
+          'a.dart': '8a21a15fad560b799f6731d436c1b698',
+          'b.dart': '6f144e08b58cd0925328610fad7ac07d',
+        };
+        expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(b)), isFalse);
+      }, overrides: <Type, Generator>{
+        FlutterVersion: () => mockVersion,
+      });
+
+      testUsingContext('reports not equal if file paths do not match', () async {
+        final Map<String, dynamic> a = <String, dynamic>{
+          'version': kVersion,
+          'properties': <String, String>{},
+          'files': <String, dynamic>{
+            'a.dart': '8a21a15fad560b799f6731d436c1b698',
+            'b.dart': '6f144e08b58cd0925328610fad7ac07c',
+          },
+        };
+        final Map<String, dynamic> b = Map<String, dynamic>.from(a);
+        b['files'] = <String, dynamic>{
+          'a.dart': '8a21a15fad560b799f6731d436c1b698',
+          'c.dart': '6f144e08b58cd0925328610fad7ac07d',
+        };
+        expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(b)), isFalse);
+      }, overrides: <Type, Generator>{
+        FlutterVersion: () => mockVersion,
+      });
+
+      testUsingContext('reports equal if properties and file checksums match', () async {
+        final Map<String, dynamic> a = <String, dynamic>{
+          'version': kVersion,
+          'properties': <String, String>{
+            'buildMode': BuildMode.debug.toString(),
+            'targetPlatform': TargetPlatform.ios.toString(),
+            'entryPoint': 'a.dart',
+          },
+          'files': <String, dynamic>{
+            'a.dart': '8a21a15fad560b799f6731d436c1b698',
+            'b.dart': '6f144e08b58cd0925328610fad7ac07c',
+          },
+        };
+        expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(a)), isTrue);
+      }, overrides: <Type, Generator>{
+        FlutterVersion: () => mockVersion,
+      });
+    });
+    group('hashCode', () {
+      testUsingContext('is consistent with equals, even if map entries are reordered', () async {
+        final Fingerprint a = Fingerprint.fromJson('{"version":"$kVersion","properties":{"a":"A","b":"B"},"files":{}}');
+        final Fingerprint b = Fingerprint.fromJson('{"version":"$kVersion","properties":{"b":"B","a":"A"},"files":{}}');
+        expect(a, b);
+        expect(a.hashCode, b.hashCode);
+      }, overrides: <Type, Generator>{
+        FlutterVersion: () => mockVersion,
+      });
+
+    });
+  });
+
+  group('readDepfile', () {
+    MemoryFileSystem fs;
+
+    setUp(() {
+      fs = MemoryFileSystem();
+    });
+
+    final Map<Type, Generator> contextOverrides = <Type, Generator>{FileSystem: () => fs};
+
+    testUsingContext('returns one file if only one is listed', () async {
+      await fs.file('a.d').writeAsString('snapshot.d: /foo/a.dart');
+      expect(await readDepfile('a.d'), unorderedEquals(<String>['/foo/a.dart']));
+    }, overrides: contextOverrides);
+
+    testUsingContext('returns multiple files', () async {
+      await fs.file('a.d').writeAsString('snapshot.d: /foo/a.dart /foo/b.dart');
+      expect(await readDepfile('a.d'), unorderedEquals(<String>[
+        '/foo/a.dart',
+        '/foo/b.dart',
+      ]));
+    }, overrides: contextOverrides);
+
+    testUsingContext('trims extra spaces between files', () async {
+      await fs.file('a.d').writeAsString('snapshot.d: /foo/a.dart    /foo/b.dart  /foo/c.dart');
+      expect(await readDepfile('a.d'), unorderedEquals(<String>[
+        '/foo/a.dart',
+        '/foo/b.dart',
+        '/foo/c.dart',
+      ]));
+    }, overrides: contextOverrides);
+
+    testUsingContext('returns files with spaces and backslashes', () async {
+      await fs.file('a.d').writeAsString(r'snapshot.d: /foo/a\ a.dart /foo/b\\b.dart /foo/c\\ c.dart');
+      expect(await readDepfile('a.d'), unorderedEquals(<String>[
+        r'/foo/a a.dart',
+        r'/foo/b\b.dart',
+        r'/foo/c\ c.dart',
+      ]));
+    }, overrides: contextOverrides);
+  });
+}
+
+class MockPlatform extends Mock implements Platform {
+  @override
+  Map<String, String> environment = <String, String>{};
+}
diff --git a/packages/flutter_tools/test/general.shard/base/flags_test.dart b/packages/flutter_tools/test/general.shard/base/flags_test.dart
new file mode 100644
index 0000000..b864a07
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/flags_test.dart
@@ -0,0 +1,93 @@
+// 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/flags.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/runner/flutter_command.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+typedef _TestMethod = FutureOr<void> Function();
+
+void main() {
+  Cache.disableLocking();
+
+  Future<void> runCommand(Iterable<String> flags, _TestMethod testMethod) async {
+    final List<String> args = <String>['test', ...flags];
+    final _TestCommand command = _TestCommand(testMethod);
+    await createTestCommandRunner(command).run(args);
+  }
+
+  testUsingContext('runCommand works as expected', () async {
+    bool testRan = false;
+    await runCommand(<String>[], () {
+      testRan = true;
+    });
+    expect(testRan, isTrue);
+  });
+
+  group('flags', () {
+    testUsingContext('returns null for undefined flags', () async {
+      await runCommand(<String>[], () {
+        expect(flags['undefined-flag'], isNull);
+      });
+    });
+
+    testUsingContext('picks up default values', () async {
+      await runCommand(<String>[], () {
+        expect(flags['verbose'], isFalse);
+        expect(flags['flag-defaults-to-false'], isFalse);
+        expect(flags['flag-defaults-to-true'], isTrue);
+        expect(flags['option-defaults-to-foo'], 'foo');
+      });
+    });
+
+    testUsingContext('returns null for flags with no default values', () async {
+      await runCommand(<String>[], () {
+        expect(flags['device-id'], isNull);
+        expect(flags['option-no-default'], isNull);
+      });
+    });
+
+    testUsingContext('picks up explicit values', () async {
+      await runCommand(<String>[
+        '--verbose',
+        '--flag-defaults-to-false',
+        '--option-no-default=explicit',
+        '--option-defaults-to-foo=qux',
+      ], () {
+        expect(flags['verbose'], isTrue);
+        expect(flags['flag-defaults-to-false'], isTrue);
+        expect(flags['option-no-default'], 'explicit');
+        expect(flags['option-defaults-to-foo'], 'qux');
+      });
+    });
+  });
+}
+
+class _TestCommand extends FlutterCommand {
+  _TestCommand(this.testMethod) {
+    argParser.addFlag('flag-defaults-to-false', defaultsTo: false);
+    argParser.addFlag('flag-defaults-to-true', defaultsTo: true);
+    argParser.addOption('option-no-default');
+    argParser.addOption('option-defaults-to-foo', defaultsTo: 'foo');
+  }
+
+  final _TestMethod testMethod;
+
+  @override
+  String get name => 'test';
+
+  @override
+  String get description => 'runs a test method';
+
+  @override
+  Future<FlutterCommandResult> runCommand() async {
+    await testMethod();
+    return null;
+  }
+}
diff --git a/packages/flutter_tools/test/general.shard/base/io_test.dart b/packages/flutter_tools/test/general.shard/base/io_test.dart
new file mode 100644
index 0000000..f70a378
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/io_test.dart
@@ -0,0 +1,34 @@
+// 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 'dart:io' as io;
+
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  group('ProcessSignal', () {
+
+    testUsingContext('signals are properly delegated', () async {
+      final MockIoProcessSignal mockSignal = MockIoProcessSignal();
+      final ProcessSignal signalUnderTest = ProcessSignal(mockSignal);
+      final StreamController<io.ProcessSignal> controller = StreamController<io.ProcessSignal>();
+
+      when(mockSignal.watch()).thenAnswer((Invocation invocation) => controller.stream);
+      controller.add(mockSignal);
+
+      expect(signalUnderTest, await signalUnderTest.watch().first);
+    });
+
+    testUsingContext('toString() works', () async {
+      expect(io.ProcessSignal.sigint.toString(), ProcessSignal.SIGINT.toString());
+    });
+  });
+}
+
+class MockIoProcessSignal extends Mock implements io.ProcessSignal {}
diff --git a/packages/flutter_tools/test/general.shard/base/logger_test.dart b/packages/flutter_tools/test/general.shard/base/logger_test.dart
new file mode 100644
index 0000000..3e36fee
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/logger_test.dart
@@ -0,0 +1,742 @@
+// 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:convert' show jsonEncode;
+
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/io.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:quiver/testing/async.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+final Generator _kNoAnsiPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
+
+void main() {
+  final String red = RegExp.escape(AnsiTerminal.red);
+  final String bold = RegExp.escape(AnsiTerminal.bold);
+  final String resetBold = RegExp.escape(AnsiTerminal.resetBold);
+  final String resetColor = RegExp.escape(AnsiTerminal.resetColor);
+
+  group('AppContext', () {
+    testUsingContext('error', () async {
+      final BufferLogger mockLogger = BufferLogger();
+      final VerboseLogger verboseLogger = VerboseLogger(mockLogger);
+
+      verboseLogger.printStatus('Hey Hey Hey Hey');
+      verboseLogger.printTrace('Oooh, I do I do I do');
+      verboseLogger.printError('Helpless!');
+
+      expect(mockLogger.statusText, matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] Hey Hey Hey Hey\n'
+                                             r'\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] Oooh, I do I do I do\n$'));
+      expect(mockLogger.traceText, '');
+      expect(mockLogger.errorText, matches( r'^\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] Helpless!\n$'));
+    }, overrides: <Type, Generator>{
+      OutputPreferences: () => OutputPreferences(showColor: false),
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('ANSI colored errors', () async {
+      final BufferLogger mockLogger = BufferLogger();
+      final VerboseLogger verboseLogger = VerboseLogger(mockLogger);
+
+      verboseLogger.printStatus('Hey Hey Hey Hey');
+      verboseLogger.printTrace('Oooh, I do I do I do');
+      verboseLogger.printError('Helpless!');
+
+      expect(
+          mockLogger.statusText,
+          matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] ' '${bold}Hey Hey Hey Hey$resetBold'
+                  r'\n\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] Oooh, I do I do I do\n$'));
+      expect(mockLogger.traceText, '');
+      expect(
+          mockLogger.errorText,
+          matches('^$red' r'\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] ' '${bold}Helpless!$resetBold$resetColor' r'\n$'));
+    }, overrides: <Type, Generator>{
+      OutputPreferences: () => OutputPreferences(showColor: true),
+      Platform: () => FakePlatform()..stdoutSupportsAnsi = true,
+    });
+  });
+
+  group('Spinners', () {
+    MockStdio mockStdio;
+    FakeStopwatch mockStopwatch;
+    int called;
+    const List<String> testPlatforms = <String>['linux', 'macos', 'windows', 'fuchsia'];
+    final RegExp secondDigits = RegExp(r'[0-9,.]*[0-9]m?s');
+
+    AnsiStatus _createAnsiStatus() {
+      mockStopwatch = FakeStopwatch();
+      return AnsiStatus(
+        message: 'Hello world',
+        timeout: const Duration(seconds: 2),
+        padding: 20,
+        onFinish: () => called += 1,
+      );
+    }
+
+    setUp(() {
+      mockStdio = MockStdio();
+      called = 0;
+    });
+
+    List<String> outputStdout() => mockStdio.writtenToStdout.join('').split('\n');
+    List<String> outputStderr() => mockStdio.writtenToStderr.join('').split('\n');
+
+    void doWhileAsync(FakeAsync time, bool doThis()) {
+      do {
+        time.elapse(const Duration(milliseconds: 1));
+      } while (doThis());
+    }
+
+    for (String testOs in testPlatforms) {
+      testUsingContext('AnsiSpinner works for $testOs (1)', () async {
+        bool done = false;
+        FakeAsync().run((FakeAsync time) {
+          final AnsiSpinner ansiSpinner = AnsiSpinner(
+            timeout: const Duration(hours: 10),
+          )..start();
+          doWhileAsync(time, () => ansiSpinner.ticks < 10);
+          List<String> lines = outputStdout();
+          expect(lines[0], startsWith(
+            platform.isWindows
+              ? ' \b\\\b|\b/\b-\b\\\b|\b/\b-'
+              : ' \b⣽\b⣻\b⢿\b⡿\b⣟\b⣯\b⣷\b⣾\b⣽\b⣻'
+            ),
+          );
+          expect(lines[0].endsWith('\n'), isFalse);
+          expect(lines.length, equals(1));
+          ansiSpinner.stop();
+          lines = outputStdout();
+          expect(lines[0], endsWith('\b \b'));
+          expect(lines.length, equals(1));
+
+          // Verify that stopping or canceling multiple times throws.
+          expect(() {
+            ansiSpinner.stop();
+          }, throwsA(isInstanceOf<AssertionError>()));
+          expect(() {
+            ansiSpinner.cancel();
+          }, throwsA(isInstanceOf<AssertionError>()));
+          done = true;
+        });
+        expect(done, isTrue);
+      }, overrides: <Type, Generator>{
+        Platform: () => FakePlatform(operatingSystem: testOs),
+        Stdio: () => mockStdio,
+      });
+
+      testUsingContext('AnsiSpinner works for $testOs (2)', () async {
+        bool done = false;
+        mockStopwatch = FakeStopwatch();
+        FakeAsync().run((FakeAsync time) {
+          final AnsiSpinner ansiSpinner = AnsiSpinner(
+            timeout: const Duration(seconds: 2),
+          )..start();
+          mockStopwatch.elapsed = const Duration(seconds: 1);
+          doWhileAsync(time, () => ansiSpinner.ticks < 10); // one second
+          expect(ansiSpinner.seemsSlow, isFalse);
+          expect(outputStdout().join('\n'), isNot(contains('This is taking an unexpectedly long time.')));
+          mockStopwatch.elapsed = const Duration(seconds: 3);
+          doWhileAsync(time, () => ansiSpinner.ticks < 30); // three seconds
+          expect(ansiSpinner.seemsSlow, isTrue);
+          // Check the 2nd line to verify there's a newline before the warning
+          expect(outputStdout()[1], contains('This is taking an unexpectedly long time.'));
+          ansiSpinner.stop();
+          expect(outputStdout().join('\n'), isNot(contains('(!)')));
+          done = true;
+        });
+        expect(done, isTrue);
+      }, overrides: <Type, Generator>{
+        Platform: () => FakePlatform(operatingSystem: testOs),
+        Stdio: () => mockStdio,
+        Stopwatch: () => mockStopwatch,
+      });
+
+      testUsingContext('Stdout startProgress on colored terminal for $testOs', () async {
+        bool done = false;
+        FakeAsync().run((FakeAsync time) {
+          final Logger logger = context.get<Logger>();
+          final Status status = logger.startProgress(
+            'Hello',
+            progressId: null,
+            timeout: timeoutConfiguration.slowOperation,
+            progressIndicatorPadding: 20, // this minus the "Hello" equals the 15 below.
+          );
+          expect(outputStderr().length, equals(1));
+          expect(outputStderr().first, isEmpty);
+          // the 5 below is the margin that is always included between the message and the time.
+          expect(outputStdout().join('\n'), matches(platform.isWindows ? r'^Hello {15} {5} {8}[\b]{8} {7}\\$' :
+                                                                         r'^Hello {15} {5} {8}[\b]{8} {7}⣽$'));
+          status.stop();
+          expect(outputStdout().join('\n'), matches(platform.isWindows ? r'^Hello {15} {5} {8}[\b]{8} {7}\\[\b]{8} {8}[\b]{8}[\d, ]{4}[\d]\.[\d]s[\n]$' :
+                                                                         r'^Hello {15} {5} {8}[\b]{8} {7}⣽[\b]{8} {8}[\b]{8}[\d, ]{4}[\d]\.[\d]s[\n]$'));
+          done = true;
+        });
+        expect(done, isTrue);
+      }, overrides: <Type, Generator>{
+        Logger: () => StdoutLogger(),
+        OutputPreferences: () => OutputPreferences(showColor: true),
+        Platform: () => FakePlatform(operatingSystem: testOs)..stdoutSupportsAnsi = true,
+        Stdio: () => mockStdio,
+      });
+
+      testUsingContext('Stdout startProgress on colored terminal pauses on $testOs', () async {
+        bool done = false;
+        FakeAsync().run((FakeAsync time) {
+          final Logger logger = context.get<Logger>();
+          final Status status = logger.startProgress(
+            'Knock Knock, Who\'s There',
+            timeout: const Duration(days: 10),
+            progressIndicatorPadding: 10,
+          );
+          logger.printStatus('Rude Interrupting Cow');
+          status.stop();
+          final String a = platform.isWindows ? '\\' : '⣽';
+          final String b = platform.isWindows ? '|' : '⣻';
+          expect(
+            outputStdout().join('\n'),
+            'Knock Knock, Who\'s There     ' // initial message
+            '        ' // placeholder so that spinner can backspace on its first tick
+            '\b\b\b\b\b\b\b\b       $a' // first tick
+            '\b\b\b\b\b\b\b\b        ' // clearing the spinner
+            '\b\b\b\b\b\b\b\b' // clearing the clearing of the spinner
+            '\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b                             ' // clearing the message
+            '\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b' // clearing the clearing of the message
+            'Rude Interrupting Cow\n' // message
+            'Knock Knock, Who\'s There     ' // message restoration
+            '        ' // placeholder so that spinner can backspace on its second tick
+            '\b\b\b\b\b\b\b\b       $b' // second tick
+            '\b\b\b\b\b\b\b\b        ' // clearing the spinner to put the time
+            '\b\b\b\b\b\b\b\b' // clearing the clearing of the spinner
+            '    0.0s\n', // replacing it with the time
+          );
+          done = true;
+        });
+        expect(done, isTrue);
+      }, overrides: <Type, Generator>{
+        Logger: () => StdoutLogger(),
+        OutputPreferences: () => OutputPreferences(showColor: true),
+        Platform: () => FakePlatform(operatingSystem: testOs)..stdoutSupportsAnsi = true,
+        Stdio: () => mockStdio,
+      });
+
+      testUsingContext('AnsiStatus works for $testOs', () {
+        final AnsiStatus ansiStatus = _createAnsiStatus();
+        bool done = false;
+        FakeAsync().run((FakeAsync time) {
+          ansiStatus.start();
+          mockStopwatch.elapsed = const Duration(seconds: 1);
+          doWhileAsync(time, () => ansiStatus.ticks < 10); // one second
+          expect(ansiStatus.seemsSlow, isFalse);
+          expect(outputStdout().join('\n'), isNot(contains('This is taking an unexpectedly long time.')));
+          expect(outputStdout().join('\n'), isNot(contains('(!)')));
+          mockStopwatch.elapsed = const Duration(seconds: 3);
+          doWhileAsync(time, () => ansiStatus.ticks < 30); // three seconds
+          expect(ansiStatus.seemsSlow, isTrue);
+          expect(outputStdout().join('\n'), contains('This is taking an unexpectedly long time.'));
+
+          // Test that the number of '\b' is correct.
+          for (String line in outputStdout()) {
+            int currLength = 0;
+            for (int i = 0; i < line.length; i += 1) {
+              currLength += line[i] == '\b' ? -1 : 1;
+              expect(currLength, isNonNegative, reason: 'The following line has overflow backtraces:\n' + jsonEncode(line));
+            }
+          }
+
+          ansiStatus.stop();
+          expect(outputStdout().join('\n'), contains('(!)'));
+          done = true;
+        });
+        expect(done, isTrue);
+      }, overrides: <Type, Generator>{
+        Platform: () => FakePlatform(operatingSystem: testOs),
+        Stdio: () => mockStdio,
+        Stopwatch: () => mockStopwatch,
+      });
+
+      testUsingContext('AnsiStatus works when canceled for $testOs', () async {
+        final AnsiStatus ansiStatus = _createAnsiStatus();
+        bool done = false;
+        FakeAsync().run((FakeAsync time) {
+          ansiStatus.start();
+          mockStopwatch.elapsed = const Duration(seconds: 1);
+          doWhileAsync(time, () => ansiStatus.ticks < 10);
+          List<String> lines = outputStdout();
+          expect(lines[0], startsWith(platform.isWindows
+              ? 'Hello world                      \b\b\b\b\b\b\b\b       \\\b\b\b\b\b\b\b\b       |\b\b\b\b\b\b\b\b       /\b\b\b\b\b\b\b\b       -\b\b\b\b\b\b\b\b       \\\b\b\b\b\b\b\b\b       |\b\b\b\b\b\b\b\b       /\b\b\b\b\b\b\b\b       -\b\b\b\b\b\b\b\b       \\\b\b\b\b\b\b\b\b       |'
+              : 'Hello world                      \b\b\b\b\b\b\b\b       ⣽\b\b\b\b\b\b\b\b       ⣻\b\b\b\b\b\b\b\b       ⢿\b\b\b\b\b\b\b\b       ⡿\b\b\b\b\b\b\b\b       ⣟\b\b\b\b\b\b\b\b       ⣯\b\b\b\b\b\b\b\b       ⣷\b\b\b\b\b\b\b\b       ⣾\b\b\b\b\b\b\b\b       ⣽\b\b\b\b\b\b\b\b       ⣻'));
+          expect(lines.length, equals(1));
+          expect(lines[0].endsWith('\n'), isFalse);
+
+          // Verify a cancel does _not_ print the time and prints a newline.
+          ansiStatus.cancel();
+          lines = outputStdout();
+          final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
+          expect(matches, isEmpty);
+          final String x = platform.isWindows ? '|' : '⣻';
+          expect(lines[0], endsWith('$x\b\b\b\b\b\b\b\b        \b\b\b\b\b\b\b\b'));
+          expect(called, equals(1));
+          expect(lines.length, equals(2));
+          expect(lines[1], equals(''));
+
+          // Verify that stopping or canceling multiple times throws.
+          expect(() { ansiStatus.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
+          expect(() { ansiStatus.stop(); }, throwsA(isInstanceOf<AssertionError>()));
+          done = true;
+        });
+        expect(done, isTrue);
+      }, overrides: <Type, Generator>{
+        Platform: () => FakePlatform(operatingSystem: testOs),
+        Stdio: () => mockStdio,
+        Stopwatch: () => mockStopwatch,
+      });
+
+      testUsingContext('AnsiStatus works when stopped for $testOs', () async {
+        final AnsiStatus ansiStatus = _createAnsiStatus();
+        bool done = false;
+        FakeAsync().run((FakeAsync time) {
+          ansiStatus.start();
+          mockStopwatch.elapsed = const Duration(seconds: 1);
+          doWhileAsync(time, () => ansiStatus.ticks < 10);
+          List<String> lines = outputStdout();
+          expect(lines, hasLength(1));
+          expect(lines[0],
+            platform.isWindows
+              ? 'Hello world                      \b\b\b\b\b\b\b\b       \\\b\b\b\b\b\b\b\b       |\b\b\b\b\b\b\b\b       /\b\b\b\b\b\b\b\b       -\b\b\b\b\b\b\b\b       \\\b\b\b\b\b\b\b\b       |\b\b\b\b\b\b\b\b       /\b\b\b\b\b\b\b\b       -\b\b\b\b\b\b\b\b       \\\b\b\b\b\b\b\b\b       |'
+              : 'Hello world                      \b\b\b\b\b\b\b\b       ⣽\b\b\b\b\b\b\b\b       ⣻\b\b\b\b\b\b\b\b       ⢿\b\b\b\b\b\b\b\b       ⡿\b\b\b\b\b\b\b\b       ⣟\b\b\b\b\b\b\b\b       ⣯\b\b\b\b\b\b\b\b       ⣷\b\b\b\b\b\b\b\b       ⣾\b\b\b\b\b\b\b\b       ⣽\b\b\b\b\b\b\b\b       ⣻',
+          );
+
+          // Verify a stop prints the time.
+          ansiStatus.stop();
+          lines = outputStdout();
+          expect(lines, hasLength(2));
+          expect(lines[0], matches(
+            platform.isWindows
+              ? r'Hello world               {8}[\b]{8} {7}\\[\b]{8} {7}|[\b]{8} {7}/[\b]{8} {7}-[\b]{8} {7}\\[\b]{8} {7}|[\b]{8} {7}/[\b]{8} {7}-[\b]{8} {7}\\[\b]{8} {7}|[\b]{8} {7} [\b]{8}[\d., ]{6}[\d]ms$'
+              : r'Hello world               {8}[\b]{8} {7}⣽[\b]{8} {7}⣻[\b]{8} {7}⢿[\b]{8} {7}⡿[\b]{8} {7}⣟[\b]{8} {7}⣯[\b]{8} {7}⣷[\b]{8} {7}⣾[\b]{8} {7}⣽[\b]{8} {7}⣻[\b]{8} {7} [\b]{8}[\d., ]{5}[\d]ms$'
+          ));
+          expect(lines[1], isEmpty);
+          final List<Match> times = secondDigits.allMatches(lines[0]).toList();
+          expect(times, isNotNull);
+          expect(times, hasLength(1));
+          final Match match = times.single;
+          expect(lines[0], endsWith(match.group(0)));
+          expect(called, equals(1));
+          expect(lines.length, equals(2));
+          expect(lines[1], equals(''));
+
+          // Verify that stopping or canceling multiple times throws.
+          expect(() { ansiStatus.stop(); }, throwsA(isInstanceOf<AssertionError>()));
+          expect(() { ansiStatus.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
+          done = true;
+        });
+        expect(done, isTrue);
+      }, overrides: <Type, Generator>{
+        Platform: () => FakePlatform(operatingSystem: testOs),
+        Stdio: () => mockStdio,
+        Stopwatch: () => mockStopwatch,
+      });
+    }
+  });
+  group('Output format', () {
+    MockStdio mockStdio;
+    SummaryStatus summaryStatus;
+    int called;
+    final RegExp secondDigits = RegExp(r'[^\b]\b\b\b\b\b[0-9]+[.][0-9]+(?:s|ms)');
+
+    setUp(() {
+      mockStdio = MockStdio();
+      called = 0;
+      summaryStatus = SummaryStatus(
+        message: 'Hello world',
+        timeout: timeoutConfiguration.slowOperation,
+        padding: 20,
+        onFinish: () => called++,
+      );
+    });
+
+    List<String> outputStdout() => mockStdio.writtenToStdout.join('').split('\n');
+    List<String> outputStderr() => mockStdio.writtenToStderr.join('').split('\n');
+
+    testUsingContext('Error logs are wrapped', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printError('0123456789' * 15);
+      final List<String> lines = outputStderr();
+      expect(outputStdout().length, equals(1));
+      expect(outputStdout().first, isEmpty);
+      expect(lines[0], equals('0123456789' * 4));
+      expect(lines[1], equals('0123456789' * 4));
+      expect(lines[2], equals('0123456789' * 4));
+      expect(lines[3], equals('0123456789' * 3));
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40, showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('Error logs are wrapped and can be indented.', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printError('0123456789' * 15, indent: 5);
+      final List<String> lines = outputStderr();
+      expect(outputStdout().length, equals(1));
+      expect(outputStdout().first, isEmpty);
+      expect(lines.length, equals(6));
+      expect(lines[0], equals('     01234567890123456789012345678901234'));
+      expect(lines[1], equals('     56789012345678901234567890123456789'));
+      expect(lines[2], equals('     01234567890123456789012345678901234'));
+      expect(lines[3], equals('     56789012345678901234567890123456789'));
+      expect(lines[4], equals('     0123456789'));
+      expect(lines[5], isEmpty);
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40, showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('Error logs are wrapped and can have hanging indent.', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printError('0123456789' * 15, hangingIndent: 5);
+      final List<String> lines = outputStderr();
+      expect(outputStdout().length, equals(1));
+      expect(outputStdout().first, isEmpty);
+      expect(lines.length, equals(6));
+      expect(lines[0], equals('0123456789012345678901234567890123456789'));
+      expect(lines[1], equals('     01234567890123456789012345678901234'));
+      expect(lines[2], equals('     56789012345678901234567890123456789'));
+      expect(lines[3], equals('     01234567890123456789012345678901234'));
+      expect(lines[4], equals('     56789'));
+      expect(lines[5], isEmpty);
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40, showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('Error logs are wrapped, indented, and can have hanging indent.', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printError('0123456789' * 15, indent: 4, hangingIndent: 5);
+      final List<String> lines = outputStderr();
+      expect(outputStdout().length, equals(1));
+      expect(outputStdout().first, isEmpty);
+      expect(lines.length, equals(6));
+      expect(lines[0], equals('    012345678901234567890123456789012345'));
+      expect(lines[1], equals('         6789012345678901234567890123456'));
+      expect(lines[2], equals('         7890123456789012345678901234567'));
+      expect(lines[3], equals('         8901234567890123456789012345678'));
+      expect(lines[4], equals('         901234567890123456789'));
+      expect(lines[5], isEmpty);
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40, showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('Stdout logs are wrapped', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printStatus('0123456789' * 15);
+      final List<String> lines = outputStdout();
+      expect(outputStderr().length, equals(1));
+      expect(outputStderr().first, isEmpty);
+      expect(lines[0], equals('0123456789' * 4));
+      expect(lines[1], equals('0123456789' * 4));
+      expect(lines[2], equals('0123456789' * 4));
+      expect(lines[3], equals('0123456789' * 3));
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40, showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('Stdout logs are wrapped and can be indented.', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printStatus('0123456789' * 15, indent: 5);
+      final List<String> lines = outputStdout();
+      expect(outputStderr().length, equals(1));
+      expect(outputStderr().first, isEmpty);
+      expect(lines.length, equals(6));
+      expect(lines[0], equals('     01234567890123456789012345678901234'));
+      expect(lines[1], equals('     56789012345678901234567890123456789'));
+      expect(lines[2], equals('     01234567890123456789012345678901234'));
+      expect(lines[3], equals('     56789012345678901234567890123456789'));
+      expect(lines[4], equals('     0123456789'));
+      expect(lines[5], isEmpty);
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40, showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('Stdout logs are wrapped and can have hanging indent.', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printStatus('0123456789' * 15, hangingIndent: 5);
+      final List<String> lines = outputStdout();
+      expect(outputStderr().length, equals(1));
+      expect(outputStderr().first, isEmpty);
+      expect(lines.length, equals(6));
+      expect(lines[0], equals('0123456789012345678901234567890123456789'));
+      expect(lines[1], equals('     01234567890123456789012345678901234'));
+      expect(lines[2], equals('     56789012345678901234567890123456789'));
+      expect(lines[3], equals('     01234567890123456789012345678901234'));
+      expect(lines[4], equals('     56789'));
+      expect(lines[5], isEmpty);
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40, showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('Stdout logs are wrapped, indented, and can have hanging indent.', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printStatus('0123456789' * 15, indent: 4, hangingIndent: 5);
+      final List<String> lines = outputStdout();
+      expect(outputStderr().length, equals(1));
+      expect(outputStderr().first, isEmpty);
+      expect(lines.length, equals(6));
+      expect(lines[0], equals('    012345678901234567890123456789012345'));
+      expect(lines[1], equals('         6789012345678901234567890123456'));
+      expect(lines[2], equals('         7890123456789012345678901234567'));
+      expect(lines[3], equals('         8901234567890123456789012345678'));
+      expect(lines[4], equals('         901234567890123456789'));
+      expect(lines[5], isEmpty);
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40, showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('Error logs are red', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printError('Pants on fire!');
+      final List<String> lines = outputStderr();
+      expect(outputStdout().length, equals(1));
+      expect(outputStdout().first, isEmpty);
+      expect(lines[0], equals('${AnsiTerminal.red}Pants on fire!${AnsiTerminal.resetColor}'));
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(showColor: true),
+      Platform: () => FakePlatform()..stdoutSupportsAnsi = true,
+      Stdio: () => mockStdio,
+    });
+
+    testUsingContext('Stdout logs are not colored', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printStatus('All good.');
+      final List<String> lines = outputStdout();
+      expect(outputStderr().length, equals(1));
+      expect(outputStderr().first, isEmpty);
+      expect(lines[0], equals('All good.'));
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(showColor: true),
+      Stdio: () => mockStdio,
+    });
+
+    testUsingContext('Stdout printStatus handle null inputs on colored terminal', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printStatus(
+        null,
+        emphasis: null,
+        color: null,
+        newline: null,
+        indent: null,
+      );
+      final List<String> lines = outputStdout();
+      expect(outputStderr().length, equals(1));
+      expect(outputStderr().first, isEmpty);
+      expect(lines[0], equals(''));
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(showColor: true),
+      Stdio: () => mockStdio,
+    });
+
+    testUsingContext('Stdout printStatus handle null inputs on non-color terminal', () async {
+      final Logger logger = context.get<Logger>();
+      logger.printStatus(
+        null,
+        emphasis: null,
+        color: null,
+        newline: null,
+        indent: null,
+      );
+      final List<String> lines = outputStdout();
+      expect(outputStderr().length, equals(1));
+      expect(outputStderr().first, isEmpty);
+      expect(lines[0], equals(''));
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('Stdout startProgress on non-color terminal', () async {
+      bool done = false;
+      FakeAsync().run((FakeAsync time) {
+        final Logger logger = context.get<Logger>();
+        final Status status = logger.startProgress(
+          'Hello',
+          progressId: null,
+          timeout: timeoutConfiguration.slowOperation,
+          progressIndicatorPadding: 20, // this minus the "Hello" equals the 15 below.
+        );
+        expect(outputStderr().length, equals(1));
+        expect(outputStderr().first, isEmpty);
+        // the 5 below is the margin that is always included between the message and the time.
+        expect(outputStdout().join('\n'), matches(platform.isWindows ? r'^Hello {15} {5}$' :
+                                                                       r'^Hello {15} {5}$'));
+        status.stop();
+        expect(outputStdout().join('\n'), matches(platform.isWindows ? r'^Hello {15} {5}[\d, ]{4}[\d]\.[\d]s[\n]$' :
+                                                                       r'^Hello {15} {5}[\d, ]{4}[\d]\.[\d]s[\n]$'));
+        done = true;
+      });
+      expect(done, isTrue);
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('SummaryStatus works when canceled', () async {
+      summaryStatus.start();
+      List<String> lines = outputStdout();
+      expect(lines[0], startsWith('Hello world              '));
+      expect(lines.length, equals(1));
+      expect(lines[0].endsWith('\n'), isFalse);
+
+      // Verify a cancel does _not_ print the time and prints a newline.
+      summaryStatus.cancel();
+      lines = outputStdout();
+      final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
+      expect(matches, isEmpty);
+      expect(lines[0], endsWith(' '));
+      expect(called, equals(1));
+      expect(lines.length, equals(2));
+      expect(lines[1], equals(''));
+
+      // Verify that stopping or canceling multiple times throws.
+      expect(() { summaryStatus.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
+      expect(() { summaryStatus.stop(); }, throwsA(isInstanceOf<AssertionError>()));
+    }, overrides: <Type, Generator>{Stdio: () => mockStdio, Platform: _kNoAnsiPlatform});
+
+    testUsingContext('SummaryStatus works when stopped', () async {
+      summaryStatus.start();
+      List<String> lines = outputStdout();
+      expect(lines[0], startsWith('Hello world              '));
+      expect(lines.length, equals(1));
+
+      // Verify a stop prints the time.
+      summaryStatus.stop();
+      lines = outputStdout();
+      final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
+      expect(matches, isNotNull);
+      expect(matches, hasLength(1));
+      final Match match = matches.first;
+      expect(lines[0], endsWith(match.group(0)));
+      expect(called, equals(1));
+      expect(lines.length, equals(2));
+      expect(lines[1], equals(''));
+
+      // Verify that stopping or canceling multiple times throws.
+      expect(() { summaryStatus.stop(); }, throwsA(isInstanceOf<AssertionError>()));
+      expect(() { summaryStatus.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
+    }, overrides: <Type, Generator>{Stdio: () => mockStdio, Platform: _kNoAnsiPlatform});
+
+    testUsingContext('sequential startProgress calls with StdoutLogger', () async {
+      final Logger logger = context.get<Logger>();
+      logger.startProgress('AAA', timeout: timeoutConfiguration.fastOperation)..stop();
+      logger.startProgress('BBB', timeout: timeoutConfiguration.fastOperation)..stop();
+      final List<String> output = outputStdout();
+      expect(output.length, equals(3));
+      // There's 61 spaces at the start: 59 (padding default) - 3 (length of AAA) + 5 (margin).
+      // Then there's a left-padded "0ms" 8 characters wide, so 5 spaces then "0ms"
+      // (except sometimes it's randomly slow so we handle up to "99,999ms").
+      expect(output[0], matches(RegExp(r'AAA[ ]{61}[\d, ]{5}[\d]ms')));
+      expect(output[1], matches(RegExp(r'BBB[ ]{61}[\d, ]{5}[\d]ms')));
+    }, overrides: <Type, Generator>{
+      Logger: () => StdoutLogger(),
+      OutputPreferences: () => OutputPreferences(showColor: false),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('sequential startProgress calls with VerboseLogger and StdoutLogger', () async {
+      final Logger logger = context.get<Logger>();
+      logger.startProgress('AAA', timeout: timeoutConfiguration.fastOperation)..stop();
+      logger.startProgress('BBB', timeout: timeoutConfiguration.fastOperation)..stop();
+      expect(outputStdout(), <Matcher>[
+        matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] AAA$'),
+        matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] AAA \(completed.*\)$'),
+        matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] BBB$'),
+        matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] BBB \(completed.*\)$'),
+        matches(r'^$'),
+      ]);
+    }, overrides: <Type, Generator>{
+      Logger: () => VerboseLogger(StdoutLogger()),
+      Stdio: () => mockStdio,
+      Platform: _kNoAnsiPlatform,
+    });
+
+    testUsingContext('sequential startProgress calls with BufferLogger', () async {
+      final BufferLogger logger = context.get<Logger>();
+      logger.startProgress('AAA', timeout: timeoutConfiguration.fastOperation)..stop();
+      logger.startProgress('BBB', timeout: timeoutConfiguration.fastOperation)..stop();
+      expect(logger.statusText, 'AAA\nBBB\n');
+    }, overrides: <Type, Generator>{
+      Logger: () => BufferLogger(),
+      Platform: _kNoAnsiPlatform,
+    });
+  });
+}
+
+class FakeStopwatch implements Stopwatch {
+  @override
+  bool get isRunning => _isRunning;
+  bool _isRunning = false;
+
+  @override
+  void start() => _isRunning = true;
+
+  @override
+  void stop() => _isRunning = false;
+
+  @override
+  Duration elapsed = Duration.zero;
+
+  @override
+  int get elapsedMicroseconds => elapsed.inMicroseconds;
+
+  @override
+  int get elapsedMilliseconds => elapsed.inMilliseconds;
+
+  @override
+  int get elapsedTicks => elapsed.inMilliseconds;
+
+  @override
+  int get frequency => 1000;
+
+  @override
+  void reset() {
+    _isRunning = false;
+    elapsed = Duration.zero;
+  }
+
+  @override
+  String toString() => '$runtimeType $elapsed $isRunning';
+}
diff --git a/packages/flutter_tools/test/general.shard/base/logs_test.dart b/packages/flutter_tools/test/general.shard/base/logs_test.dart
new file mode 100644
index 0000000..f0f3ad0
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/logs_test.dart
@@ -0,0 +1,25 @@
+// 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/base/common.dart';
+import 'package:flutter_tools/src/commands/logs.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  group('logs', () {
+    testUsingContext('fail with a bad device id', () async {
+      final LogsCommand command = LogsCommand();
+      applyMocksToCommand(command);
+      try {
+        await createTestCommandRunner(command).run(<String>['-d', 'abc123', 'logs']);
+        fail('Expect exception');
+      } on ToolExit catch (e) {
+        expect(e.exitCode ?? 1, 1);
+      }
+    });
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/base/net_test.dart b/packages/flutter_tools/test/general.shard/base/net_test.dart
new file mode 100644
index 0000000..a7dc3c0
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/net_test.dart
@@ -0,0 +1,265 @@
+// 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 'dart:typed_data';
+
+import 'package:flutter_tools/src/base/io.dart' as io;
+import 'package:flutter_tools/src/base/net.dart';
+import 'package:quiver/testing/async.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  testUsingContext('retry from 500', () async {
+    String error;
+    FakeAsync().run((FakeAsync time) {
+      fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
+        error = 'test completed unexpectedly';
+      }, onError: (dynamic exception) {
+        error = 'test failed unexpectedly: $exception';
+      });
+      expect(testLogger.statusText, '');
+      time.elapse(const Duration(milliseconds: 10000));
+      expect(testLogger.statusText,
+        'Download failed -- attempting retry 1 in 1 second...\n'
+        'Download failed -- attempting retry 2 in 2 seconds...\n'
+        'Download failed -- attempting retry 3 in 4 seconds...\n'
+        'Download failed -- attempting retry 4 in 8 seconds...\n',
+      );
+    });
+    expect(testLogger.errorText, isEmpty);
+    expect(error, isNull);
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () => () => MockHttpClient(500),
+  });
+
+  testUsingContext('retry from network error', () async {
+    String error;
+    FakeAsync().run((FakeAsync time) {
+      fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
+        error = 'test completed unexpectedly';
+      }, onError: (dynamic exception) {
+        error = 'test failed unexpectedly: $exception';
+      });
+      expect(testLogger.statusText, '');
+      time.elapse(const Duration(milliseconds: 10000));
+      expect(testLogger.statusText,
+        'Download failed -- attempting retry 1 in 1 second...\n'
+        'Download failed -- attempting retry 2 in 2 seconds...\n'
+        'Download failed -- attempting retry 3 in 4 seconds...\n'
+        'Download failed -- attempting retry 4 in 8 seconds...\n',
+      );
+    });
+    expect(testLogger.errorText, isEmpty);
+    expect(error, isNull);
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () => () => MockHttpClient(200),
+  });
+
+  testUsingContext('retry from SocketException', () async {
+    String error;
+    FakeAsync().run((FakeAsync time) {
+      fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
+        error = 'test completed unexpectedly';
+      }, onError: (dynamic exception) {
+        error = 'test failed unexpectedly: $exception';
+      });
+      expect(testLogger.statusText, '');
+      time.elapse(const Duration(milliseconds: 10000));
+      expect(testLogger.statusText,
+        'Download failed -- attempting retry 1 in 1 second...\n'
+        'Download failed -- attempting retry 2 in 2 seconds...\n'
+        'Download failed -- attempting retry 3 in 4 seconds...\n'
+        'Download failed -- attempting retry 4 in 8 seconds...\n',
+      );
+    });
+    expect(testLogger.errorText, isEmpty);
+    expect(error, isNull);
+    expect(testLogger.traceText, contains('Download error: SocketException'));
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () => () => MockHttpClientThrowing(
+      const io.SocketException('test exception handling'),
+    ),
+  });
+
+  testUsingContext('no retry from HandshakeException', () async {
+    String error;
+    FakeAsync().run((FakeAsync time) {
+      fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
+        error = 'test completed unexpectedly';
+      }, onError: (dynamic exception) {
+        error = 'test failed: $exception';
+      });
+      expect(testLogger.statusText, '');
+      time.elapse(const Duration(milliseconds: 10000));
+      expect(testLogger.statusText, '');
+    });
+    expect(error, startsWith('test failed'));
+    expect(testLogger.traceText, contains('HandshakeException'));
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () => () => MockHttpClientThrowing(
+      const io.HandshakeException('test exception handling'),
+    ),
+  });
+
+testUsingContext('retry from HttpException', () async {
+    String error;
+    FakeAsync().run((FakeAsync time) {
+      fetchUrl(Uri.parse('http://example.invalid/')).then((List<int> value) {
+        error = 'test completed unexpectedly';
+      }, onError: (dynamic exception) {
+        error = 'test failed unexpectedly: $exception';
+      });
+      expect(testLogger.statusText, '');
+      time.elapse(const Duration(milliseconds: 10000));
+      expect(testLogger.statusText,
+        'Download failed -- attempting retry 1 in 1 second...\n'
+        'Download failed -- attempting retry 2 in 2 seconds...\n'
+        'Download failed -- attempting retry 3 in 4 seconds...\n'
+        'Download failed -- attempting retry 4 in 8 seconds...\n',
+      );
+    });
+    expect(testLogger.errorText, isEmpty);
+    expect(error, isNull);
+    expect(testLogger.traceText, contains('Download error: HttpException'));
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () => () => MockHttpClientThrowing(
+      const io.HttpException('test exception handling'),
+    ),
+  });
+
+  testUsingContext('max attempts', () async {
+    String error;
+    List<int> actualResult;
+    FakeAsync().run((FakeAsync time) {
+      fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 3).then((List<int> value) {
+        actualResult = value;
+      }, onError: (dynamic exception) {
+        error = 'test failed unexpectedly: $exception';
+      });
+      expect(testLogger.statusText, '');
+      time.elapse(const Duration(milliseconds: 10000));
+      expect(testLogger.statusText,
+        'Download failed -- attempting retry 1 in 1 second...\n'
+        'Download failed -- attempting retry 2 in 2 seconds...\n'
+        'Download failed -- retry 3\n',
+      );
+    });
+    expect(testLogger.errorText, isEmpty);
+    expect(error, isNull);
+    expect(actualResult, isNull);
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () => () => MockHttpClient(500),
+  });
+
+  testUsingContext('remote file non-existant', () async {
+    final Uri invalid = Uri.parse('http://example.invalid/');
+    final bool result = await doesRemoteFileExist(invalid);
+    expect(result, false);
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () => () => MockHttpClient(404),
+  });
+
+  testUsingContext('remote file server error', () async {
+    final Uri valid = Uri.parse('http://example.valid/');
+    final bool result = await doesRemoteFileExist(valid);
+    expect(result, false);
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () => () => MockHttpClient(500),
+  });
+
+  testUsingContext('remote file exists', () async {
+    final Uri valid = Uri.parse('http://example.valid/');
+    final bool result = await doesRemoteFileExist(valid);
+    expect(result, true);
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () => () => MockHttpClient(200),
+  });
+}
+
+class MockHttpClientThrowing implements io.HttpClient {
+  MockHttpClientThrowing(this.exception);
+
+  final Exception exception;
+
+  @override
+  Future<io.HttpClientRequest> getUrl(Uri url) async {
+    throw exception;
+  }
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) {
+    throw 'io.HttpClient - $invocation';
+  }
+}
+
+class MockHttpClient implements io.HttpClient {
+  MockHttpClient(this.statusCode);
+
+  final int statusCode;
+
+  @override
+  Future<io.HttpClientRequest> getUrl(Uri url) async {
+    return MockHttpClientRequest(statusCode);
+  }
+
+  @override
+  Future<io.HttpClientRequest> headUrl(Uri url) async {
+    return MockHttpClientRequest(statusCode);
+  }
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) {
+    throw 'io.HttpClient - $invocation';
+  }
+}
+
+class MockHttpClientRequest implements io.HttpClientRequest {
+  MockHttpClientRequest(this.statusCode);
+
+  final int statusCode;
+
+  @override
+  Future<io.HttpClientResponse> close() async {
+    return MockHttpClientResponse(statusCode);
+  }
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) {
+    throw 'io.HttpClientRequest - $invocation';
+  }
+}
+
+class MockHttpClientResponse implements io.HttpClientResponse {
+  MockHttpClientResponse(this.statusCode);
+
+  @override
+  final int statusCode;
+
+  @override
+  String get reasonPhrase => '<reason phrase>';
+
+  @override
+  StreamSubscription<Uint8List> listen(
+    void onData(Uint8List event), {
+    Function onError,
+    void onDone(),
+    bool cancelOnError,
+  }) {
+    return Stream<Uint8List>.fromFuture(Future<Uint8List>.error(const io.SocketException('test')))
+      .listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+  }
+
+  @override
+  Future<dynamic> forEach(void Function(Uint8List element) action) {
+    return Future<void>.error(const io.SocketException('test'));
+  }
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) {
+    throw 'io.HttpClientResponse - $invocation';
+  }
+}
diff --git a/packages/flutter_tools/test/general.shard/base/os_test.dart b/packages/flutter_tools/test/general.shard/base/os_test.dart
new file mode 100644
index 0000000..04cf9ac
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/os_test.dart
@@ -0,0 +1,99 @@
+// 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 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/os.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+import 'package:platform/platform.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+const String kExecutable = 'foo';
+const String kPath1 = '/bar/bin/$kExecutable';
+const String kPath2 = '/another/bin/$kExecutable';
+
+void main() {
+  ProcessManager mockProcessManager;
+
+  setUp(() {
+    mockProcessManager = MockProcessManager();
+  });
+
+  group('which on POSIX', () {
+
+    testUsingContext('returns null when executable does not exist', () async {
+      when(mockProcessManager.runSync(<String>['which', kExecutable]))
+          .thenReturn(ProcessResult(0, 1, null, null));
+      final OperatingSystemUtils utils = OperatingSystemUtils();
+      expect(utils.which(kExecutable), isNull);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Platform: () => FakePlatform(operatingSystem: 'linux'),
+    });
+
+    testUsingContext('returns exactly one result', () async {
+      when(mockProcessManager.runSync(<String>['which', 'foo']))
+          .thenReturn(ProcessResult(0, 0, kPath1, null));
+      final OperatingSystemUtils utils = OperatingSystemUtils();
+      expect(utils.which(kExecutable).path, kPath1);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Platform: () => FakePlatform(operatingSystem: 'linux'),
+    });
+
+    testUsingContext('returns all results for whichAll', () async {
+      when(mockProcessManager.runSync(<String>['which', '-a', kExecutable]))
+          .thenReturn(ProcessResult(0, 0, '$kPath1\n$kPath2', null));
+      final OperatingSystemUtils utils = OperatingSystemUtils();
+      final List<File> result = utils.whichAll(kExecutable);
+      expect(result, hasLength(2));
+      expect(result[0].path, kPath1);
+      expect(result[1].path, kPath2);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Platform: () => FakePlatform(operatingSystem: 'linux'),
+    });
+  });
+
+  group('which on Windows', () {
+
+    testUsingContext('returns null when executable does not exist', () async {
+      when(mockProcessManager.runSync(<String>['where', kExecutable]))
+          .thenReturn(ProcessResult(0, 1, null, null));
+      final OperatingSystemUtils utils = OperatingSystemUtils();
+      expect(utils.which(kExecutable), isNull);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Platform: () => FakePlatform(operatingSystem: 'windows'),
+    });
+
+    testUsingContext('returns exactly one result', () async {
+      when(mockProcessManager.runSync(<String>['where', 'foo']))
+          .thenReturn(ProcessResult(0, 0, '$kPath1\n$kPath2', null));
+      final OperatingSystemUtils utils = OperatingSystemUtils();
+      expect(utils.which(kExecutable).path, kPath1);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Platform: () => FakePlatform(operatingSystem: 'windows'),
+    });
+
+    testUsingContext('returns all results for whichAll', () async {
+      when(mockProcessManager.runSync(<String>['where', kExecutable]))
+          .thenReturn(ProcessResult(0, 0, '$kPath1\n$kPath2', null));
+      final OperatingSystemUtils utils = OperatingSystemUtils();
+      final List<File> result = utils.whichAll(kExecutable);
+      expect(result, hasLength(2));
+      expect(result[0].path, kPath1);
+      expect(result[1].path, kPath2);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+      Platform: () => FakePlatform(operatingSystem: 'windows'),
+    });
+  });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
diff --git a/packages/flutter_tools/test/general.shard/base/os_utils_test.dart b/packages/flutter_tools/test/general.shard/base/os_utils_test.dart
new file mode 100644
index 0000000..a3e7714
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/os_utils_test.dart
@@ -0,0 +1,39 @@
+// 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/base/file_system.dart';
+import 'package:flutter_tools/src/base/os.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  group('OperatingSystemUtils', () {
+    Directory tempDir;
+
+    setUp(() {
+      tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_os_utils_test.');
+    });
+
+    tearDown(() {
+      tryToDelete(tempDir);
+    });
+
+    testUsingContext('makeExecutable', () async {
+      final File file = fs.file(fs.path.join(tempDir.path, 'foo.script'));
+      file.writeAsStringSync('hello world');
+      os.makeExecutable(file);
+
+      // Skip this test on windows.
+      if (!platform.isWindows) {
+        final String mode = file.statSync().modeString();
+        // rwxr--r--
+        expect(mode.substring(0, 3), endsWith('x'));
+      }
+    }, overrides: <Type, Generator>{
+      OperatingSystemUtils: () => OperatingSystemUtils(),
+    });
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/base/process_test.dart b/packages/flutter_tools/test/general.shard/base/process_test.dart
new file mode 100644
index 0000000..8ffca5e
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/process_test.dart
@@ -0,0 +1,95 @@
+// 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 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/base/process.dart';
+import 'package:flutter_tools/src/base/terminal.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, MockProcessManager;
+
+void main() {
+  group('process exceptions', () {
+    ProcessManager mockProcessManager;
+
+    setUp(() {
+      mockProcessManager = PlainMockProcessManager();
+    });
+
+    testUsingContext('runCheckedAsync exceptions should be ProcessException objects', () async {
+      when(mockProcessManager.run(<String>['false']))
+          .thenAnswer((Invocation invocation) => Future<ProcessResult>.value(ProcessResult(0, 1, '', '')));
+      expect(() async => await runCheckedAsync(<String>['false']), throwsA(isInstanceOf<ProcessException>()));
+    }, overrides: <Type, Generator>{ProcessManager: () => mockProcessManager});
+  });
+  group('shutdownHooks', () {
+    testUsingContext('runInExpectedOrder', () async {
+      int i = 1;
+      int serializeRecording1;
+      int serializeRecording2;
+      int postProcessRecording;
+      int cleanup;
+
+      addShutdownHook(() async {
+        serializeRecording1 = i++;
+      }, ShutdownStage.SERIALIZE_RECORDING);
+
+      addShutdownHook(() async {
+        cleanup = i++;
+      }, ShutdownStage.CLEANUP);
+
+      addShutdownHook(() async {
+        postProcessRecording = i++;
+      }, ShutdownStage.POST_PROCESS_RECORDING);
+
+      addShutdownHook(() async {
+        serializeRecording2 = i++;
+      }, ShutdownStage.SERIALIZE_RECORDING);
+
+      await runShutdownHooks();
+
+      expect(serializeRecording1, lessThanOrEqualTo(2));
+      expect(serializeRecording2, lessThanOrEqualTo(2));
+      expect(postProcessRecording, 3);
+      expect(cleanup, 4);
+    });
+  });
+  group('output formatting', () {
+    MockProcessManager mockProcessManager;
+    BufferLogger mockLogger;
+
+    setUp(() {
+      mockProcessManager = MockProcessManager();
+      mockLogger = BufferLogger();
+    });
+
+    MockProcess Function(List<String>) processMetaFactory(List<String> stdout, { List<String> stderr = const <String>[] }) {
+      final Stream<List<int>> stdoutStream =
+          Stream<List<int>>.fromIterable(stdout.map<List<int>>((String s) => s.codeUnits));
+      final Stream<List<int>> stderrStream =
+      Stream<List<int>>.fromIterable(stderr.map<List<int>>((String s) => s.codeUnits));
+      return (List<String> command) => MockProcess(stdout: stdoutStream, stderr: stderrStream);
+    }
+
+    testUsingContext('Command output is not wrapped.', () async {
+      final List<String> testString = <String>['0123456789' * 10];
+      mockProcessManager.processFactory = processMetaFactory(testString, stderr: testString);
+      await runCommandAndStreamOutput(<String>['command']);
+      expect(mockLogger.statusText, equals('${testString[0]}\n'));
+      expect(mockLogger.errorText, equals('${testString[0]}\n'));
+    }, overrides: <Type, Generator>{
+      Logger: () => mockLogger,
+      ProcessManager: () => mockProcessManager,
+      OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40),
+      Platform: () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false,
+    });
+  });
+}
+
+class PlainMockProcessManager extends Mock implements ProcessManager {}
diff --git a/packages/flutter_tools/test/general.shard/base/terminal_test.dart b/packages/flutter_tools/test/general.shard/base/terminal_test.dart
new file mode 100644
index 0000000..f189d2b
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/terminal_test.dart
@@ -0,0 +1,171 @@
+// 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/platform.dart';
+import 'package:flutter_tools/src/base/terminal.dart';
+import 'package:flutter_tools/src/globals.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+  group('output preferences', () {
+    testUsingContext('can wrap output', () async {
+      printStatus('0123456789' * 8);
+      expect(testLogger.statusText, equals(('0123456789' * 4 + '\n') * 2));
+    }, overrides: <Type, Generator>{
+      OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40),
+    });
+
+    testUsingContext('can turn off wrapping', () async {
+      final String testString = '0123456789' * 20;
+      printStatus(testString);
+      expect(testLogger.statusText, equals('$testString\n'));
+    }, overrides: <Type, Generator>{
+      Platform: () => FakePlatform()..stdoutSupportsAnsi = true,
+      OutputPreferences: () => OutputPreferences(wrapText: false),
+    });
+  });
+
+  group('ANSI coloring and bold', () {
+    AnsiTerminal terminal;
+
+    setUp(() {
+      terminal = AnsiTerminal();
+    });
+
+    testUsingContext('adding colors works', () {
+      for (TerminalColor color in TerminalColor.values) {
+        expect(
+          terminal.color('output', color),
+          equals('${AnsiTerminal.colorCode(color)}output${AnsiTerminal.resetColor}'),
+        );
+      }
+    }, overrides: <Type, Generator>{
+      OutputPreferences: () => OutputPreferences(showColor: true),
+      Platform: () => FakePlatform()..stdoutSupportsAnsi = true,
+    });
+
+    testUsingContext('adding bold works', () {
+      expect(
+        terminal.bolden('output'),
+        equals('${AnsiTerminal.bold}output${AnsiTerminal.resetBold}'),
+      );
+    }, overrides: <Type, Generator>{
+      OutputPreferences: () => OutputPreferences(showColor: true),
+      Platform: () => FakePlatform()..stdoutSupportsAnsi = true,
+    });
+
+    testUsingContext('nesting bold within color works', () {
+      expect(
+        terminal.color(terminal.bolden('output'), TerminalColor.blue),
+        equals('${AnsiTerminal.blue}${AnsiTerminal.bold}output${AnsiTerminal.resetBold}${AnsiTerminal.resetColor}'),
+      );
+      expect(
+        terminal.color('non-bold ${terminal.bolden('output')} also non-bold', TerminalColor.blue),
+        equals('${AnsiTerminal.blue}non-bold ${AnsiTerminal.bold}output${AnsiTerminal.resetBold} also non-bold${AnsiTerminal.resetColor}'),
+      );
+    }, overrides: <Type, Generator>{
+      OutputPreferences: () => OutputPreferences(showColor: true),
+      Platform: () => FakePlatform()..stdoutSupportsAnsi = true,
+    });
+
+    testUsingContext('nesting color within bold works', () {
+      expect(
+        terminal.bolden(terminal.color('output', TerminalColor.blue)),
+        equals('${AnsiTerminal.bold}${AnsiTerminal.blue}output${AnsiTerminal.resetColor}${AnsiTerminal.resetBold}'),
+      );
+      expect(
+        terminal.bolden('non-color ${terminal.color('output', TerminalColor.blue)} also non-color'),
+        equals('${AnsiTerminal.bold}non-color ${AnsiTerminal.blue}output${AnsiTerminal.resetColor} also non-color${AnsiTerminal.resetBold}'),
+      );
+    }, overrides: <Type, Generator>{
+      OutputPreferences: () => OutputPreferences(showColor: true),
+      Platform: () => FakePlatform()..stdoutSupportsAnsi = true,
+    });
+
+    testUsingContext('nesting color within color works', () {
+      expect(
+        terminal.color(terminal.color('output', TerminalColor.blue), TerminalColor.magenta),
+        equals('${AnsiTerminal.magenta}${AnsiTerminal.blue}output${AnsiTerminal.resetColor}${AnsiTerminal.magenta}${AnsiTerminal.resetColor}'),
+      );
+      expect(
+        terminal.color('magenta ${terminal.color('output', TerminalColor.blue)} also magenta', TerminalColor.magenta),
+        equals('${AnsiTerminal.magenta}magenta ${AnsiTerminal.blue}output${AnsiTerminal.resetColor}${AnsiTerminal.magenta} also magenta${AnsiTerminal.resetColor}'),
+      );
+    }, overrides: <Type, Generator>{
+      OutputPreferences: () => OutputPreferences(showColor: true),
+      Platform: () => FakePlatform()..stdoutSupportsAnsi = true,
+    });
+
+    testUsingContext('nesting bold within bold works', () {
+      expect(
+        terminal.bolden(terminal.bolden('output')),
+        equals('${AnsiTerminal.bold}output${AnsiTerminal.resetBold}'),
+      );
+      expect(
+        terminal.bolden('bold ${terminal.bolden('output')} still bold'),
+        equals('${AnsiTerminal.bold}bold output still bold${AnsiTerminal.resetBold}'),
+      );
+    }, overrides: <Type, Generator>{
+      OutputPreferences: () => OutputPreferences(showColor: true),
+      Platform: () => FakePlatform()..stdoutSupportsAnsi = true,
+    });
+  });
+
+  group('character input prompt', () {
+    AnsiTerminal terminalUnderTest;
+
+    setUp(() {
+      terminalUnderTest = TestTerminal();
+    });
+
+    testUsingContext('character prompt', () async {
+      mockStdInStream = Stream<String>.fromFutures(<Future<String>>[
+        Future<String>.value('d'), // Not in accepted list.
+        Future<String>.value('\n'), // Not in accepted list
+        Future<String>.value('b'),
+      ]).asBroadcastStream();
+      final String choice = await terminalUnderTest.promptForCharInput(
+        <String>['a', 'b', 'c'],
+        prompt: 'Please choose something',
+      );
+      expect(choice, 'b');
+      expect(
+          testLogger.statusText,
+          'Please choose something [a|b|c]: d\n'
+          'Please choose something [a|b|c]: \n'
+          '\n'
+          'Please choose something [a|b|c]: b\n');
+    });
+
+    testUsingContext('default character choice without displayAcceptedCharacters', () async {
+      mockStdInStream = Stream<String>.fromFutures(<Future<String>>[
+        Future<String>.value('\n'), // Not in accepted list
+      ]).asBroadcastStream();
+      final String choice = await terminalUnderTest.promptForCharInput(
+        <String>['a', 'b', 'c'],
+        prompt: 'Please choose something',
+        displayAcceptedCharacters: false,
+        defaultChoiceIndex: 1, // which is b.
+      );
+      expect(choice, 'b');
+      expect(
+          testLogger.statusText,
+          'Please choose something: \n'
+          '\n');
+    });
+  });
+}
+
+Stream<String> mockStdInStream;
+
+class TestTerminal extends AnsiTerminal {
+  @override
+  Stream<String> get keystrokes {
+    return mockStdInStream;
+  }
+}
diff --git a/packages/flutter_tools/test/general.shard/base/utils_test.dart b/packages/flutter_tools/test/general.shard/base/utils_test.dart
new file mode 100644
index 0000000..097d72f
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/utils_test.dart
@@ -0,0 +1,56 @@
+// 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/io.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/base/utils.dart';
+import 'package:platform/platform.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+  group('BotDetector', () {
+    FakePlatform fakePlatform;
+    MockStdio mockStdio;
+    BotDetector botDetector;
+
+    setUp(() {
+      fakePlatform = FakePlatform()..environment = <String, String>{};
+      mockStdio = MockStdio();
+      botDetector = const BotDetector();
+    });
+
+    group('isRunningOnBot', () {
+      testUsingContext('returns false unconditionally if BOT=false is set', () async {
+        fakePlatform.environment['BOT'] = 'false';
+        fakePlatform.environment['TRAVIS'] = 'true';
+        expect(botDetector.isRunningOnBot, isFalse);
+      }, overrides: <Type, Generator>{
+        Stdio: () => mockStdio,
+        Platform: () => fakePlatform,
+      });
+
+      testUsingContext('returns false unconditionally if FLUTTER_HOST is set', () async {
+        fakePlatform.environment['FLUTTER_HOST'] = 'foo';
+        fakePlatform.environment['TRAVIS'] = 'true';
+        expect(botDetector.isRunningOnBot, isFalse);
+      }, overrides: <Type, Generator>{
+        Stdio: () => mockStdio,
+        Platform: () => fakePlatform,
+      });
+
+      testUsingContext('returns true for non-interactive terminals', () async {
+        mockStdio.stdout.hasTerminal = true;
+        expect(botDetector.isRunningOnBot, isFalse);
+        mockStdio.stdout.hasTerminal = false;
+        expect(botDetector.isRunningOnBot, isTrue);
+      }, overrides: <Type, Generator>{
+        Stdio: () => mockStdio,
+        Platform: () => fakePlatform,
+      });
+    });
+  });
+}