[flutter_tools] refactor GenSnapshot and AotBuilder (#52091)

Remove dependency injection from GenSnapshot and AotBuilder and update tests to use FakeProcessManager
diff --git a/packages/flutter_tools/lib/src/aot.dart b/packages/flutter_tools/lib/src/aot.dart
index 77796f9..83ee0e9 100644
--- a/packages/flutter_tools/lib/src/aot.dart
+++ b/packages/flutter_tools/lib/src/aot.dart
@@ -6,15 +6,18 @@
 
 import 'package:meta/meta.dart';
 
-import 'base/build.dart';
 import 'base/common.dart';
-import 'base/io.dart';
 import 'base/logger.dart';
-import 'base/process.dart';
 import 'build_info.dart';
-import 'dart/package_map.dart';
+import 'build_system/build_system.dart';
+import 'build_system/targets/dart.dart';
+import 'build_system/targets/icon_tree_shaker.dart';
+import 'build_system/targets/ios.dart';
+import 'cache.dart';
+import 'convert.dart';
 import 'globals.dart' as globals;
 import 'ios/bitcode.dart';
+import 'project.dart';
 
 /// Builds AOT snapshots given a platform, build mode and a path to a Dart
 /// library.
@@ -26,18 +29,41 @@
     @required String mainDartFile,
     bool bitcode = kBitcodeEnabledDefault,
     bool quiet = true,
-    bool reportTimings = false,
     Iterable<DarwinArch> iosBuildArchs = defaultIOSArchs,
   }) async {
     if (platform == null) {
       throwToolExit('No AOT build platform specified');
     }
-
-    if (bitcode) {
-      if (platform != TargetPlatform.ios) {
-        throwToolExit('Bitcode is only supported on iOS (TargetPlatform is $platform).');
-      }
-      await validateBitcode(buildInfo.mode, platform);
+    Target target;
+    bool expectSo = false;
+    switch (platform) {
+      case TargetPlatform.android:
+      case TargetPlatform.darwin_x64:
+      case TargetPlatform.linux_x64:
+      case TargetPlatform.windows_x64:
+      case TargetPlatform.tester:
+      case TargetPlatform.web_javascript:
+      case TargetPlatform.android_x86:
+        throwToolExit('$platform is not supported in AOT.');
+        break;
+      case TargetPlatform.fuchsia_arm64:
+      case TargetPlatform.fuchsia_x64:
+        throwToolExit(
+          "To build release for fuchsia, use 'flutter build fuchsia --release'"
+        );
+        break;
+      case TargetPlatform.ios:
+        target = buildInfo.isRelease
+          ? const AotAssemblyRelease()
+          : const AotAssemblyProfile();
+        break;
+      case TargetPlatform.android_arm:
+      case TargetPlatform.android_arm64:
+      case TargetPlatform.android_x64:
+        expectSo = true;
+        target = buildInfo.isRelease
+          ? const AotElfRelease()
+          : const AotElfProfile();
     }
 
     Status status;
@@ -48,102 +74,45 @@
         timeout: timeoutConfiguration.slowOperation,
       );
     }
-    try {
-      final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: reportTimings);
 
-      // Compile to kernel.
-      final String kernelOut = await snapshotter.compileKernel(
-        platform: platform,
-        buildMode: buildInfo.mode,
-        mainPath: mainDartFile,
-        packagesPath: PackageMap.globalPackagesPath,
-        trackWidgetCreation: buildInfo.trackWidgetCreation,
-        outputPath: outputPath,
-        extraFrontEndOptions: buildInfo.extraFrontEndOptions,
-        dartDefines: buildInfo.dartDefines
-      );
-      if (kernelOut == null) {
-        throwToolExit('Compiler terminated unexpectedly.');
-        return;
+    final Environment environment = Environment(
+      projectDir: globals.fs.currentDirectory,
+      outputDir: globals.fs.directory(outputPath),
+      buildDir: FlutterProject.current().dartTool.childDirectory('flutter_build'),
+      cacheDir: null,
+      flutterRootDir: globals.fs.directory(Cache.flutterRoot),
+      defines: <String, String>{
+        kTargetFile: mainDartFile ?? globals.fs.path.join('lib', 'main.dart'),
+        kBuildMode: getNameForBuildMode(buildInfo.mode),
+        kTargetPlatform: getNameForTargetPlatform(platform),
+        kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(),
+        kDartDefines: jsonEncode(buildInfo.dartDefines),
+        if (buildInfo?.extraGenSnapshotOptions?.isNotEmpty ?? false)
+          kExtraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions.join(','),
+        if (buildInfo?.extraFrontEndOptions?.isNotEmpty ?? false)
+          kExtraFrontEndOptions: buildInfo.extraFrontEndOptions.join(','),
+        if (platform == TargetPlatform.ios)
+          kIosArchs: iosBuildArchs.map(getNameForDarwinArch).join(' ')
       }
-
-      // Build AOT snapshot.
-      if (platform == TargetPlatform.ios) {
-        // Determine which iOS architectures to build for.
-        final Map<DarwinArch, String> iosBuilds = <DarwinArch, String>{};
-        for (final DarwinArch arch in iosBuildArchs) {
-          iosBuilds[arch] = globals.fs.path.join(outputPath, getNameForDarwinArch(arch));
-        }
-
-        // Generate AOT snapshot and compile to arch-specific App.framework.
-        final Map<DarwinArch, Future<int>> exitCodes = <DarwinArch, Future<int>>{};
-        iosBuilds.forEach((DarwinArch iosArch, String outputPath) {
-          exitCodes[iosArch] = snapshotter.build(
-            platform: platform,
-            darwinArch: iosArch,
-            buildMode: buildInfo.mode,
-            mainPath: kernelOut,
-            packagesPath: PackageMap.globalPackagesPath,
-            outputPath: outputPath,
-            extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions,
-            bitcode: bitcode,
-            quiet: quiet,
-            splitDebugInfo: null,
-            dartObfuscation: false,
-          ).then<int>((int buildExitCode) {
-            return buildExitCode;
-          });
-        });
-
-        // Merge arch-specific App.frameworks into a multi-arch App.framework.
-        if ((await Future.wait<int>(exitCodes.values)).every((int buildExitCode) => buildExitCode == 0)) {
-          final Iterable<String> dylibs = iosBuilds.values.map<String>(
-              (String outputDir) => globals.fs.path.join(outputDir, 'App.framework', 'App'));
-          globals.fs.directory(globals.fs.path.join(outputPath, 'App.framework')).createSync();
-          await processUtils.run(
-            <String>[
-              'lipo',
-              ...dylibs,
-              '-create',
-              '-output', globals.fs.path.join(outputPath, 'App.framework', 'App'),
-            ],
-            throwOnError: true,
-          );
-        } else {
-          status?.cancel();
-          exitCodes.forEach((DarwinArch iosArch, Future<int> exitCodeFuture) async {
-            final int buildExitCode = await exitCodeFuture;
-            globals.printError('Snapshotting ($iosArch) exited with non-zero exit code: $buildExitCode');
-          });
-        }
-      } else {
-        // Android AOT snapshot.
-        final int snapshotExitCode = await snapshotter.build(
-          platform: platform,
-          buildMode: buildInfo.mode,
-          mainPath: kernelOut,
-          packagesPath: PackageMap.globalPackagesPath,
-          outputPath: outputPath,
-          extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions,
-          bitcode: false,
-          splitDebugInfo: null,
-          dartObfuscation: false,
-        );
-        if (snapshotExitCode != 0) {
-          status?.cancel();
-          throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
-        }
-      }
-    } on ProcessException catch (error) {
-      // Catch the String exceptions thrown from the `runSync` methods below.
-      status?.cancel();
-      globals.printError(error.toString());
-      return;
-    }
+    );
+    final BuildResult result = await buildSystem.build(target, environment);
     status?.stop();
 
