Revert "Rollback patch that broke microbenchmarks" (#11641)

* Revert "Fix a typo in the saved certificate error message (#11640)"

This reverts commit bfda885a9de024b61c3279e7848cc91e8c81adc1.

* Revert "Rollback patch that broke microbenchmarks (#11616)"

This reverts commit 70fe6f4c232111d61187ea5c2b511bc4ebb339ca.
diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index 6b48ea6..f5f7f8a 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -2,11 +2,48 @@
 // 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' show JSON;
 
 import 'package:crypto/crypto.dart' show md5;
+import 'package:meta/meta.dart';
 
+import '../artifacts.dart';
+import '../build_info.dart';
+import '../globals.dart';
+import 'context.dart';
 import 'file_system.dart';
+import 'process.dart';
+
+GenSnapshot get genSnapshot => context.putIfAbsent(GenSnapshot, () => const GenSnapshot());
+
+class GenSnapshot {
+  const GenSnapshot();
+
+  Future<int> run({
+    @required TargetPlatform targetPlatform,
+    @required BuildMode buildMode,
+    @required String packagesPath,
+    @required String depfilePath,
+    Iterable<String> additionalArgs: const <String>[],
+  }) {
+    final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData);
+    final String isolateSnapshotData = artifacts.getArtifactPath(Artifact.isolateSnapshotData);
+    final List<String> args = <String>[
+      '--assert_initializer',
+      '--await_is_keyword',
+      '--causal_async_stacks',
+      '--vm_snapshot_data=$vmSnapshotData',
+      '--isolate_snapshot_data=$isolateSnapshotData',
+      '--packages=$packagesPath',
+      '--dependencies=$depfilePath',
+      '--print_snapshot_sizes',
+    ]..addAll(additionalArgs);
+
+    final String snapshotterPath = artifacts.getArtifactPath(Artifact.genSnapshot, targetPlatform, buildMode);
+    return runCommandAndStreamOutput(<String>[snapshotterPath]..addAll(args));
+  }
+}
 
 /// A collection of checksums for a set of input files.
 ///
@@ -42,3 +79,134 @@
   @override
   int get hashCode => _checksums.hashCode;
 }