-    if (outputPath == null) {
-      throwToolExit(null);
+    if (!result.success) {
+      for (final ExceptionMeasurement measurement in result.exceptions.values) {
+        globals.printError(measurement.exception.toString());
+      }
+      throwToolExit('The aot build failed.');
+    }
+
+    if (expectSo) {
+      environment.buildDir.childFile('app.so')
+        .copySync(globals.fs.path.join(outputPath, 'app.so'));
+    } else {
+      globals.fs.directory(globals.fs.path.join(outputPath, 'App.framework'))
+        .createSync(recursive: true);
+      environment.buildDir.childDirectory('App.framework').childFile('App')
+        .copySync(globals.fs.path.join(outputPath, 'App.framework', 'App'));
     }
 
     final String builtMessage = 'Built to $outputPath${globals.fs.path.separator}.';
diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index a1f0539..235765a 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -3,23 +3,17 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-
 import 'package:meta/meta.dart';
+import 'package:process/process.dart';
 
 import '../artifacts.dart';
 import '../build_info.dart';
-import '../bundle.dart';
-import '../compile.dart';
-import '../globals.dart' as globals;
 import '../macos/xcode.dart';
-import '../project.dart';
 
-import 'context.dart';
 import 'file_system.dart';
+import 'logger.dart';
 import 'process.dart';
 
-GenSnapshot get genSnapshot => context.get<GenSnapshot>();
-
 /// A snapshot build configuration.
 class SnapshotType {
   SnapshotType(this.platform, this.mode)
@@ -34,10 +28,18 @@
 
 /// Interface to the gen_snapshot command-line tool.
 class GenSnapshot {
-  const GenSnapshot();
+  GenSnapshot({
+    @required Artifacts artifacts,
+    @required ProcessManager processManager,
+    @required Logger logger,
+  }) : _artifacts = artifacts,
+       _processUtils = ProcessUtils(logger: logger, processManager: processManager);
 
-  static String getSnapshotterPath(SnapshotType snapshotType) {
-    return globals.artifacts.getArtifactPath(
+  final Artifacts _artifacts;
+  final ProcessUtils _processUtils;
+
+  String getSnapshotterPath(SnapshotType snapshotType) {
+    return _artifacts.getArtifactPath(
         Artifact.genSnapshot, platform: snapshotType.platform, mode: snapshotType.mode);
   }
 
@@ -51,6 +53,7 @@
     'Warning: This VM has been configured to obfuscate symbol information which violates the Dart standard.',
     '         See dartbug.com/30524 for more information.',
   };
+
   Future<int> run({
     @required SnapshotType snapshotType,
     DarwinArch darwinArch,
@@ -68,7 +71,7 @@
       snapshotterPath += '_' + getNameForDarwinArch(darwinArch);
     }
 
-    return processUtils.stream(
+    return _processUtils.stream(
       <String>[snapshotterPath, ...args],
       mapFunction: (String line) =>  kIgnoredWarnings.contains(line) ? null : line,
     );
@@ -76,7 +79,26 @@
 }
 
 class AOTSnapshotter {
-  AOTSnapshotter({this.reportTimings = false});
+  AOTSnapshotter({
+    this.reportTimings = false,
+    @required Logger logger,
+    @required FileSystem fileSystem,
+    @required Xcode xcode,
+    @required ProcessManager processManager,
+    @required Artifacts artifacts,
+  }) : _logger = logger,
+      _fileSystem = fileSystem,
+      _xcode = xcode,
+      _genSnapshot = GenSnapshot(
+        artifacts: artifacts,
+        processManager: processManager,
+        logger: logger,
+      );
+
+  final Logger _logger;
+  final FileSystem _fileSystem;
+  final Xcode _xcode;
+  final GenSnapshot _genSnapshot;
 
   /// If true then AOTSnapshotter would report timings for individual building
   /// steps (Dart front-end parsing and snapshot generation) in a stable
@@ -97,40 +119,43 @@
     @required bool dartObfuscation,
     bool quiet = false,
   }) async {
+    // TODO(cbracken): replace IOSArch with TargetPlatform.ios_{armv7,arm64}.
+    assert(platform != TargetPlatform.ios || darwinArch != null);
     if (bitcode && platform != TargetPlatform.ios) {
-      globals.printError('Bitcode is only supported for iOS.');
+      _logger.printError('Bitcode is only supported for iOS.');
       return 1;
     }
 
     if (!_isValidAotPlatform(platform, buildMode)) {
-      globals.printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
+      _logger.printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
       return 1;
     }
-    // TODO(cbracken): replace IOSArch with TargetPlatform.ios_{armv7,arm64}.
-    assert(platform != TargetPlatform.ios || darwinArch != null);
 
-    final Directory outputDir = globals.fs.directory(outputPath);
+    final Directory outputDir = _fileSystem.directory(outputPath);
     outputDir.createSync(recursive: true);
 
     final List<String> genSnapshotArgs = <String>[
       '--deterministic',
     ];
     if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) {
-      globals.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions');
+      _logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions');
       genSnapshotArgs.addAll(extraGenSnapshotOptions);
     }
 
-    final String assembly = globals.fs.path.join(outputDir.path, 'snapshot_assembly.S');
+    final String assembly = _fileSystem.path.join(outputDir.path, 'snapshot_assembly.S');
     if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64) {
-      // Assembly AOT snapshot.
-      genSnapshotArgs.add('--snapshot_kind=app-aot-assembly');
-      genSnapshotArgs.add('--assembly=$assembly');
-      genSnapshotArgs.add('--strip');
+      genSnapshotArgs.addAll(<String>[
+        '--snapshot_kind=app-aot-assembly',
+        '--assembly=$assembly',
+        '--strip'
+      ]);
     } else {
-      final String aotSharedLibrary = globals.fs.path.join(outputDir.path, 'app.so');
-      genSnapshotArgs.add('--snapshot_kind=app-aot-elf');
-      genSnapshotArgs.add('--elf=$aotSharedLibrary');
-      genSnapshotArgs.add('--strip');
+      final String aotSharedLibrary = _fileSystem.path.join(outputDir.path, 'app.so');
+      genSnapshotArgs.addAll(<String>[
+        '--snapshot_kind=app-aot-elf',
+        '--elf=$aotSharedLibrary',
+        '--strip'
+      ]);
     }
 
     if (platform == TargetPlatform.android_arm || darwinArch == DarwinArch.armv7) {
@@ -150,7 +175,7 @@
     final String debugFilename = 'app.$archName.symbols';
     final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false;
     if (shouldSplitDebugInfo) {
-      globals.fs.directory(splitDebugInfo)
+      _fileSystem.directory(splitDebugInfo)
         .createSync(recursive: true);
     }
 
@@ -161,7 +186,7 @@
       '--lazy-async-stacks',
       if (shouldSplitDebugInfo) ...<String>[
         '--dwarf-stack-traces',
-        '--save-debugging-info=${globals.fs.path.join(splitDebugInfo, debugFilename)}'
+        '--save-debugging-info=${_fileSystem.path.join(splitDebugInfo, debugFilename)}'
       ],
       if (dartObfuscation)
         '--obfuscate',
@@ -170,15 +195,13 @@
     genSnapshotArgs.add(mainPath);
 
     final SnapshotType snapshotType = SnapshotType(platform, buildMode);
-    final int genSnapshotExitCode =
-      await _timedStep('snapshot(CompileTime)', 'aot-snapshot',
-        () => genSnapshot.run(
+    final int genSnapshotExitCode = await _genSnapshot.run(
       snapshotType: snapshotType,
       additionalArgs: genSnapshotArgs,
       darwinArch: darwinArch,
-    ));
+    );
     if (genSnapshotExitCode != 0) {
-      globals.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode');
+      _logger.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode');
       return genSnapshotExitCode;
     }
 
@@ -212,7 +235,7 @@
   }) async {
     final String targetArch = getNameForDarwinArch(appleArch);
     if (!quiet) {
-      globals.printStatus('Building App.framework for $targetArch...');
+      _logger.printStatus('Building App.framework for $targetArch...');
     }
 
     final List<String> commonBuildOptions = <String>[
@@ -222,15 +245,15 @@
     ];
 
     const String embedBitcodeArg = '-fembed-bitcode';
-    final String assemblyO = globals.fs.path.join(outputPath, 'snapshot_assembly.o');
+    final String assemblyO = _fileSystem.path.join(outputPath, 'snapshot_assembly.o');
     List<String> isysrootArgs;
     if (isIOS) {
-      final String iPhoneSDKLocation = await globals.xcode.sdkLocation(SdkType.iPhone);
+      final String iPhoneSDKLocation = await _xcode.sdkLocation(SdkType.iPhone);
       if (iPhoneSDKLocation != null) {
         isysrootArgs = <String>['-isysroot', iPhoneSDKLocation];
       }
     }
-    final RunResult compileResult = await globals.xcode.cc(<String>[
+    final RunResult compileResult = await _xcode.cc(<String>[
       '-arch', targetArch,
       if (isysrootArgs != null) ...isysrootArgs,
       if (bitcode) embedBitcodeArg,
@@ -240,13 +263,13 @@
       assemblyO,
     ]);
     if (compileResult.exitCode != 0) {
-      globals.printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}');
+      _logger.printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}');
       return compileResult;
     }
 
-    final String frameworkDir = globals.fs.path.join(outputPath, 'App.framework');
-    globals.fs.directory(frameworkDir).createSync(recursive: true);
-    final String appLib = globals.fs.path.join(frameworkDir, 'App');
+    final String frameworkDir = _fileSystem.path.join(outputPath, 'App.framework');
+    _fileSystem.directory(frameworkDir).createSync(recursive: true);
+    final String appLib = _fileSystem.path.join(frameworkDir, 'App');
     final List<String> linkArgs = <String>[
       ...commonBuildOptions,
       '-dynamiclib',
@@ -258,64 +281,13 @@
       '-o', appLib,
       assemblyO,
     ];
-    final RunResult linkResult = await globals.xcode.clang(linkArgs);
+    final RunResult linkResult = await _xcode.clang(linkArgs);
     if (linkResult.exitCode != 0) {
-      globals.printError('Failed to link AOT snapshot. Linker terminated with exit code ${compileResult.exitCode}');
+      _logger.printError('Failed to link AOT snapshot. Linker terminated with exit code ${compileResult.exitCode}');
     }
     return linkResult;
   }
 
-  /// Compiles a Dart file to kernel.
-  ///
-  /// Returns the output kernel file path, or null on failure.
-  Future<String> compileKernel({
-    @required TargetPlatform platform,
-    @required BuildMode buildMode,
-    @required String mainPath,
-    @required String packagesPath,
-    @required String outputPath,
-    @required bool trackWidgetCreation,
-    @required List<String> dartDefines,
-    List<String> extraFrontEndOptions = const <String>[],
-  }) async {
-    final FlutterProject flutterProject = FlutterProject.current();
-    final Directory outputDir = globals.fs.directory(outputPath);
-    outputDir.createSync(recursive: true);
-
-    globals.printTrace('Compiling Dart to kernel: $mainPath');
-
-    if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty) {
-      globals.printTrace('Extra front-end options: $extraFrontEndOptions');
-    }
-
-    final String depfilePath = globals.fs.path.join(outputPath, 'kernel_compile.d');
-    final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(flutterProject);
-    final CompilerOutput compilerOutput =
-      await _timedStep('frontend(CompileTime)', 'aot-kernel',
-        () => kernelCompiler.compile(
-      sdkRoot: globals.artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, mode: buildMode),
-      mainPath: mainPath,
-      packagesPath: packagesPath,
-      outputFilePath: getKernelPathForTransformerOptions(
-        globals.fs.path.join(outputPath, 'app.dill'),
-        trackWidgetCreation: trackWidgetCreation,
-      ),
-      depFilePath: depfilePath,
-      extraFrontEndOptions: extraFrontEndOptions,
-      linkPlatformKernelIn: true,
-      aot: true,
-      buildMode: buildMode,
-      trackWidgetCreation: trackWidgetCreation,
-      dartDefines: dartDefines,
-    ));
-
-    // Write path to frontend_server, since things need to be re-generated when that changes.
-    final String frontendPath = globals.artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk);
-    globals.fs.directory(outputPath).childFile('frontend_server.d').writeAsStringSync('frontend_server.d: $frontendPath\n');
-
-    return compilerOutput?.outputFilename;
-  }
-
   bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) {
     if (buildMode == BuildMode.debug) {
       return false;
@@ -328,19 +300,4 @@
       TargetPlatform.darwin_x64,
     ].contains(platform);
   }