+
+final RegExp _separatorExpr = new RegExp(r'([^\\]) ');
+final RegExp _escapeExpr = new RegExp(r'\\(.)');
+
+/// Parses a VM snapshot dependency file.
+///
+/// Snapshot dependency files are a single line mapping the output snapshot to a
+/// space-separated list of input files used to generate that output. Spaces and
+/// backslashes are escaped with a backslash. e.g,
+///
+/// outfile : file1.dart fil\\e2.dart fil\ e3.dart
+///
+/// will return a set containing: 'file1.dart', 'fil\e2.dart', 'fil e3.dart'.
+Future<Set<String>> readDepfile(String depfilePath) async {
+  // Depfile format:
+  // outfile1 outfile2 : file1.dart file2.dart file3.dart
+  final String contents = await fs.file(depfilePath).readAsString();
+  final String dependencies = contents.split(': ')[1];
+  return dependencies
+      .replaceAllMapped(_separatorExpr, (Match match) => '${match.group(1)}\n')
+      .split('\n')
+      .map((String path) => path.replaceAllMapped(_escapeExpr, (Match match) => match.group(1)).trim())
+      .where((String path) => path.isNotEmpty)
+      .toSet();
+}
+
+/// Dart snapshot builder.
+///
+/// Builds Dart snapshots in one of three modes:
+///   * Script snapshot: architecture-independent snapshot of a Dart script core
+///     libraries.
+///   * AOT snapshot: architecture-specific ahead-of-time compiled snapshot
+///     suitable for loading with `mmap`.
+///   * Assembly AOT snapshot: architecture-specific ahead-of-time compile to
+///     assembly suitable for compilation as a static or dynamic library.
+class Snapshotter {
+  /// Builds an architecture-independent snapshot of the specified script.
+  Future<int> buildScriptSnapshot({
+    @required String mainPath,
+    @required String snapshotPath,
+    @required String depfilePath,
+    @required String packagesPath
+  }) async {
+    final List<String> args = <String>[
+      '--snapshot_kind=script',
+      '--script_snapshot=$snapshotPath',
+      mainPath,
+    ];
+
+    final String checksumsPath = '$depfilePath.checksums';
+    final int exitCode = await _build(
+      outputSnapshotPath: snapshotPath,
+      packagesPath: packagesPath,
+      snapshotArgs: args,
+      depfilePath: depfilePath,
+      mainPath: mainPath,
+      checksumsPath: checksumsPath,
+    );
+    if (exitCode != 0)
+      return exitCode;
+    await _writeChecksum(snapshotPath, depfilePath, mainPath, checksumsPath);
+    return exitCode;
+  }
+
+  /// Builds an architecture-specific ahead-of-time compiled snapshot of the specified script.
+  Future<Null> buildAotSnapshot() async {
+    throw new UnimplementedError('AOT snapshotting not yet implemented');
+  }
+
+  Future<int> _build({
+    @required List<String> snapshotArgs,
+    @required String outputSnapshotPath,
+    @required String packagesPath,
+    @required String depfilePath,
+    @required String mainPath,
+    @required String checksumsPath,
+  }) async {
+    if (!await _isBuildRequired(outputSnapshotPath, depfilePath, mainPath, checksumsPath)) {
+      printTrace('Skipping snapshot build. Checksums match.');
+      return 0;
+    }
+
+    // Build the snapshot.
+    final int exitCode = await genSnapshot.run(
+        targetPlatform: null,
+        buildMode: BuildMode.debug,
+        packagesPath: packagesPath,
+        depfilePath: depfilePath,
+        additionalArgs: snapshotArgs,
+    );
+    if (exitCode != 0)
+      return exitCode;
+
+    _writeChecksum(outputSnapshotPath, depfilePath, mainPath, checksumsPath);
+    return 0;
+  }
+
+  Future<bool> _isBuildRequired(String outputSnapshotPath, String depfilePath, String mainPath, String checksumsPath) async {
+    final File checksumFile = fs.file(checksumsPath);
+    final File outputSnapshotFile = fs.file(outputSnapshotPath);
+    final File depfile = fs.file(depfilePath);
+    if (!outputSnapshotFile.existsSync() || !depfile.existsSync() || !checksumFile.existsSync())
+      return true;
+
+    try {
+      if (checksumFile.existsSync()) {
+        final Checksum oldChecksum = new Checksum.fromJson(await checksumFile.readAsString());
+        final Set<String> checksumPaths = await readDepfile(depfilePath)
+          ..addAll(<String>[outputSnapshotPath, mainPath]);
+        final Checksum newChecksum = new Checksum.fromFiles(checksumPaths);
+        return oldChecksum != newChecksum;
+      }
+    } catch (e, s) {
+      // Log exception and continue, this step is a performance improvement only.
+      printTrace('Error during snapshot checksum output: $e\n$s');
+    }
+    return true;
+  }
+
+  Future<Null> _writeChecksum(String outputSnapshotPath, String depfilePath, String mainPath, String checksumsPath) async {
+    try {
+      final Set<String> checksumPaths = await readDepfile(depfilePath)
+        ..addAll(<String>[outputSnapshotPath, mainPath]);
+      final Checksum checksum = new Checksum.fromFiles(checksumPaths);
+      await fs.file(checksumsPath).writeAsString(checksum.toJson());
+    } catch (e, s) {
+      // Log exception and continue, this step is a performance improvement only.
+      print('Error during snapshot checksum output: $e\n$s');
+    }
+  }
+}
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart
index f042441..9414cf0 100644
--- a/packages/flutter_tools/lib/src/commands/build_aot.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 
 import '../artifacts.dart';
+import '../base/build.dart';
 import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/logger.dart';