-
-  /// This method is used to measure duration of an action and emit it into
-  /// verbose output from flutter_tool for other tools (e.g. benchmark runner)
-  /// to find.
-  /// Important: external performance tracking tools expect format of this
-  /// output to be stable.
-  Future<T> _timedStep<T>(String marker, String analyticsVar, FutureOr<T> Function() action) async {
-    final Stopwatch sw = Stopwatch()..start();
-    final T value = await action();
-    if (reportTimings) {
-      globals.printStatus('$marker: ${sw.elapsedMilliseconds} ms.');
-    }
-    globals.flutterUsage.sendTiming('build', analyticsVar, Duration(milliseconds: sw.elapsedMilliseconds));
-    return value;
-  }
 }
diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart
index 0d92833..d946e22 100644
--- a/packages/flutter_tools/lib/src/base/logger.dart
+++ b/packages/flutter_tools/lib/src/base/logger.dart
@@ -697,7 +697,7 @@
     @required Duration timeout,
     @required TimeoutConfiguration timeoutConfiguration,
     @required Stopwatch stopwatch,
-    @required AnsiTerminal terminal,
+    @required Terminal terminal,
     VoidCallback onFinish,
     SlowWarningCallback slowWarningCallback,
   }) {
@@ -877,7 +877,7 @@
     @required Duration timeout,
     @required TimeoutConfiguration timeoutConfiguration,
     @required Stopwatch stopwatch,
-    @required AnsiTerminal terminal,
+    @required Terminal terminal,
     VoidCallback onFinish,
     this.slowWarningCallback,
     Stdio stdio,
@@ -893,7 +893,7 @@
   final String _backspaceChar = '\b';
   final String _clearChar = ' ';
   final Stdio _stdio;
-  final AnsiTerminal _terminal;
+  final Terminal _terminal;
 
   bool timedOut = false;
 
diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart
index ed677b2..d832c50 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/android.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart
@@ -210,7 +210,14 @@
 
   @override
   Future<void> build(Environment environment) async {
-    final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false);
+    final AOTSnapshotter snapshotter = AOTSnapshotter(
+      reportTimings: false,
+      fileSystem: globals.fs,
+      logger: globals.logger,
+      xcode: globals.xcode,
+      processManager: globals.processManager,
+      artifacts: globals.artifacts,
+    );
     final Directory output = environment.buildDir.childDirectory(_androidAbiName);
     final String splitDebugInfo = environment.defines[kSplitDebugInfo];
     if (environment.defines[kBuildMode] == null) {
diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
index afa08b7..a67e834 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
@@ -204,9 +204,7 @@
     final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
 
     // This configuration is all optional.
-    final List<String> extraFrontEndOptions = <String>[
-      ...?environment.defines[kExtraFrontEndOptions]?.split(',')
-    ];
+    final List<String> extraFrontEndOptions = environment.defines[kExtraFrontEndOptions]?.split(',');
     final List<String> fileSystemRoots = environment.defines[kFileSystemRoots]?.split(',');
     final String fileSystemScheme = environment.defines[kFileSystemScheme];
 
@@ -261,7 +259,14 @@
 
   @override
   Future<void> build(Environment environment) async {
-    final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false);
+    final AOTSnapshotter snapshotter = AOTSnapshotter(
+      reportTimings: false,
+      fileSystem: globals.fs,
+      logger: globals.logger,
+      xcode: globals.xcode,
+      processManager: globals.processManager,
+      artifacts: globals.artifacts,
+    );
     final String outputPath = environment.buildDir.path;
     if (environment.defines[kBuildMode] == null) {
       throw MissingDefineException(kBuildMode, 'aot_elf');
diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart
index 990716c..20e685a 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart
@@ -28,7 +28,14 @@
 
   @override
   Future<void> build(Environment environment) async {
-    final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false);
+    final AOTSnapshotter snapshotter = AOTSnapshotter(
+      reportTimings: false,
+      fileSystem: globals.fs,
+      logger: globals.logger,
+      xcode: globals.xcode,
+      artifacts: globals.artifacts,
+      processManager: globals.processManager,
+    );
     final String buildOutputPath = environment.buildDir.path;
     if (environment.defines[kBuildMode] == null) {
       throw MissingDefineException(kBuildMode, 'aot_assembly');
@@ -36,6 +43,8 @@
     if (environment.defines[kTargetPlatform] == null) {
       throw MissingDefineException(kTargetPlatform, 'aot_assembly');
     }
+    final List<String> extraGenSnapshotOptions = environment
+      .defines[kExtraGenSnapshotOptions]?.split(',') ?? const <String>[];
     final bool bitcode = environment.defines[kBitcodeFlag] == 'true';
     final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
     final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
@@ -65,6 +74,7 @@
         quiet: true,
         splitDebugInfo: splitDebugInfo,
         dartObfuscation: dartObfuscation,
+        extraGenSnapshotOptions: extraGenSnapshotOptions,
       ));
     }
     final List<int> results = await Future.wait(pending);
diff --git a/packages/flutter_tools/lib/src/build_system/targets/macos.dart b/packages/flutter_tools/lib/src/build_system/targets/macos.dart
index a4d5c9d..5104d99 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart
@@ -202,7 +202,15 @@
     }
     final String splitDebugInfo = environment.defines[kSplitDebugInfo];
     final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
-    final int result = await AOTSnapshotter(reportTimings: false).build(
+    final AOTSnapshotter snapshotter = AOTSnapshotter(
+      reportTimings: false,
+      fileSystem: globals.fs,
+      logger: globals.logger,
+      xcode: globals.xcode,
+      artifacts: globals.artifacts,
+      processManager: globals.processManager
+    );
+    final int result = await snapshotter.build(
       bitcode: false,
       buildMode: buildMode,
       mainPath: environment.buildDir.childFile('app.dill').path,
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart
index fb56942..f54ec5c 100644
--- a/packages/flutter_tools/lib/src/commands/build_aot.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -27,11 +27,6 @@
         allowed: <String>['android-arm', 'android-arm64', 'ios', 'android-x64'],
       )
       ..addFlag('quiet', defaultsTo: false)
-      ..addFlag('report-timings',
-        negatable: false,
-        defaultsTo: false,
-        help: 'Report timing information about build steps in machine readable form,',
-      )
       ..addMultiOption('ios-arch',
         splitCommas: true,
         defaultsTo: defaultIOSArchs.map<String>(getNameForDarwinArch),
@@ -80,7 +75,6 @@
       mainDartFile: findMainDartFile(targetFile),
       bitcode: boolArg('bitcode'),
       quiet: boolArg('quiet'),
-      reportTimings: boolArg('report-timings'),
       iosBuildArchs: stringsArg('ios-arch').map<DarwinArch>(getIOSArchForName),
     );
     return FlutterCommandResult.success();
diff --git a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart
index dd09437..1784a90 100644
--- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart
+++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart
@@ -436,7 +436,6 @@
         mainDartFile: globals.fs.path.absolute(targetFile),
         quiet: true,
         bitcode: true,
-        reportTimings: false,
         iosBuildArchs: <DarwinArch>[DarwinArch.armv7, DarwinArch.arm64],
       );
     } finally {
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 07ad8c8..beb2100 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -11,7 +11,6 @@
 import 'application_package.dart';
 import 'artifacts.dart';
 import 'asset.dart';
-import 'base/build.dart';
 import 'base/config.dart';
 import 'base/context.dart';
 import 'base/io.dart';
@@ -125,7 +124,6 @@
       FuchsiaDeviceTools: () => FuchsiaDeviceTools(),
       FuchsiaSdk: () => FuchsiaSdk(),
       FuchsiaWorkflow: () => FuchsiaWorkflow(),
-      GenSnapshot: () => const GenSnapshot(),
       GradleUtils: () => GradleUtils(),
       HotRunnerConfig: () => HotRunnerConfig(),
       IMobileDevice: () => IMobileDevice(),
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index 30ce20d..0c8b643 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -249,7 +249,7 @@
     @required ProcessManager processManager,
     @required Logger logger,
     @required FileSystem fileSystem,
-    @required AnsiTerminal terminal,
+    @required Terminal terminal,
   }) : _platform = platform,
        _fileSystem = fileSystem,
        _terminal = terminal,
@@ -259,7 +259,7 @@
   final Platform _platform;
   final FileSystem _fileSystem;
   final ProcessUtils _processUtils;
-  final AnsiTerminal _terminal;
+  final Terminal _terminal;
   final Logger _logger;
 
   static const String _executable = '/usr/bin/xcodebuild';
diff --git a/packages/flutter_tools/test/general.shard/base/build_test.dart b/packages/flutter_tools/test/general.shard/base/build_test.dart
index 6ea4d69..00a4625 100644
--- a/packages/flutter_tools/test/general.shard/base/build_test.dart
+++ b/packages/flutter_tools/test/general.shard/base/build_test.dart
@@ -2,75 +2,70 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
-import 'dart:convert';
-
 import 'package:file/memory.dart';
-import 'package:flutter_tools/src/android/android_sdk.dart';
+import 'package:mockito/mockito.dart';
+import 'package:platform/platform.dart';
+
 import 'package:flutter_tools/src/artifacts.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/base/terminal.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/process.dart';
+import 'package:flutter_tools/src/ios/xcodeproj.dart';
 import 'package:flutter_tools/src/macos/xcode.dart';
-import 'package:flutter_tools/src/version.dart';
-import 'package:flutter_tools/src/globals.dart' as globals;
-import 'package:mockito/mockito.dart';
-import 'package:process/process.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 MockProcessManager extends Mock implements ProcessManager {}
-class MockProcess extends Mock implements Process {}
+const FakeCommand kSdkPathCommand = FakeCommand(
+  command: <String>[
+    'xcrun',
+    '--sdk',
+    'iphoneos',
+    '--show-sdk-path'
+  ]
+);
 