@@ -149,13 +150,16 @@
   final String uiPath = fs.path.join(skyEnginePkg, 'lib', 'ui', 'ui.dart');
   final String vmServicePath = fs.path.join(skyEnginePkg, 'sdk_ext', 'vmservice_io.dart');
 
-  final List<String> filePaths = <String>[
+  final List<String> inputPaths = <String>[
     vmEntryPoints,
     ioEntryPoints,
     uiPath,
     vmServicePath,
+    mainPath,
   ];
 
+  final Set<String> outputPaths = new Set<String>();
+
   // These paths are used only on iOS.
   String snapshotDartIOS;
   String assembly;
@@ -164,13 +168,15 @@
     case TargetPlatform.android_arm:
     case TargetPlatform.android_x64:
     case TargetPlatform.android_x86:
+      outputPaths.addAll(<String>[
+        vmSnapshotData,
+        isolateSnapshotData,
+      ]);
       break;
     case TargetPlatform.ios:
       snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode);
       assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');
-      filePaths.addAll(<String>[
-        snapshotDartIOS,
-      ]);
+      inputPaths.add(snapshotDartIOS);
       break;
     case TargetPlatform.darwin_x64:
     case TargetPlatform.linux_x64:
@@ -179,9 +185,9 @@
       assert(false);
   }
 