-class _FakeGenSnapshot implements GenSnapshot {
-  _FakeGenSnapshot({
-    this.succeed = true,
-  });
+const List<String> kDefaultClang = <String>[
+  '-miphoneos-version-min=8.0',
+  '-dynamiclib',
+  '-Xlinker',
+  '-rpath',
+  '-Xlinker',
+  '@executable_path/Frameworks',
+  '-Xlinker',
+  '-rpath',
+  '-Xlinker',
+  '@loader_path/Frameworks',
+  '-install_name',
+  '@rpath/App.framework/App',
+  '-isysroot',
+  '',
+  '-o',
+  'build/foo/App.framework/App',
+  'build/foo/snapshot_assembly.o',
+];
 
-  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,
-    DarwinArch darwinArch,
-    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) {
-      globals.fs.file(filePath).writeAsString(fileContent);
-    });
-    return 0;
-  }
-}
+const List<String> kBitcodeClang = <String>[
+  '-miphoneos-version-min=8.0',
+  '-dynamiclib',
+  '-Xlinker',
+  '-rpath',
+  '-Xlinker',
+  '@executable_path/Frameworks',
+  '-Xlinker',
+  '-rpath',
+  '-Xlinker',
+  '@loader_path/Frameworks',
+  '-install_name',
+  '@rpath/App.framework/App',
+  '-fembed-bitcode',
+  '-isysroot',
+  '',
+  '-o',
+  'build/foo/App.framework/App',
+  'build/foo/snapshot_assembly.o',
+];
 
 void main() {
   group('SnapshotType', () {
@@ -81,181 +76,137 @@
       );
     });
     test('does not throw, if target platform is null', () {
-      expect(SnapshotType(null, BuildMode.release), isNotNull);
+      expect(() => SnapshotType(null, BuildMode.release), returnsNormally);
     });
   });
 
   group('GenSnapshot', () {
     GenSnapshot genSnapshot;
     MockArtifacts mockArtifacts;
-    MockProcessManager mockProcessManager;
-    MockProcess mockProc;
+    FakeProcessManager processManager;
+    BufferLogger logger;
 
     setUp(() async {
-      genSnapshot = const GenSnapshot();
       mockArtifacts = MockArtifacts();
-      mockProcessManager = MockProcessManager();
-      mockProc = MockProcess();
+      logger = BufferLogger.test();
+      processManager = FakeProcessManager.list(<  FakeCommand>[]);
+      genSnapshot = GenSnapshot(
+        artifacts: mockArtifacts,
+        logger: logger,
+        processManager: processManager,
+      );
+      when(mockArtifacts.getArtifactPath(
+        any,
+        platform: anyNamed('platform'),
+        mode: anyNamed('mode'),
+      )).thenReturn('gen_snapshot');
     });
 
-    final Map<Type, Generator> contextOverrides = <Type, Generator>{
-      Artifacts: () => mockArtifacts,
-      ProcessManager: () => mockProcessManager,
-    };
+    testWithoutContext('android_x64', () async {
+      processManager.addCommand(const FakeCommand(
+        command: <String>['gen_snapshot', '--additional_arg']
+      ));
 
-    testUsingContext('android_x64', () async {
-      when(mockArtifacts.getArtifactPath(Artifact.genSnapshot,
-              platform: TargetPlatform.android_x64, mode: BuildMode.release))
-          .thenReturn('gen_snapshot');
-      when(mockProcessManager.start(any,
-              workingDirectory: anyNamed('workingDirectory'),
-              environment: anyNamed('environment')))
-          .thenAnswer((_) => Future<Process>.value(mockProc));
-      when(mockProc.stdout).thenAnswer((_) => const Stream<List<int>>.empty());
-      when(mockProc.stderr).thenAnswer((_) => const Stream<List<int>>.empty());
-      await genSnapshot.run(
-          snapshotType:
-              SnapshotType(TargetPlatform.android_x64, BuildMode.release),
-          darwinArch: null,
-          additionalArgs: <String>['--additional_arg']);
-      verify(mockProcessManager.start(
-        <String>[
-          'gen_snapshot',
-          '--additional_arg',
-        ],
-        workingDirectory: anyNamed('workingDirectory'),
-        environment: anyNamed('environment'),
-      )).called(1);
-    }, overrides: contextOverrides);
+      final int result = await genSnapshot.run(
+        snapshotType: SnapshotType(TargetPlatform.android_x64, BuildMode.release),
+        darwinArch: null,
+        additionalArgs: <String>['--additional_arg'],
+      );
+      expect(result, 0);
+    });
 
-    testUsingContext('iOS armv7', () async {
-      when(mockArtifacts.getArtifactPath(Artifact.genSnapshot,
-              platform: TargetPlatform.ios, mode: BuildMode.release))
-          .thenReturn('gen_snapshot');
-      when(mockProcessManager.start(any,
-              workingDirectory: anyNamed('workingDirectory'),
-              environment: anyNamed('environment')))
-          .thenAnswer((_) => Future<Process>.value(mockProc));
-      when(mockProc.stdout).thenAnswer((_) => const Stream<List<int>>.empty());
-      when(mockProc.stderr).thenAnswer((_) => const Stream<List<int>>.empty());
-      await genSnapshot.run(
-          snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release),
-          darwinArch: DarwinArch.armv7,
-          additionalArgs: <String>['--additional_arg']);
-      verify(mockProcessManager.start(
-        <String>[
-          'gen_snapshot_armv7',
-          '--additional_arg',
-        ],
-        workingDirectory: anyNamed('workingDirectory'),
-        environment: anyNamed('environment')),
-      ).called(1);
-    }, overrides: contextOverrides);
+    testWithoutContext('iOS armv7', () async {
+      processManager.addCommand(const FakeCommand(
+        command: <String>['gen_snapshot_armv7', '--additional_arg']
+      ));
 
-    testUsingContext('iOS arm64', () async {
-      when(mockArtifacts.getArtifactPath(Artifact.genSnapshot,
-              platform: TargetPlatform.ios, mode: BuildMode.release))
-          .thenReturn('gen_snapshot');
-      when(mockProcessManager.start(any,
-              workingDirectory: anyNamed('workingDirectory'),
-              environment: anyNamed('environment')))
-          .thenAnswer((_) => Future<Process>.value(mockProc));
-      when(mockProc.stdout).thenAnswer((_) => const Stream<List<int>>.empty());
-      when(mockProc.stderr).thenAnswer((_) => const Stream<List<int>>.empty());
-      await genSnapshot.run(
-          snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release),
-          darwinArch: DarwinArch.arm64,
-          additionalArgs: <String>['--additional_arg']);
-      verify(mockProcessManager.start(
-        <String>[
-          'gen_snapshot_arm64',
-          '--additional_arg',
-        ],
-        workingDirectory: anyNamed('workingDirectory'),
-        environment: anyNamed('environment'),
-      )).called(1);
-    }, overrides: contextOverrides);
+      final int result = await genSnapshot.run(
+        snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release),
+        darwinArch: DarwinArch.armv7,
+        additionalArgs: <String>['--additional_arg'],
+      );
+      expect(result, 0);
+    });
 
-    testUsingContext('--strip filters outputs', () async {
-      when(mockArtifacts.getArtifactPath(Artifact.genSnapshot,
-              platform: TargetPlatform.android_x64, mode: BuildMode.release))
-          .thenReturn('gen_snapshot');
-      when(mockProcessManager.start(
-              <String>['gen_snapshot', '--strip'],
-              workingDirectory: anyNamed('workingDirectory'),
-              environment: anyNamed('environment')))
-          .thenAnswer((_) => Future<Process>.value(mockProc));
-      when(mockProc.stdout).thenAnswer((_) => const Stream<List<int>>.empty());
-      when(mockProc.stderr)
-        .thenAnswer((_) => Stream<String>.fromIterable(<String>[
-          '--ABC\n',
-          'Warning: Generating ELF library without DWARF debugging information.\n',
-          '--XYZ\n',
-        ])
-        .transform<List<int>>(utf8.encoder));
-      await genSnapshot.run(
-          snapshotType:
-              SnapshotType(TargetPlatform.android_x64, BuildMode.release),
-          darwinArch: null,
-          additionalArgs: <String>['--strip']);
-      verify(mockProcessManager.start(
-              <String>['gen_snapshot', '--strip'],
-              workingDirectory: anyNamed('workingDirectory'),
-              environment: anyNamed('environment')))
-          .called(1);
-      expect(testLogger.errorText, contains('ABC'));
-      expect(testLogger.errorText, isNot(contains('ELF library')));
-      expect(testLogger.errorText, contains('XYZ'));
-    }, overrides: contextOverrides);
+    testWithoutContext('iOS arm64', () async {
+      processManager.addCommand(const FakeCommand(
+        command: <String>['gen_snapshot_arm64', '--additional_arg']
+      ));
+
+      final int result = await genSnapshot.run(
+        snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release),
+        darwinArch: DarwinArch.arm64,
+        additionalArgs: <String>['--additional_arg'],
+      );
+      expect(result, 0);
+    });
+
+    testWithoutContext('--strip filters error output from gen_snapshot', () async {
+        processManager.addCommand(FakeCommand(
+        command: const <String>['gen_snapshot', '--strip'],
+        stderr: 'ABC\n${GenSnapshot.kIgnoredWarnings.join('\n')}\nXYZ\n'
+      ));
+
+      final int result = await genSnapshot.run(
+        snapshotType: SnapshotType(TargetPlatform.android_x64, BuildMode.release),
+        darwinArch: null,
+        additionalArgs: <String>['--strip'],
+      );
+
+      expect(result, 0);
+      expect(logger.errorText, contains('ABC'));
+      for (final String ignoredWarning in GenSnapshot.kIgnoredWarnings)  {
+        expect(logger.errorText, isNot(contains(ignoredWarning)));
+      }
+      expect(logger.errorText, contains('XYZ'));
+    });
   });
 