-  final List<String> missingFiles = filePaths.where((String p) => !fs.isFileSync(p)).toList();
-  if (missingFiles.isNotEmpty) {
-    printError('Missing files: $missingFiles');
+  final Iterable<String> missingInputs = inputPaths.where((String p) => !fs.isFileSync(p));
+  if (missingInputs.isNotEmpty) {
+    printError('Missing input files: $missingInputs');
     return null;
   }
   if (!processManager.canRun(genSnapshot)) {
@@ -208,6 +214,17 @@
     genSnapshotCmd.add('--embedder_entry_points_manifest=$ioEntryPoints');
   }
 
+  // iOS symbols used to load snapshot data in the engine.
+  const String kVmSnapshotData = 'kDartVmSnapshotData';
+  const String kIsolateSnapshotData = 'kDartIsolateSnapshotData';
+
+  // iOS snapshot generated files, compiled object files.
+  final String kVmSnapshotDataC = fs.path.join(outputDir.path, '$kVmSnapshotData.c');
+  final String kIsolateSnapshotDataC = fs.path.join(outputDir.path, '$kIsolateSnapshotData.c');
+  final String kVmSnapshotDataO = fs.path.join(outputDir.path, '$kVmSnapshotData.o');
+  final String kIsolateSnapshotDataO = fs.path.join(outputDir.path, '$kIsolateSnapshotData.o');
+  final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');
+
   switch (platform) {
     case TargetPlatform.android_arm:
     case TargetPlatform.android_x64:
@@ -224,9 +241,14 @@
       if (interpreter) {
         genSnapshotCmd.add('--snapshot_kind=core');
         genSnapshotCmd.add(snapshotDartIOS);
+        outputPaths.addAll(<String>[
+          kVmSnapshotDataO,
+          kIsolateSnapshotDataO,
+        ]);
       } else {
         genSnapshotCmd.add('--snapshot_kind=app-aot-assembly');
         genSnapshotCmd.add('--assembly=$assembly');
+        outputPaths.add(assemblyO);
       }
       break;
     case TargetPlatform.darwin_x64:
@@ -245,6 +267,27 @@
 
   genSnapshotCmd.add(mainPath);
 
+  final File checksumFile = fs.file('$dependencies.checksum');
+  final List<File> checksumFiles = <File>[checksumFile, fs.file(dependencies)]
+      ..addAll(inputPaths.map(fs.file))
+      ..addAll(outputPaths.map(fs.file));
+  if (checksumFiles.every((File file) => file.existsSync())) {
+    try {
+      final String json = await checksumFile.readAsString();
+      final Checksum oldChecksum = new Checksum.fromJson(json);
+      final Set<String> snapshotInputPaths = await readDepfile(dependencies);
+      snapshotInputPaths.addAll(outputPaths);
+      final Checksum newChecksum = new Checksum.fromFiles(snapshotInputPaths);
+      if (oldChecksum == newChecksum) {
+        printTrace('Skipping AOT snapshot build. Checksums match.');
+        return outputPath;
+      }
+    } catch (e, s) {
+      // Log exception and continue, this step is a performance improvement only.
+      printTrace('Error during AOT snapshot checksum check: $e\n$s');
+    }
+  }
+
   final RunResult results = await runAsync(genSnapshotCmd);
   if (results.exitCode != 0) {
     printError('Dart snapshot generator failed with exit code ${results.exitCode}');
@@ -261,16 +304,6 @@
   if (platform == TargetPlatform.ios) {
     printStatus('Building App.framework...');
 
-    // These names are known to from the engine.
-    const String kVmSnapshotData = 'kDartVmSnapshotData';
-    const String kIsolateSnapshotData = 'kDartIsolateSnapshotData';
-
-    final String kVmSnapshotDataC = fs.path.join(outputDir.path, '$kVmSnapshotData.c');
-    final String kIsolateSnapshotDataC = fs.path.join(outputDir.path, '$kIsolateSnapshotData.c');
-    final String kVmSnapshotDataO = fs.path.join(outputDir.path, '$kVmSnapshotData.o');
-    final String kIsolateSnapshotDataO = fs.path.join(outputDir.path, '$kIsolateSnapshotData.o');
-    final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');
-
     final List<String> commonBuildOptions = <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
 
     if (interpreter) {
@@ -317,5 +350,16 @@
     await runCheckedAsync(linkCommand);
   }
 
+  // Compute and record checksums.
+  try {
+    final Set<String> snapshotInputPaths = await readDepfile(dependencies);
+    snapshotInputPaths..addAll(outputPaths);
+    final Checksum checksum = new Checksum.fromFiles(snapshotInputPaths);
+    await checksumFile.writeAsString(checksum.toJson());
+  } catch (e, s) {
+    // Log exception and continue, this step is a performance improvement only.
+    printTrace('Error during AOT snapshot checksum output: $e\n$s');
+  }
+
   return outputPath;
 }
diff --git a/packages/flutter_tools/lib/src/flx.dart b/packages/flutter_tools/lib/src/flx.dart
index 996b302..2d9abf2 100644
--- a/packages/flutter_tools/lib/src/flx.dart
+++ b/packages/flutter_tools/lib/src/flx.dart
@@ -4,14 +4,10 @@
 
 import 'dart:async';
 
-import 'package:meta/meta.dart' show required;
-
-import 'artifacts.dart';
 import 'asset.dart';
 import 'base/build.dart';
 import 'base/common.dart';
 import 'base/file_system.dart';
-import 'base/process.dart';
 import 'build_info.dart';
 import 'dart/package_map.dart';
 import 'devfs.dart';
@@ -30,90 +26,6 @@
 const String _kSnapshotKey = 'snapshot_blob.bin';
 const String _kDylibKey = 'libapp.so';
 
-Future<int> _createSnapshot({
-  @required String mainPath,
-  @required String snapshotPath,
-  @required String depfilePath,
-  @required String packages
-}) async {
-  assert(mainPath != null);
-  assert(snapshotPath != null);
-  assert(depfilePath != null);
-  assert(packages != null);
-  final String snapshotterPath = artifacts.getArtifactPath(Artifact.genSnapshot, null, BuildMode.debug);
-  final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData);
-  final String isolateSnapshotData = artifacts.getArtifactPath(Artifact.isolateSnapshotData);
-
-  final List<String> args = <String>[
-    snapshotterPath,
-    '--snapshot_kind=script',
-    '--vm_snapshot_data=$vmSnapshotData',
-    '--isolate_snapshot_data=$isolateSnapshotData',
-    '--packages=$packages',
-    '--script_snapshot=$snapshotPath',
-    '--dependencies=$depfilePath',
-    mainPath,
-  ];
-
-  // Write the depfile path to disk.
-  fs.file(depfilePath).parent.childFile('gen_snapshot.d').writeAsString('$depfilePath: $snapshotterPath\n');
-
-  final File checksumFile = fs.file('$depfilePath.checksums');
-  final File snapshotFile = fs.file(snapshotPath);
-  final File depfile = fs.file(depfilePath);
-  if (snapshotFile.existsSync() && depfile.existsSync() && checksumFile.existsSync()) {
-    try {
-        final String json = await checksumFile.readAsString();
-        final Checksum oldChecksum = new Checksum.fromJson(json);
-        final Set<String> inputPaths = await _readDepfile(depfilePath);
-        inputPaths.add(snapshotPath);
-        final Checksum newChecksum = new Checksum.fromFiles(inputPaths);
-        if (oldChecksum == newChecksum) {
-          printTrace('Skipping snapshot build. Checksums match.');
-          return 0;
-        }
-    } catch (e, s) {
-      // Log exception and continue, this step is a performance improvement only.
-      printTrace('Error during snapshot checksum check: $e\n$s');
-    }
-  }
-
-  // Build the snapshot.
-  final int exitCode = await runCommandAndStreamOutput(args);
-  if (exitCode != 0)
-    return exitCode;
-
-  // Compute and record input file checksums.
-  try {
-    final Set<String> inputPaths = await _readDepfile(depfilePath);
-    inputPaths.add(snapshotPath);
-    final Checksum checksum = new Checksum.fromFiles(inputPaths);
-    await checksumFile.writeAsString(checksum.toJson());
-  } catch (e, s) {
-    // Log exception and continue, this step is a performance improvement only.
-    printTrace('Error during snapshot checksum output: $e\n$s');
-  }
-  return 0;
-}
-
-/// Parses a VM snapshot dependency file.
-///
-/// Snapshot dependency files are a single line mapping the output snapshot to a
-/// space-separated list of input files used to generate that output. e.g,
-///
-/// outfile : file1.dart file2.dart file3.dart
-Future<Set<String>> _readDepfile(String depfilePath) async {
-  // Depfile format:
-  // outfile : file1.dart file2.dart file3.dart
-  final String contents = await fs.file(depfilePath).readAsString();
-  final String dependencies = contents.split(': ')[1];
-  return dependencies
-      .split(' ')
-      .map((String path) => path.trim())
-      .where((String path) => path.isNotEmpty)
-      .toSet();
-}
-
 Future<Null> build({
   String mainPath: defaultMainPath,
   String manifestPath: defaultManifestPath,
@@ -139,11 +51,12 @@
 
     // In a precompiled snapshot, the instruction buffer contains script
     // content equivalents
-    final int result = await _createSnapshot(
+    final Snapshotter snapshotter = new Snapshotter();
+    final int result = await snapshotter.buildScriptSnapshot(
       mainPath: mainPath,
       snapshotPath: snapshotPath,
       depfilePath: depfilePath,
-      packages: packagesPath
+      packagesPath: packagesPath,
     );
     if (result != 0)
       throwToolExit('Failed to run the Flutter compiler. Exit code: $result', exitCode: result);
diff --git a/packages/flutter_tools/test/base/build_test.dart b/packages/flutter_tools/test/base/build_test.dart
index ae1682a..c80a869 100644
--- a/packages/flutter_tools/test/base/build_test.dart
+++ b/packages/flutter_tools/test/base/build_test.dart
@@ -2,13 +2,54 @@
 // 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' show JSON;
+
 import 'package:file/memory.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/build_info.dart';
 import 'package:test/test.dart';
 
 import '../src/context.dart';
 
+class _FakeGenSnapshot implements GenSnapshot {
+  _FakeGenSnapshot({
+    this.succeed: true,
+    this.snapshotPath: 'output.snapshot',
+    this.snapshotContent: '',
+    this.depfilePath: 'output.snapshot.d',
+    this.depfileContent: 'output.snapshot.d : main.dart',
+  });
+
+  final bool succeed;
+  final String snapshotPath;
+  final String snapshotContent;
+  final String depfilePath;
+  final String depfileContent;
+  int _callCount = 0;
+
+  int get callCount => _callCount;
+
+  @override
+  Future<int> run({
+    TargetPlatform targetPlatform,
+    BuildMode buildMode,
+    String packagesPath,
+    String depfilePath,
+    Iterable<String> additionalArgs,
+  }) async {
+    _callCount += 1;
+
+    if (!succeed)
+      return 1;
+    await fs.file(snapshotPath).writeAsString(snapshotContent);
+    await fs.file(depfilePath).writeAsString(depfileContent);
+    return 0;
+  }
+}
+
 void main() {
   group('Checksum', () {
     group('fromFiles', () {
@@ -64,4 +105,185 @@
       });
     });
   });
+
+  group('readDepfile', () {
+    MemoryFileSystem fs;
+
+    setUp(() {
+      fs = new MemoryFileSystem();
+    });
+
+    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: <Type, Generator>{ FileSystem: () => fs });
+
+    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: <Type, Generator>{ FileSystem: () => fs});
+
+    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: <Type, Generator>{ FileSystem: () => fs });
+
+    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: <Type, Generator>{ FileSystem: () => fs });
+  });
+
+  group('Snapshotter', () {
+    _FakeGenSnapshot genSnapshot;
+    MemoryFileSystem fs;
+    Snapshotter snapshotter;
+
+    setUp(() {
+      fs = new MemoryFileSystem();
+      genSnapshot = new _FakeGenSnapshot();
+      snapshotter = new Snapshotter();
+    });
+
+    testUsingContext('builds snapshot and checksums when no checksums are present', () async {
+      await fs.file('main.dart').writeAsString('void main() {}');
+      await fs.file('output.snapshot').create();
+      await fs.file('output.snapshot.d').writeAsString('snapshot : main.dart');
+      await snapshotter.buildScriptSnapshot(
+        mainPath: 'main.dart',
+        snapshotPath: 'output.snapshot',
+        depfilePath: 'output.snapshot.d',
+        packagesPath: '.packages',
+      );
+
+      expect(genSnapshot.callCount, 1);
+
+      final Map<String, dynamic> json = JSON.decode(await fs.file('output.snapshot.d.checksums').readAsString());
+      expect(json, hasLength(2));
+      expect(json['main.dart'], '27f5ebf0f8c559b2af9419d190299a5e');
+      expect(json['output.snapshot'], 'd41d8cd98f00b204e9800998ecf8427e');
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      GenSnapshot: () => genSnapshot,
+    });
+
+    testUsingContext('builds snapshot and checksums when checksums differ', () async {
+      await fs.file('main.dart').writeAsString('void main() {}');
+      await fs.file('output.snapshot').create();
+      await fs.file('output.snapshot.d').writeAsString('output.snapshot : main.dart');
+      await fs.file('output.snapshot.d.checksums').writeAsString(JSON.encode(<String, dynamic>{
+        'main.dart': '27f5ebf0f8c559b2af9419d190299a5e',
+        'output.snapshot': 'deadbeef01234567890abcdef0123456',
+      }));
+      await snapshotter.buildScriptSnapshot(
+        mainPath: 'main.dart',
+        snapshotPath: 'output.snapshot',
+        depfilePath: 'output.snapshot.d',
+        packagesPath: '.packages',
+      );
+
+      expect(genSnapshot.callCount, 1);
+
+      final Map<String, dynamic> json = JSON.decode(await fs.file('output.snapshot.d.checksums').readAsString());
+      expect(json, hasLength(2));
+      expect(json['main.dart'], '27f5ebf0f8c559b2af9419d190299a5e');
+      expect(json['output.snapshot'], 'd41d8cd98f00b204e9800998ecf8427e');
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      GenSnapshot: () => genSnapshot,
+    });
+
+    testUsingContext('builds snapshot and checksums when checksums match but previous snapshot not present', () async {
+      await fs.file('main.dart').writeAsString('void main() {}');
+      await fs.file('output.snapshot.d').writeAsString('output.snapshot : main.dart');
+      await fs.file('output.snapshot.d.checksums').writeAsString(JSON.encode(<String, dynamic>{
+        'main.dart': '27f5ebf0f8c559b2af9419d190299a5e',
+        'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',
+      }));
+      await snapshotter.buildScriptSnapshot(
+        mainPath: 'main.dart',
+        snapshotPath: 'output.snapshot',
+        depfilePath: 'output.snapshot.d',
+        packagesPath: '.packages',
+      );
+
+      expect(genSnapshot.callCount, 1);
+
+      final Map<String, dynamic> json = JSON.decode(await fs.file('output.snapshot.d.checksums').readAsString());
+      expect(json, hasLength(2));
+      expect(json['main.dart'], '27f5ebf0f8c559b2af9419d190299a5e');
+      expect(json['output.snapshot'], 'd41d8cd98f00b204e9800998ecf8427e');
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      GenSnapshot: () => genSnapshot,
+    });
+
+    testUsingContext('builds snapshot and checksums when main entry point changes', () async {
+      final _FakeGenSnapshot genSnapshot = new _FakeGenSnapshot(
+        snapshotPath: 'output.snapshot',
+        depfilePath: 'output.snapshot.d',
+        depfileContent: 'output.snapshot : other.dart',
+      );
+      context.setVariable(GenSnapshot, genSnapshot);
+
+      await fs.file('main.dart').writeAsString('void main() {}');
+      await fs.file('other.dart').writeAsString('void main() { print("Kanpai ima kimi wa jinsei no ookina ookina butai ni tachi"); }');
+      await fs.file('output.snapshot.d').writeAsString('output.snapshot : main.dart');
+      await fs.file('output.snapshot.d.checksums').writeAsString(JSON.encode(<String, dynamic>{
+        'main.dart': '27f5ebf0f8c559b2af9419d190299a5e',
+        'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',
+      }));
+      await snapshotter.buildScriptSnapshot(
+        mainPath: 'other.dart',
+        snapshotPath: 'output.snapshot',
+        depfilePath: 'output.snapshot.d',
+        packagesPath: '.packages',
+      );
+
+      expect(genSnapshot.callCount, 1);
+      final Map<String, dynamic> json = JSON.decode(await fs.file('output.snapshot.d.checksums').readAsString());
+      expect(json, hasLength(2));
+      expect(json['other.dart'], '3238d0ae341339b1731d3c2e195ad177');
+      expect(json['output.snapshot'], 'd41d8cd98f00b204e9800998ecf8427e');
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+
+    testUsingContext('skips snapshot when checksums match and previous snapshot is present', () async {
+      await fs.file('main.dart').writeAsString('void main() {}');
+      await fs.file('output.snapshot').create();
+      await fs.file('output.snapshot.d').writeAsString('output.snapshot : main.dart');
+      await fs.file('output.snapshot.d.checksums').writeAsString(JSON.encode(<String, dynamic>{
+        'main.dart': '27f5ebf0f8c559b2af9419d190299a5e',
+        'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',
+      }));
+      await snapshotter.buildScriptSnapshot(
+        mainPath: 'main.dart',
+        snapshotPath: 'output.snapshot',
+        depfilePath: 'output.snapshot.d',
+        packagesPath: '.packages',
+      );
+
+      expect(genSnapshot.callCount, 0);
+
+      final Map<String, dynamic> json = JSON.decode(await fs.file('output.snapshot.d.checksums').readAsString());
+      expect(json, hasLength(2));
+      expect(json['main.dart'], '27f5ebf0f8c559b2af9419d190299a5e');
+      expect(json['output.snapshot'], 'd41d8cd98f00b204e9800998ecf8427e');
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      GenSnapshot: () => genSnapshot,
+    });
+  });
 }