-  group('Snapshotter - AOT', () {
-    const String kSnapshotDart = 'snapshot.dart';
-    const String kSDKPath = '/path/to/sdk';
-    String skyEnginePath;
-
-    _FakeGenSnapshot genSnapshot;
-    MemoryFileSystem fs;
+  group('AOTSnapshotter', () {
+    MemoryFileSystem fileSystem;
     AOTSnapshotter snapshotter;
-    AOTSnapshotter snapshotterWithTimings;
-    MockAndroidSdk mockAndroidSdk;
     MockArtifacts mockArtifacts;
-    MockXcode mockXcode;
+    FakeProcessManager processManager;
+    Logger logger;
 
     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();
+      final Platform platform = FakePlatform(operatingSystem: 'macos');
+      logger = BufferLogger.test();
+      fileSystem = MemoryFileSystem.test();
       mockArtifacts = MockArtifacts();
-      mockXcode = MockXcode();
-      when(mockXcode.sdkLocation(any)).thenAnswer((_) => Future<String>.value(kSDKPath));
+      processManager = FakeProcessManager.list(<FakeCommand>[]);
+      snapshotter = AOTSnapshotter(
+        fileSystem: fileSystem,
+        logger: logger,
+        xcode: Xcode(
+          fileSystem: fileSystem,
+          logger: logger,
+          platform: FakePlatform(operatingSystem: 'macos'),
+          processManager: processManager,
+          xcodeProjectInterpreter: XcodeProjectInterpreter(
+            platform: platform,
+            processManager: processManager,
+            logger: logger,
+            fileSystem: fileSystem,
+            terminal: Terminal.test(),
+          ),
+        ),
+        artifacts: mockArtifacts,
+        processManager: processManager,
+      );
+      when(mockArtifacts.getArtifactPath(
+        Artifact.genSnapshot,
+        platform: anyNamed('platform'),
+        mode: anyNamed('mode')),
+      ).thenReturn('gen_snapshot');
     });
 
-    final Map<Type, Generator> contextOverrides = <Type, Generator>{
-      AndroidSdk: () => mockAndroidSdk,
-      Artifacts: () => mockArtifacts,
-      FileSystem: () => fs,
-      ProcessManager: () => FakeProcessManager.any(),
-      GenSnapshot: () => genSnapshot,
-      Xcode: () => mockXcode,
-    };
+    testWithoutContext('does not build iOS with debug build mode', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
 
-    testUsingContext('iOS debug AOT snapshot is invalid', () async {
-      final String outputPath = globals.fs.path.join('build', 'foo');
       expect(await snapshotter.build(
         platform: TargetPlatform.ios,
+        darwinArch: DarwinArch.arm64,
         buildMode: BuildMode.debug,
         mainPath: 'main.dill',
         packagesPath: '.packages',
@@ -264,10 +215,11 @@
         splitDebugInfo: null,
         dartObfuscation: false,
       ), isNot(equals(0)));
-    }, overrides: contextOverrides);
+    });
 
-    testUsingContext('Android arm debug AOT snapshot is invalid', () async {
-      final String outputPath = globals.fs.path.join('build', 'foo');
+    testWithoutContext('does not build android-arm with debug build mode', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+
       expect(await snapshotter.build(
         platform: TargetPlatform.android_arm,
         buildMode: BuildMode.debug,
@@ -278,10 +230,11 @@
         splitDebugInfo: null,
         dartObfuscation: false,
       ), isNot(0));
-    }, overrides: contextOverrides);
+    });
 
-    testUsingContext('Android arm64 debug AOT snapshot is invalid', () async {
-      final String outputPath = globals.fs.path.join('build', 'foo');
+    testWithoutContext('does not build android-arm64 with debug build mode', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+
       expect(await snapshotter.build(
         platform: TargetPlatform.android_arm64,
         buildMode: BuildMode.debug,
@@ -292,22 +245,50 @@
         splitDebugInfo: null,
         dartObfuscation: false,
       ), isNot(0));
-    }, overrides: contextOverrides);
+    });
 
-    testUsingContext('iOS profile AOT with bitcode uses right flags', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
-
-      final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S');
-      genSnapshot.outputs = <String, String>{
-        assembly: 'blah blah\n.section __DWARF\nblah blah\n',
-      };
-
-      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
-      when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
-      when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+    testWithoutContext('builds iOS with bitcode', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+      final String assembly = fileSystem.path.join(outputPath, 'snapshot_assembly.S');
+      processManager.addCommand(FakeCommand(
+        command: <String>[
+          'gen_snapshot_armv7',
+          '--deterministic',
+          '--snapshot_kind=app-aot-assembly',
+          '--assembly=$assembly',
+          '--strip',
+          '--no-sim-use-hardfp',
+          '--no-use-integer-division',
+          '--no-causal-async-stacks',
+          '--lazy-async-stacks',
+          'main.dill',
+        ]
+      ));
+      processManager.addCommand(kSdkPathCommand);
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'xcrun',
+          'cc',
+          '-arch',
+          'armv7',
+          '-isysroot',
+          '',
+          '-fembed-bitcode',
+          '-c',
+          'build/foo/snapshot_assembly.S',
+          '-o',
+          'build/foo/snapshot_assembly.o',
+        ]
+      ));
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'xcrun',
+          'clang',
+          '-arch',
+          'armv7',
+          ...kBitcodeClang,
+        ]
+      ));
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.ios,
@@ -322,169 +303,53 @@
       );
 
       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=$assembly',
-        '--strip',
-        '--no-sim-use-hardfp',
-        '--no-use-integer-division',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        'main.dill',
-      ]);
+      expect(processManager.hasRemainingExpectations, false);
+    });
 
-      final VerificationResult toVerifyCC = verify(mockXcode.cc(captureAny));
-      expect(toVerifyCC.callCount, 1);
-      final dynamic ccArgs = toVerifyCC.captured.first;
-      expect(ccArgs, contains('-fembed-bitcode'));
-      expect(ccArgs, contains('-isysroot'));
-      expect(ccArgs, contains(kSDKPath));
-
-      final VerificationResult toVerifyClang = verify(mockXcode.clang(captureAny));
-      expect(toVerifyClang.callCount, 1);
-      final dynamic clangArgs = toVerifyClang.captured.first;
-      expect(clangArgs, contains('-fembed-bitcode'));
-      expect(clangArgs, contains('-isysroot'));
-      expect(clangArgs, contains(kSDKPath));
-
-      final File assemblyFile = globals.fs.file(assembly);
-      expect(assemblyFile.existsSync(), true);
-      expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
-    }, overrides: contextOverrides);
-
-    testUsingContext('iOS release AOT with bitcode uses right flags', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
-
-      final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S');
-      genSnapshot.outputs = <String, String>{
-        assembly: 'blah blah\n',
-      };
-
-      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
-      when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
-      when(mockXcode.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,
-        darwinArch: DarwinArch.armv7,
-        bitcode: true,
-        splitDebugInfo: null,
-        dartObfuscation: false,
-      );
-
-      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=$assembly',
-        '--strip',
-        '--no-sim-use-hardfp',
-        '--no-use-integer-division',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        'main.dill',
-      ]);
-
-      final VerificationResult toVerifyCC = verify(mockXcode.cc(captureAny));
-      expect(toVerifyCC.callCount, 1);
-      final dynamic ccArgs = toVerifyCC.captured.first;
-      expect(ccArgs, contains('-fembed-bitcode'));
-      expect(ccArgs, contains('-isysroot'));
-      expect(ccArgs, contains(kSDKPath));
-
-      final VerificationResult toVerifyClang = verify(mockXcode.clang(captureAny));
-      expect(toVerifyClang.callCount, 1);
-      final dynamic clangArgs = toVerifyClang.captured.first;
-      expect(clangArgs, contains('-fembed-bitcode'));
-      expect(clangArgs, contains('-isysroot'));
-      expect(clangArgs, contains(kSDKPath));
-
-      final File assemblyFile = globals.fs.file(assembly);
-      expect(assemblyFile.existsSync(), true);
-    }, overrides: contextOverrides);
-
-    testUsingContext('builds iOS armv7 profile AOT snapshot', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
-
-      final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S');
-      genSnapshot.outputs = <String, String>{
-        assembly: 'blah blah\n.section __DWARF\nblah blah\n',
-      };
-
-      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
-      when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
-      when(mockXcode.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,
-        darwinArch: DarwinArch.armv7,
-        bitcode: false,
-        splitDebugInfo: null,
-        dartObfuscation: false,
-      );
-
-      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=$assembly',
-        '--strip',
-        '--no-sim-use-hardfp',
-        '--no-use-integer-division',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        'main.dill',
-      ]);
-      verifyNever(mockXcode.cc(argThat(contains('-fembed-bitcode'))));
-      verifyNever(mockXcode.clang(argThat(contains('-fembed-bitcode'))));
-
-      verify(mockXcode.cc(argThat(contains('-isysroot')))).called(1);
-      verify(mockXcode.clang(argThat(contains('-isysroot')))).called(1);
-
-      final File assemblyFile = globals.fs.file(assembly);
-      expect(assemblyFile.existsSync(), true);
-      expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
-    }, overrides: contextOverrides);
-
-    testUsingContext('builds iOS armv7 profile AOT snapshot with dwarStackTraces', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
-
-      final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S');
-      genSnapshot.outputs = <String, String>{
-        assembly: 'blah blah\n.section __DWARF\nblah blah\n',
-      };
-      final String debugPath = globals.fs.path.join('foo', 'app.ios-armv7.symbols');
-
-      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
-      when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
-      when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+    testWithoutContext('builds iOS armv7 snapshot with dwarStackTraces', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+      final String assembly = fileSystem.path.join(outputPath, 'snapshot_assembly.S');
+      final String debugPath = fileSystem.path.join('foo', 'app.ios-armv7.symbols');
+        processManager.addCommand(FakeCommand(
+        command: <String>[
+          'gen_snapshot_armv7',
+          '--deterministic',
+          '--snapshot_kind=app-aot-assembly',
+          '--assembly=$assembly',
+          '--strip',
+          '--no-sim-use-hardfp',
+          '--no-use-integer-division',
+          '--no-causal-async-stacks',
+          '--lazy-async-stacks',
+          '--dwarf-stack-traces',
+          '--save-debugging-info=$debugPath',
+          'main.dill',
+        ]
+      ));
+      processManager.addCommand(kSdkPathCommand);
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'xcrun',
+          'cc',
+          '-arch',
+          'armv7',
+          '-isysroot',
+          '',
+          '-c',
+          'build/foo/snapshot_assembly.S',
+          '-o',
+          'build/foo/snapshot_assembly.o',
+        ]
+      ));
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'xcrun',
+          'clang',
+          '-arch',
+          'armv7',
+          ...kDefaultClang,
+        ]
+      ));
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.ios,
@@ -499,47 +364,51 @@
       );
 
       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=$assembly',
-        '--strip',
-        '--no-sim-use-hardfp',
-        '--no-use-integer-division',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        '--dwarf-stack-traces',
-        '--save-debugging-info=$debugPath',
-        'main.dill',
-      ]);
-      verifyNever(mockXcode.cc(argThat(contains('-fembed-bitcode'))));
-      verifyNever(mockXcode.clang(argThat(contains('-fembed-bitcode'))));
+      expect(processManager.hasRemainingExpectations, false);
+    });
 
-      verify(mockXcode.cc(argThat(contains('-isysroot')))).called(1);
-      verify(mockXcode.clang(argThat(contains('-isysroot')))).called(1);
-
-      final File assemblyFile = globals.fs.file(assembly);
-      expect(assemblyFile.existsSync(), true);
-      expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
-    }, overrides: contextOverrides);
-
-    testUsingContext('builds iOS armv7 profile AOT snapshot with obfuscate', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
-
-      final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S');
-      genSnapshot.outputs = <String, String>{
-        assembly: 'blah blah\n.section __DWARF\nblah blah\n',
-      };
-
-      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
-      when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
-      when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+    testWithoutContext('builds iOS armv7 snapshot with obfuscate', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+      final String assembly = fileSystem.path.join(outputPath, 'snapshot_assembly.S');
+      processManager.addCommand(FakeCommand(
+        command: <String>[
+          'gen_snapshot_armv7',
+          '--deterministic',
+          '--snapshot_kind=app-aot-assembly',
+          '--assembly=$assembly',
+          '--strip',
+          '--no-sim-use-hardfp',
+          '--no-use-integer-division',
+          '--no-causal-async-stacks',
+          '--lazy-async-stacks',
+          '--obfuscate',
+          'main.dill',
+        ]
+      ));
+      processManager.addCommand(kSdkPathCommand);
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'xcrun',
+          'cc',
+          '-arch',
+          'armv7',
+          '-isysroot',
+          '',
+          '-c',
+          'build/foo/snapshot_assembly.S',
+          '-o',
+          'build/foo/snapshot_assembly.o',
+        ]
+      ));
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'xcrun',
+          'clang',
+          '-arch',
+          'armv7',
+          ...kDefaultClang,
+        ]
+      ));
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.ios,
@@ -554,86 +423,50 @@
       );
 
       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=$assembly',
-        '--strip',
-        '--no-sim-use-hardfp',
-        '--no-use-integer-division',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        '--obfuscate',
-        'main.dill',
-      ]);
-      verifyNever(mockXcode.cc(argThat(contains('-fembed-bitcode'))));
-      verifyNever(mockXcode.clang(argThat(contains('-fembed-bitcode'))));
+      expect(processManager.hasRemainingExpectations, false);
+    });
 
-      verify(mockXcode.cc(argThat(contains('-isysroot')))).called(1);
-      verify(mockXcode.clang(argThat(contains('-isysroot')))).called(1);
 
-      final File assemblyFile = globals.fs.file(assembly);
-      expect(assemblyFile.existsSync(), true);
-      expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
-    }, overrides: contextOverrides);
-
-    testUsingContext('builds iOS arm64 profile AOT snapshot', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
-
-      genSnapshot.outputs = <String, String>{
-        globals.fs.path.join(outputPath, 'snapshot_assembly.S'): '',
-      };
-
-      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
-      when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
-      when(mockXcode.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,
-        darwinArch: DarwinArch.arm64,
-        bitcode: false,
-        splitDebugInfo: null,
-        dartObfuscation: false,
-      );
-
-      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=${globals.fs.path.join(outputPath, 'snapshot_assembly.S')}',
-        '--strip',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        'main.dill',
-      ]);
-    }, overrides: contextOverrides);
-
-    testUsingContext('builds iOS release armv7 AOT snapshot', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
-
-      genSnapshot.outputs = <String, String>{
-        globals.fs.path.join(outputPath, 'snapshot_assembly.S'): '',
-      };
-
-      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
-      when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
-      when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+    testWithoutContext('builds iOS armv7 snapshot', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+      processManager.addCommand(FakeCommand(
+        command: <String>[
+          'gen_snapshot_armv7',
+          '--deterministic',
+          '--snapshot_kind=app-aot-assembly',
+          '--assembly=${fileSystem.path.join(outputPath, 'snapshot_assembly.S')}',
+          '--strip',
+          '--no-sim-use-hardfp',
+          '--no-use-integer-division',
+          '--no-causal-async-stacks',
+          '--lazy-async-stacks',
+          'main.dill',
+        ]
+      ));
+      processManager.addCommand(kSdkPathCommand);
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'xcrun',
+          'cc',
+          '-arch',
+          'armv7',
+          '-isysroot',
+          '',
+          '-c',
+          'build/foo/snapshot_assembly.S',
+          '-o',
+          'build/foo/snapshot_assembly.o',
+        ]
+      ));
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'xcrun',
+          'clang',
+          '-arch',
+          'armv7',
+          ...kDefaultClang,
+        ]
+      ));
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.ios,
@@ -648,35 +481,47 @@
       );
 
       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=${globals.fs.path.join(outputPath, 'snapshot_assembly.S')}',
-        '--strip',
-        '--no-sim-use-hardfp',
-        '--no-use-integer-division',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        'main.dill',
-      ]);
-    }, overrides: contextOverrides);
+      expect(processManager.hasRemainingExpectations, false);
+    });
 
-    testUsingContext('builds iOS release arm64 AOT snapshot', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
-
-      genSnapshot.outputs = <String, String>{
-        globals.fs.path.join(outputPath, 'snapshot_assembly.S'): '',
-      };
-
-      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
-      when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
-      when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+    testWithoutContext('builds iOS arm64 snapshot', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+      processManager.addCommand(FakeCommand(
+        command: <String>[
+          'gen_snapshot_arm64',
+          '--deterministic',
+          '--snapshot_kind=app-aot-assembly',
+          '--assembly=${fileSystem.path.join(outputPath, 'snapshot_assembly.S')}',
+          '--strip',
+          '--no-causal-async-stacks',
+          '--lazy-async-stacks',
+          'main.dill',
+        ]
+      ));
+      processManager.addCommand(kSdkPathCommand);
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'xcrun',
+          'cc',
+          '-arch',
+          'arm64',
+          '-isysroot',
+          '',
+          '-c',
+          'build/foo/snapshot_assembly.S',
+          '-o',
+          'build/foo/snapshot_assembly.o',
+        ]
+      ));
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'xcrun',
+          'clang',
+          '-arch',
+          'arm64',
+          ...kDefaultClang,
+        ]
+      ));
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.ios,
@@ -691,25 +536,25 @@
       );
 
       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=${globals.fs.path.join(outputPath, 'snapshot_assembly.S')}',
-        '--strip',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        'main.dill',
-      ]);
-    }, overrides: contextOverrides);
+      expect(processManager.hasRemainingExpectations, false);
+    });
 
-    testUsingContext('builds shared library for android-arm', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
+    testWithoutContext('builds shared library for android-arm (32bit)', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'gen_snapshot',
+          '--deterministic',
+          '--snapshot_kind=app-aot-elf',
+          '--elf=build/foo/app.so',
+          '--strip',
+          '--no-sim-use-hardfp',
+          '--no-use-integer-division',
+          '--no-causal-async-stacks',
+          '--lazy-async-stacks',
+          'main.dill',
+        ]
+      ));
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.android_arm,
@@ -723,28 +568,28 @@
       );
 
       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',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        'main.dill',
-      ]);
-    }, overrides: contextOverrides);
+      expect(processManager.hasRemainingExpectations, false);
+    });
 
-    testUsingContext('builds shared library for android-arm with dwarf stack traces', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      final String debugPath = globals.fs.path.join('foo', 'app.android-arm.symbols');
-      globals.fs.directory(outputPath).createSync(recursive: true);
+    testWithoutContext('builds shared library for android-arm with dwarf stack traces', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+      final String debugPath = fileSystem.path.join('foo', 'app.android-arm.symbols');
+      processManager.addCommand(FakeCommand(
+        command: <String>[
+          'gen_snapshot',
+          '--deterministic',
+          '--snapshot_kind=app-aot-elf',
+          '--elf=build/foo/app.so',
+          '--strip',
+          '--no-sim-use-hardfp',
+          '--no-use-integer-division',
+          '--no-causal-async-stacks',
+          '--lazy-async-stacks',
+          '--dwarf-stack-traces',
+          '--save-debugging-info=$debugPath',
+          'main.dill',
+        ]
+      ));
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.android_arm,
@@ -758,29 +603,26 @@
       );
 
       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',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        '--dwarf-stack-traces',
-        '--save-debugging-info=$debugPath',
-        'main.dill',
-      ]);
-    }, overrides: contextOverrides);
+      expect(processManager.hasRemainingExpectations, false);
+    });
 
-    testUsingContext('builds shared library for android-arm with obfuscate', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
+    testWithoutContext('builds shared library for android-arm with obfuscate', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'gen_snapshot',
+          '--deterministic',
+          '--snapshot_kind=app-aot-elf',
+          '--elf=build/foo/app.so',
+          '--strip',
+          '--no-sim-use-hardfp',
+          '--no-use-integer-division',
+          '--no-causal-async-stacks',
+          '--lazy-async-stacks',
+          '--obfuscate',
+          'main.dill',
+        ]
+      ));
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.android_arm,
@@ -794,28 +636,25 @@
       );
 
       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',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        '--obfuscate',
-        'main.dill',
-      ]);
-    }, overrides: contextOverrides);
+      expect(processManager.hasRemainingExpectations, false);
+    });
 
-    testUsingContext('builds shared library for android-arm without dwarf stack traces due to empty string', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
+    testWithoutContext('builds shared library for android-arm without dwarf stack traces due to empty string', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'gen_snapshot',
+          '--deterministic',
+          '--snapshot_kind=app-aot-elf',
+          '--elf=build/foo/app.so',
+          '--strip',
+          '--no-sim-use-hardfp',
+          '--no-use-integer-division',
+          '--no-causal-async-stacks',
+          '--lazy-async-stacks',
+          'main.dill',
+        ]
+      ));
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.android_arm,
@@ -829,27 +668,23 @@
       );
 
       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',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        'main.dill',
-      ]);
-    }, overrides: contextOverrides);
+       expect(processManager.hasRemainingExpectations, false);
+    });
 
-    testUsingContext('builds shared library for android-arm64', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
+    testWithoutContext('builds shared library for android-arm64', () async {
+      final String outputPath = fileSystem.path.join('build', 'foo');
+      processManager.addCommand(const FakeCommand(
+        command: <String>[
+          'gen_snapshot',
+          '--deterministic',
+          '--snapshot_kind=app-aot-elf',
+          '--elf=build/foo/app.so',
+          '--strip',
+          '--no-causal-async-stacks',
+          '--lazy-async-stacks',
+          'main.dill',
+        ]
+      ));
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.android_arm64,
@@ -863,48 +698,9 @@
       );
 
       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',
-        '--no-causal-async-stacks',
-        '--lazy-async-stacks',
-        'main.dill',
-      ]);
-    }, overrides: contextOverrides);
-
-    testUsingContext('reports timing', () async {
-      globals.fs.file('main.dill').writeAsStringSync('binary magic');
-
-      final String outputPath = globals.fs.path.join('build', 'foo');
-      globals.fs.directory(outputPath).createSync(recursive: true);
-
-      genSnapshot.outputs = <String, String>{
-        globals.fs.path.join(outputPath, 'app.so'): '',
-      };
-
-      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
-      when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
-      when(mockXcode.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,
-        bitcode: false,
-        splitDebugInfo: null,
-        dartObfuscation: false,
-      );
-
-      expect(genSnapshotExitCode, 0);
-      expect(genSnapshot.callCount, 1);
-      expect(testLogger.statusText, matches(RegExp(r'snapshot\(CompileTime\): \d+ ms.')));
-    }, overrides: contextOverrides);
+      expect(processManager.hasRemainingExpectations, false);
+    });
   });
 }
+
+class MockArtifacts extends Mock implements Artifacts {}
diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart
index 0cee898..e0ebb09 100644
--- a/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart
@@ -5,7 +5,6 @@
 import 'package:flutter_tools/src/base/build.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
-import 'package:flutter_tools/src/base/process.dart';
 import 'package:flutter_tools/src/build_system/build_system.dart';
 import 'package:flutter_tools/src/build_system/targets/dart.dart';
 import 'package:flutter_tools/src/build_system/targets/macos.dart';
@@ -49,7 +48,6 @@
   Testbed testbed;
   Environment environment;
   MockPlatform mockPlatform;
-  MockXcode mockXcode;
 
   setUpAll(() {
     Cache.disableLocking();
@@ -57,7 +55,6 @@
   });
 
   setUp(() {
-    mockXcode = MockXcode();
     mockPlatform = MockPlatform();
     when(mockPlatform.isWindows).thenReturn(false);
     when(mockPlatform.isMacOS).thenReturn(true);
@@ -193,35 +190,6 @@
 
     expect(outputFramework.readAsStringSync(), 'DEF');
   }));
-
-  test('release/profile macOS compilation uses correct gen_snapshot', () => testbed.run(() async {
-    when(genSnapshot.run(
-      snapshotType: anyNamed('snapshotType'),
-      additionalArgs: anyNamed('additionalArgs'),
-      darwinArch: anyNamed('darwinArch'),
-    )).thenAnswer((Invocation invocation) {
-      environment.buildDir.childFile('snapshot_assembly.o').createSync();
-      environment.buildDir.childFile('snapshot_assembly.S').createSync();
-      return Future<int>.value(0);
-    });
-    when(mockXcode.cc(any)).thenAnswer((Invocation invocation) {
-      return Future<RunResult>.value(RunResult(FakeProcessResult()..exitCode = 0, <String>['test']));
-    });
-    when(mockXcode.clang(any)).thenAnswer((Invocation invocation) {
-      return Future<RunResult>.value(RunResult(FakeProcessResult()..exitCode = 0, <String>['test']));
-    });
-    environment.buildDir.childFile('app.dill').createSync(recursive: true);
-    globals.fs.file('.packages')
-      ..createSync()
-      ..writeAsStringSync('''
-# Generated
-sky_engine:file:///bin/cache/pkg/sky_engine/lib/
-flutter_tools:lib/''');
-    await const CompileMacOSFramework().build(environment..defines[kBuildMode] = 'release');
-  }, overrides: <Type, Generator>{
-    GenSnapshot: () => MockGenSnapshot(),
-    Xcode: () => mockXcode,
-  }));
 }
 
 class MockPlatform extends Mock implements Platform {}