Make AppContext immutable and race-free (#15984)

This updates AppContext per the recommendations in #15352

Fixes #15352
diff --git a/packages/flutter_tools/bin/fuchsia_asset_builder.dart b/packages/flutter_tools/bin/fuchsia_asset_builder.dart
index a2ac486..bc3b4d5 100644
--- a/packages/flutter_tools/bin/fuchsia_asset_builder.dart
+++ b/packages/flutter_tools/bin/fuchsia_asset_builder.dart
@@ -12,8 +12,10 @@
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/context_runner.dart';
 import 'package:flutter_tools/src/devfs.dart';
+import 'package:flutter_tools/src/disabled_usage.dart';
 import 'package:flutter_tools/src/flx.dart';
 import 'package:flutter_tools/src/globals.dart';
+import 'package:flutter_tools/src/usage.dart';
 
 const String _kOptionPackages = 'packages';
 const String _kOptionWorking = 'working-dir';
@@ -25,8 +27,10 @@
   _kOptionAssetManifestOut,
 ];
 
-Future<Null> main(List<String> args) async {
-  await runInContext(args, run);
+Future<Null> main(List<String> args) {
+  return runInContext<Null>(() => run(args), overrides: <Type, dynamic>{
+    Usage: new DisabledUsage(),
+  });
 }
 
 Future<Null> writeFile(libfs.File outputFile, DevFSContent content) async {
diff --git a/packages/flutter_tools/bin/fuchsia_builder.dart b/packages/flutter_tools/bin/fuchsia_builder.dart
index 15f0b55..a3693d3 100644
--- a/packages/flutter_tools/bin/fuchsia_builder.dart
+++ b/packages/flutter_tools/bin/fuchsia_builder.dart
@@ -12,8 +12,10 @@
 import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/context_runner.dart';
+import 'package:flutter_tools/src/disabled_usage.dart';
 import 'package:flutter_tools/src/flx.dart';
 import 'package:flutter_tools/src/globals.dart';
+import 'package:flutter_tools/src/usage.dart';
 
 const String _kOptionPackages = 'packages';
 const String _kOptionOutput = 'output-file';
@@ -33,8 +35,10 @@
   _kOptionBuildRoot,
 ];
 
-Future<Null> main(List<String> args) async {
-  await runInContext(args, run);
+Future<Null> main(List<String> args) {
+  return runInContext<Null>(() => run(args), overrides: <Type, dynamic>{
+    Usage: new DisabledUsage(),
+  });
 }
 
 Future<Null> run(List<String> args) async {
diff --git a/packages/flutter_tools/bin/fuchsia_tester.dart b/packages/flutter_tools/bin/fuchsia_tester.dart
index b78ff45..3906045 100644
--- a/packages/flutter_tools/bin/fuchsia_tester.dart
+++ b/packages/flutter_tools/bin/fuchsia_tester.dart
@@ -6,24 +6,18 @@
 
 import 'package:args/args.dart';
 import 'package:flutter_tools/src/base/common.dart';
-import 'package:flutter_tools/src/base/config.dart';
-import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
-import 'package:flutter_tools/src/base/logger.dart';
-import 'package:flutter_tools/src/base/os.dart';
-import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/context_runner.dart';
 import 'package:flutter_tools/src/dart/package_map.dart';
 import 'package:flutter_tools/src/disabled_usage.dart';
 import 'package:flutter_tools/src/globals.dart';
 import 'package:flutter_tools/src/test/flutter_platform.dart' as loader;
 import 'package:flutter_tools/src/usage.dart';
-import 'package:process/process.dart';
 import 'package:test/src/executable.dart'
     as test; // ignore: implementation_imports
 
-
 // Note: this was largely inspired by lib/src/commands/test.dart.
 
 const String _kOptionPackages = 'packages';
@@ -35,22 +29,9 @@
   _kOptionTestDirectory,
 ];
 
-Future<Null> main(List<String> args) async {
-  final AppContext executableContext = new AppContext();
-  executableContext.setVariable(Logger, new StdoutLogger());
-  await executableContext.runInZone(() {
-    // Initialize the context with some defaults.
-    // This list must be kept in sync with lib/executable.dart.
-    context.putIfAbsent(Stdio, () => const Stdio());
-    context.putIfAbsent(Platform, () => const LocalPlatform());
-    context.putIfAbsent(FileSystem, () => const LocalFileSystem());
-    context.putIfAbsent(ProcessManager, () => const LocalProcessManager());
-    context.putIfAbsent(Logger, () => new StdoutLogger());
-    context.putIfAbsent(Cache, () => new Cache());
-    context.putIfAbsent(Config, () => new Config());
-    context.putIfAbsent(OperatingSystemUtils, () => new OperatingSystemUtils());
-    context.putIfAbsent(Usage, () => new DisabledUsage());
-    return run(args);
+Future<Null> main(List<String> args) {
+  return runInContext<Null>(() => run(args), overrides: <Type, dynamic>{
+    Usage: new DisabledUsage(),
   });
 }
 
diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart
index d1c0139..85934c6 100644
--- a/packages/flutter_tools/lib/runner.dart
+++ b/packages/flutter_tools/lib/runner.dart
@@ -7,26 +7,18 @@
 import 'package:args/command_runner.dart';
 import 'package:intl/intl_standalone.dart' as intl;
 import 'package:meta/meta.dart';
-import 'package:process/process.dart';
 
-import 'src/artifacts.dart';
 import 'src/base/common.dart';
-import 'src/base/config.dart';
 import 'src/base/context.dart';
 import 'src/base/file_system.dart';
 import 'src/base/io.dart';
 import 'src/base/logger.dart';
-import 'src/base/platform.dart';
 import 'src/base/process.dart';
 import 'src/base/utils.dart';
-import 'src/cache.dart';
+import 'src/context_runner.dart';
 import 'src/crash_reporting.dart';
-import 'src/devfs.dart';
-import 'src/device.dart';
 import 'src/doctor.dart';
 import 'src/globals.dart';
-import 'src/ios/simulators.dart';
-import 'src/run_hot.dart';
 import 'src/runner/flutter_command.dart';
 import 'src/runner/flutter_command_runner.dart';
 import 'src/usage.dart';
@@ -41,7 +33,7 @@
   bool verboseHelp: false,
   bool reportCrashes,
   String flutterVersion,
-}) async {
+}) {
   reportCrashes ??= !isRunningOnBot;
 
   if (muteCommandLogging) {
@@ -54,35 +46,7 @@
   final FlutterCommandRunner runner = new FlutterCommandRunner(verboseHelp: verboseHelp);
   commands.forEach(runner.addCommand);
 
-  // Construct a context.
-  final AppContext _executableContext = new AppContext();
-
-  // Make the context current.
-  return await _executableContext.runInZone(() async {
-    // Initialize the context with some defaults.
-    // NOTE: Similar lists also exist in `bin/fuchsia_builder.dart` and
-    // `test/src/context.dart`. If you update this list of defaults, look
-    // in those locations as well to see if you need a similar update there.
-
-    // Seed these context entries first since others depend on them
-    context.putIfAbsent(Stdio, () => const Stdio());
-    context.putIfAbsent(Platform, () => const LocalPlatform());
-    context.putIfAbsent(FileSystem, () => const LocalFileSystem());
-    context.putIfAbsent(ProcessManager, () => const LocalProcessManager());
-    context.putIfAbsent(Logger, () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger());
-    context.putIfAbsent(Config, () => new Config());
-
-    // Order-independent context entries
-    context.putIfAbsent(BotDetector, () => const BotDetector());
-    context.putIfAbsent(DeviceManager, () => new DeviceManager());
-    context.putIfAbsent(DevFSConfig, () => new DevFSConfig());
-    context.putIfAbsent(Doctor, () => new Doctor());
-    context.putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig());
-    context.putIfAbsent(Cache, () => new Cache());
-    context.putIfAbsent(Artifacts, () => new CachedArtifacts());
-    context.putIfAbsent(IOSSimulatorUtils, () => new IOSSimulatorUtils());
-    context.putIfAbsent(SimControl, () => new SimControl());
-
+  return runInContext<int>(() async {
     // Initialize the system locale.
     await intl.findSystemLocale();
 
@@ -104,6 +68,7 @@
 /// Writes a line to STDERR.
 ///
 /// Overwrite this in tests to avoid spurious test output.
+// TODO(tvolkert): Remove this in favor of context[Stdio]
 @visibleForTesting
 WriteCallback writelnStderr = stderr.writeln;
 
@@ -230,11 +195,13 @@
 Future<String> _doctorText() async {
   try {
     final BufferLogger logger = new BufferLogger();
-    final AppContext appContext = new AppContext();
 
-    appContext.setVariable(Logger, logger);
-
-    await appContext.runInZone(() => doctor.diagnose(verbose: true));
+    await context.run<Future<bool>>(
+      body: () => doctor.diagnose(verbose: true),
+      overrides: <Type, Generator>{
+        Logger: () => logger,
+      },
+    );
 
     return logger.statusText;
   } catch (error, trace) {
diff --git a/packages/flutter_tools/lib/src/android/android_studio.dart b/packages/flutter_tools/lib/src/android/android_studio.dart
index 3877aac..45897b1 100644
--- a/packages/flutter_tools/lib/src/android/android_studio.dart
+++ b/packages/flutter_tools/lib/src/android/android_studio.dart
@@ -12,8 +12,7 @@
 import '../globals.dart';
 import '../ios/plist_utils.dart';
 
-AndroidStudio get androidStudio =>
-    context.putIfAbsent(AndroidStudio, AndroidStudio.latestValid);
+AndroidStudio get androidStudio => context[AndroidStudio];
 
 // Android Studio layout:
 
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index 7ce5ba5..4b3649d 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -17,7 +17,7 @@
 import '../globals.dart';
 import 'android_sdk.dart';
 
-AndroidWorkflow get androidWorkflow => context.putIfAbsent(AndroidWorkflow, () => new AndroidWorkflow());
+AndroidWorkflow get androidWorkflow => context[AndroidWorkflow];
 
 enum LicensesAccepted {
   none,
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index e1c8b01..f539a5a 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -84,8 +84,8 @@
 abstract class Artifacts {
   static Artifacts get instance => context[Artifacts];
 
-  static void useLocalEngine(String engineSrcPath, EngineBuildPaths engineBuildPaths) {
-    context.setVariable(Artifacts, new LocalEngineArtifacts(engineSrcPath, engineBuildPaths.targetEngine, engineBuildPaths.hostEngine));
+  static LocalEngineArtifacts getLocalEngine(String engineSrcPath, EngineBuildPaths engineBuildPaths) {
+    return new LocalEngineArtifacts(engineSrcPath, engineBuildPaths.targetEngine, engineBuildPaths.hostEngine);
   }
 
   // Returns the requested [artifact] for the [platform] and [mode] combination.
diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index 0915d17..44194d6 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -21,9 +21,9 @@
 /// Injected factory class for spawning [AssetBundle] instances.
 abstract class AssetBundleFactory {
   /// The singleton instance, pulled from the [AppContext].
-  static AssetBundleFactory get instance => context == null
-      ? _kManifestFactory
-      : context.putIfAbsent(AssetBundleFactory, () => _kManifestFactory);
+  static AssetBundleFactory get instance => context[AssetBundleFactory];
+
+  static AssetBundleFactory get defaultInstance => _kManifestFactory;
 
   /// Creates a new [AssetBundle].
   AssetBundle createBundle();
diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index 2f70774..57ab3b9 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -17,7 +17,7 @@
 import 'file_system.dart';
 import 'process.dart';
 
-GenSnapshot get genSnapshot => context.putIfAbsent(GenSnapshot, () => const GenSnapshot());
+GenSnapshot get genSnapshot => context[GenSnapshot];
 
 /// A snapshot build configuration.
 class SnapshotType {
diff --git a/packages/flutter_tools/lib/src/base/context.dart b/packages/flutter_tools/lib/src/base/context.dart
index 1b488e8..459adc4 100644
--- a/packages/flutter_tools/lib/src/base/context.dart
+++ b/packages/flutter_tools/lib/src/base/context.dart
@@ -3,79 +3,182 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:collection';
 
-typedef void ErrorHandler(dynamic error, StackTrace stackTrace);
+import 'package:meta/meta.dart';
 
-/// A singleton for application functionality. This singleton can be different
-/// on a per-Zone basis.
-AppContext get context => Zone.current['context'];
+/// Generates an [AppContext] value.
+///
+/// Generators are allowed to return `null`, in which case the context will
+/// store the `null` value as the value for that type.
+typedef dynamic Generator();
 
+/// An exception thrown by [AppContext] when you try to get a [Type] value from
+/// the context, and the instantiation of the value results in a dependency
+/// cycle.
+class ContextDependencyCycleException implements Exception {
+  ContextDependencyCycleException._(this.cycle);
+
+  /// The dependency cycle (last item depends on first item).
+  final List<Type> cycle;
+
+  @override
+  String toString() => 'Dependency cycle detected: ${cycle.join(' -> ')}';
+}
+
+/// The current [AppContext], as determined by the [Zone] hierarchy.
+///
+/// This will be the first context found as we scan up the zone hierarchy, or
+/// the "root" context if a context cannot be found in the hierarchy. The root
+/// context will not have any values associated with it.
+///
+/// This is guaranteed to never return `null`.
+AppContext get context => Zone.current[_Key.key] ?? AppContext._root;
+
+/// A lookup table (mapping types to values) and an implied scope, in which
+/// code is run.
+///
+/// [AppContext] is used to define a singleton injection context for code that
+/// is run within it. Each time you call [run], a child context (and a new
+/// scope) is created.
+///
+/// Child contexts are created and run using zones. To read more about how
+/// zones work, see https://www.dartlang.org/articles/libraries/zones.
 class AppContext {
-  final Map<Type, dynamic> _instances = <Type, dynamic>{};
-  Zone _zone;
+  AppContext._(
+    this._parent,
+    this.name, [
+    this._overrides = const <Type, Generator>{},
+    this._fallbacks = const <Type, Generator>{},
+  ]);
 
-  AppContext() : _zone = Zone.current;
+  final String name;
+  final AppContext _parent;
+  final Map<Type, Generator> _overrides;
+  final Map<Type, Generator> _fallbacks;
+  final Map<Type, dynamic> _values = <Type, dynamic>{};
 
-  bool isSet(Type type) {
-    if (_instances.containsKey(type))
-      return true;
+  List<Type> _reentrantChecks;
 
-    final AppContext parent = _calcParent(_zone);
-    return parent != null ? parent.isSet(type) : false;
-  }
+  /// Bootstrap context.
+  static final AppContext _root = new AppContext._(null, 'ROOT');
 
-  dynamic getVariable(Type type) {
-    if (_instances.containsKey(type))
-      return _instances[type];
+  dynamic _boxNull(dynamic value) => value ?? _BoxedNull.instance;
 
-    final AppContext parent = _calcParent(_zone);
-    return parent?.getVariable(type);
-  }
+  dynamic _unboxNull(dynamic value) => value == _BoxedNull.instance ? null : value;
 
-  void setVariable(Type type, dynamic instance) {
-    _instances[type] = instance;
-  }
-
-  dynamic operator[](Type type) => getVariable(type);
-
-  dynamic putIfAbsent(Type type, dynamic ifAbsent()) {
-    dynamic value = getVariable(type);
-    if (value != null) {
-      return value;
-    }
-    value = ifAbsent();
-    setVariable(type, value);
-    return value;
-  }
-
-  AppContext _calcParent(Zone zone) {
-    final Zone parentZone = zone.parent;
-    if (parentZone == null)
+  /// Returns the generated value for [type] if such a generator exists.
+  ///
+  /// If [generators] does not contain a mapping for the specified [type], this
+  /// returns `null`.
+  ///
+  /// If a generator existed and generated a `null` value, this will return a
+  /// boxed value indicating null.
+  ///
+  /// If a value for [type] has already been generated by this context, the
+  /// existing value will be returned, and the generator will not be invoked.
+  ///
+  /// If the generator ends up triggering a reentrant call, it signals a
+  /// dependency cycle, and a [ContextDependencyCycleException] will be thrown.
+  dynamic _generateIfNecessary(Type type, Map<Type, Generator> generators) {
+    if (!generators.containsKey(type))
       return null;
 
-    final AppContext parentContext = parentZone['context'];
-    return parentContext == this
-        ? _calcParent(parentZone)
-        : parentContext;
+    return _values.putIfAbsent(type, () {
+      _reentrantChecks ??= <Type>[];
+
+      final int index = _reentrantChecks.indexOf(type);
+      if (index >= 0) {
+        // We're already in the process of trying to generate this type.
+        throw new ContextDependencyCycleException._(
+            new UnmodifiableListView<Type>(_reentrantChecks.sublist(index)));
+      }
+
+      _reentrantChecks.add(type);
+      try {
+        return _boxNull(generators[type]());
+      } finally {
+        _reentrantChecks.removeLast();
+        if (_reentrantChecks.isEmpty)
+          _reentrantChecks = null;
+      }
+    });
   }
 
-  Future<dynamic> runInZone(dynamic method(), {
-    ZoneBinaryCallback<dynamic, dynamic, StackTrace> onError
+  /// Gets the value associated with the specified [type], or `null` if no
+  /// such value has been associated.
+  dynamic operator [](Type type) {
+    dynamic value = _generateIfNecessary(type, _overrides);
+    if (value == null && _parent != null)
+      value = _parent[type];
+    return _unboxNull(value ?? _generateIfNecessary(type, _fallbacks));
+  }
+
+  /// Runs [body] in a child context and returns the value returned by [body].
+  ///
+  /// If [overrides] is specified, the child context will return corresponding
+  /// values when consulted via [operator[]].
+  ///
+  /// If [fallbacks] is specified, the child context will return corresponding
+  /// values when consulted via [operator[]] only if its parent context didn't
+  /// return such a value.
+  ///
+  /// If [name] is specified, the child context will be assigned the given
+  /// name. This is useful for debugging purposes and is analogous to naming a
+  /// thread in Java.
+  V run<V>({
+    @required V body(),
+    String name,
+    Map<Type, Generator> overrides,
+    Map<Type, Generator> fallbacks,
   }) {
-    return runZoned(
-      () => _run(method),
-      zoneValues: <String, dynamic>{ 'context': this },
-      onError: onError
+    final AppContext child = new AppContext._(
+      this,
+      name,
+      new Map<Type, Generator>.unmodifiable(overrides ?? const <Type, Generator>{}),
+      new Map<Type, Generator>.unmodifiable(fallbacks ?? const <Type, Generator>{}),
+    );
+    return runZoned<V>(
+      body,
+      zoneValues: <_Key, AppContext>{_Key.key: child},
     );
   }
 
-  Future<dynamic> _run(dynamic method()) async {
-    final Zone previousZone = _zone;
-    try {
-      _zone = Zone.current;
-      return await method();
-    } finally {
-      _zone = previousZone;
+  @override
+  String toString() {
+    final StringBuffer buf = new StringBuffer();
+    String indent = '';
+    AppContext ctx = this;
+    while (ctx != null) {
+      buf.write('AppContext');
+      if (ctx.name != null)
+        buf.write('[${ctx.name}]');
+      if (ctx._overrides.isNotEmpty)
+        buf.write('\n$indent  overrides: [${ctx._overrides.keys.join(', ')}]');
+      if (ctx._fallbacks.isNotEmpty)
+        buf.write('\n$indent  fallbacks: [${ctx._fallbacks.keys.join(', ')}]');
+      if (ctx._parent != null)
+        buf.write('\n$indent  parent: ');
+      ctx = ctx._parent;
+      indent += '  ';
     }
+    return buf.toString();
   }
 }
+
+/// Private key used to store the [AppContext] in the [Zone].
+class _Key {
+  const _Key();
+
+  static const _Key key = const _Key();
+
+  @override
+  String toString() => 'context';
+}
+
+/// Private object that denotes a generated `null` value.
+class _BoxedNull {
+  const _BoxedNull();
+
+  static const _BoxedNull instance = const _BoxedNull();
+}
diff --git a/packages/flutter_tools/lib/src/base/file_system.dart b/packages/flutter_tools/lib/src/base/file_system.dart
index 736fff4..74fa9dc 100644
--- a/packages/flutter_tools/lib/src/base/file_system.dart
+++ b/packages/flutter_tools/lib/src/base/file_system.dart
@@ -22,19 +22,15 @@
 ///
 /// By default it uses local disk-based implementation. Override this in tests
 /// with [MemoryFileSystem].
-FileSystem get fs => context == null ? _kLocalFs : context[FileSystem];
+FileSystem get fs => context[FileSystem] ?? _kLocalFs;
 
-/// Enables recording of file system activity to the specified base recording
-/// [location].
-///
-/// This sets the [active file system](fs) to one that records all invocation
-/// activity before delegating to a [LocalFileSystem].
+/// Gets a [FileSystem] that will record file system activity to the specified
+/// base recording [location].
 ///
 /// Activity will be recorded in a subdirectory of [location] named `"file"`.
 /// It is permissible for [location] to represent an existing non-empty
 /// directory as long as there is no collision with the `"file"` subdirectory.
-void enableRecordingFileSystem(String location) {
-  final FileSystem originalFileSystem = fs;
+RecordingFileSystem getRecordingFileSystem(String location) {
   final Directory dir = getRecordingSink(location, _kRecordingType);
   final RecordingFileSystem fileSystem = new RecordingFileSystem(
       delegate: _kLocalFs, destination: dir);
@@ -42,22 +38,19 @@
     await fileSystem.recording.flush(
       pendingResultTimeout: const Duration(seconds: 5),
     );
-    context.setVariable(FileSystem, originalFileSystem);
   }, ShutdownStage.SERIALIZE_RECORDING);
-  context.setVariable(FileSystem, fileSystem);
+  return fileSystem;
 }
 
-/// Enables file system replay mode.
-///
-/// This sets the [active file system](fs) to one that replays invocation
-/// activity from a previously recorded set of invocations.
+/// Gets a [FileSystem] that replays invocation activity from a previously
+/// recorded set of invocations.
 ///
 /// [location] must represent a directory to which file system activity has
 /// been recorded (i.e. the result of having been previously passed to
-/// [enableRecordingFileSystem]), or a [ToolExit] will be thrown.
-void enableReplayFileSystem(String location) {
+/// [getRecordingFileSystem]), or a [ToolExit] will be thrown.
+ReplayFileSystem getReplayFileSystem(String location) {
   final Directory dir = getReplaySource(location, _kRecordingType);
-  context.setVariable(FileSystem, new ReplayFileSystem(recording: dir));
+  return new ReplayFileSystem(recording: dir);
 }
 
 /// Create the ancestor directories of a file path if they do not already exist.
diff --git a/packages/flutter_tools/lib/src/base/flags.dart b/packages/flutter_tools/lib/src/base/flags.dart
index 429b03c..81442a9 100644
--- a/packages/flutter_tools/lib/src/base/flags.dart
+++ b/packages/flutter_tools/lib/src/base/flags.dart
@@ -8,7 +8,7 @@
 
 /// command-line flags and options that were specified during the invocation of
 /// the Flutter tool.
-Flags get flags => context?.getVariable(Flags) ?? const _EmptyFlags();
+Flags get flags => context[Flags];
 
 /// Encapsulation of the command-line flags and options that were specified
 /// during the invocation of the Flutter tool.
@@ -52,8 +52,8 @@
   }
 }
 
-class _EmptyFlags implements Flags {
-  const _EmptyFlags();
+class EmptyFlags implements Flags {
+  const EmptyFlags();
 
   @override
   ArgResults get _globalResults => null;
diff --git a/packages/flutter_tools/lib/src/base/io.dart b/packages/flutter_tools/lib/src/base/io.dart
index 326c487..5500d2a 100644
--- a/packages/flutter_tools/lib/src/base/io.dart
+++ b/packages/flutter_tools/lib/src/base/io.dart
@@ -155,23 +155,8 @@
   io.IOSink get stderr => io.stderr;
 }
 
-io.IOSink get stderr {
-  if (context == null)
-    return io.stderr;
-  final Stdio contextStreams = context[Stdio];
-  return contextStreams.stderr;
-}
+io.IOSink get stderr => context[Stdio].stderr;
 
-Stream<List<int>> get stdin {
-  if (context == null)
-    return io.stdin;
-  final Stdio contextStreams = context[Stdio];
-  return contextStreams.stdin;
-}
+Stream<List<int>> get stdin => context[Stdio].stdin;
 
-io.IOSink get stdout {
-  if (context == null)
-    return io.stdout;
-  final Stdio contextStreams = context[Stdio];
-  return contextStreams.stdout;
-}
+io.IOSink get stdout => context[Stdio].stdout;
diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart
index 0d7bb12..ea844cb 100644
--- a/packages/flutter_tools/lib/src/base/os.dart
+++ b/packages/flutter_tools/lib/src/base/os.dart
@@ -11,7 +11,7 @@
 import 'process_manager.dart';
 
 /// Returns [OperatingSystemUtils] active in the current app context (i.e. zone).
-OperatingSystemUtils get os => context.putIfAbsent(OperatingSystemUtils, () => new OperatingSystemUtils());
+OperatingSystemUtils get os => context[OperatingSystemUtils];
 
 abstract class OperatingSystemUtils {
   factory OperatingSystemUtils() {
diff --git a/packages/flutter_tools/lib/src/base/platform.dart b/packages/flutter_tools/lib/src/base/platform.dart
index dd96240..c781499 100644
--- a/packages/flutter_tools/lib/src/base/platform.dart
+++ b/packages/flutter_tools/lib/src/base/platform.dart
@@ -14,26 +14,29 @@
 const Platform _kLocalPlatform = const LocalPlatform();
 const String _kRecordingType = 'platform';
 
-Platform get platform => context == null ? _kLocalPlatform : context[Platform];
+Platform get platform => context[Platform] ?? _kLocalPlatform;
 
-/// Enables serialization of the current [platform] to the specified base
-/// recording [location].
+/// Serializes the current [platform] to the specified base recording
+/// [location].
 ///
 /// Platform metadata will be recorded in a subdirectory of [location] named
 /// `"platform"`. It is permissible for [location] to represent an existing
 /// non-empty directory as long as there is no collision with the `"platform"`
 /// subdirectory.
-Future<Null> enableRecordingPlatform(String location) async {
+///
+/// Returns the existing platform.
+Future<Platform> getRecordingPlatform(String location) async {
   final Directory dir = getRecordingSink(location, _kRecordingType);
   final File file = _getPlatformManifest(dir);
   await file.writeAsString(platform.toJson(), flush: true);
+  return platform;
 }
 
-Future<Null> enableReplayPlatform(String location) async {
+Future<FakePlatform> getReplayPlatform(String location) async {
   final Directory dir = getReplaySource(location, _kRecordingType);
   final File file = _getPlatformManifest(dir);
   final String json = await file.readAsString();
-  context.setVariable(Platform, new FakePlatform.fromJson(json));
+  return new FakePlatform.fromJson(json);
 }
 
 File _getPlatformManifest(Directory dir) {
diff --git a/packages/flutter_tools/lib/src/base/port_scanner.dart b/packages/flutter_tools/lib/src/base/port_scanner.dart
index e1c0a45..a3e7167 100644
--- a/packages/flutter_tools/lib/src/base/port_scanner.dart
+++ b/packages/flutter_tools/lib/src/base/port_scanner.dart
@@ -7,14 +7,9 @@
 import 'context.dart';
 import 'io.dart';
 
-const PortScanner _kLocalPortScanner = const HostPortScanner();
 const int _kMaxSearchIterations = 20;
 
-PortScanner get portScanner {
-  return context == null
-      ? _kLocalPortScanner
-      : context.putIfAbsent(PortScanner, () => _kLocalPortScanner);
-}
+PortScanner get portScanner => context[PortScanner];
 
 abstract class PortScanner {
   const PortScanner();
diff --git a/packages/flutter_tools/lib/src/base/process_manager.dart b/packages/flutter_tools/lib/src/base/process_manager.dart
index bb39578..18b5703 100644
--- a/packages/flutter_tools/lib/src/base/process_manager.dart
+++ b/packages/flutter_tools/lib/src/base/process_manager.dart
@@ -16,43 +16,32 @@
 const ProcessManager _kLocalProcessManager = const LocalProcessManager();
 
 /// The active process manager.
-ProcessManager get processManager {
-  return context == null
-      ? _kLocalProcessManager
-      : context[ProcessManager];
-}
+ProcessManager get processManager => context[ProcessManager] ?? _kLocalProcessManager;
 
-/// Enables recording of process invocation activity to the specified base
-/// recording [location].
-///
-/// This sets the [active process manager](processManager) to one that records
-/// all process activity before delegating to a [LocalProcessManager].
+/// Gets a [ProcessManager] that will record process invocation activity to the
+/// specified base recording [location].
 ///
 /// Activity will be recorded in a subdirectory of [location] named `"process"`.
 /// It is permissible for [location] to represent an existing non-empty
 /// directory as long as there is no collision with the `"process"`
 /// subdirectory.
-void enableRecordingProcessManager(String location) {
-  final ProcessManager originalProcessManager = processManager;
+RecordingProcessManager getRecordingProcessManager(String location) {
   final Directory dir = getRecordingSink(location, _kRecordingType);
   const ProcessManager delegate = const LocalProcessManager();
   final RecordingProcessManager manager = new RecordingProcessManager(delegate, dir);
   addShutdownHook(() async {
     await manager.flush(finishRunningProcesses: true);
-    context.setVariable(ProcessManager, originalProcessManager);
   }, ShutdownStage.SERIALIZE_RECORDING);
-  context.setVariable(ProcessManager, manager);
+  return manager;
 }
 
-/// Enables process invocation replay mode.
-///
-/// This sets the [active process manager](processManager) to one that replays
-/// process activity from a previously recorded set of invocations.
+/// Gets a [ProcessManager] that replays process activity from a previously
+/// recorded set of invocations.
 ///
 /// [location] must represent a directory to which process activity has been
 /// recorded (i.e. the result of having been previously passed to
-/// [enableRecordingProcessManager]), or a [ToolExit] will be thrown.
-Future<Null> enableReplayProcessManager(String location) async {
+/// [getRecordingProcessManager]), or a [ToolExit] will be thrown.
+Future<ReplayProcessManager> getReplayProcessManager(String location) async {
   final Directory dir = getReplaySource(location, _kRecordingType);
 
   ProcessManager manager;
@@ -69,5 +58,5 @@
     throwToolExit('Invalid replay-from: $error');
   }
 
-  context.setVariable(ProcessManager, manager);
+  return manager;
 }
diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart
index f980e19..735ab94 100644
--- a/packages/flutter_tools/lib/src/base/utils.dart
+++ b/packages/flutter_tools/lib/src/base/utils.dart
@@ -45,7 +45,7 @@
 }
 
 bool get isRunningOnBot {
-  final BotDetector botDetector = context?.getVariable(BotDetector) ?? _kBotDetector;
+  final BotDetector botDetector = context[BotDetector] ?? _kBotDetector;
   return botDetector.isRunningOnBot;
 }
 
@@ -231,7 +231,7 @@
       value.toRadixString(16).padLeft(count, '0');
 }
 
-Clock get clock => context.putIfAbsent(Clock, () => const Clock());
+Clock get clock => context[Clock];
 
 typedef Future<Null> AsyncCallback();
 
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index cad13b3..4cd9bb2 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -50,21 +50,24 @@
   Future<Null> runCommand() {
     printStatus('Starting device daemon...');
 
-    final AppContext appContext = new AppContext();
     final NotifyingLogger notifyingLogger = new NotifyingLogger();
-    appContext.setVariable(Logger, notifyingLogger);
 
     Cache.releaseLockEarly();
 
-    return appContext.runInZone(() async {
-      final Daemon daemon = new Daemon(
-          stdinCommandStream, stdoutCommandResponse,
-          daemonCommand: this, notifyingLogger: notifyingLogger);
+    return context.run<Future<Null>>(
+      body: () async {
+        final Daemon daemon = new Daemon(
+            stdinCommandStream, stdoutCommandResponse,
+            daemonCommand: this, notifyingLogger: notifyingLogger);
 
-      final int code = await daemon.onExit;
-      if (code != 0)
-        throwToolExit('Daemon exited with non-zero exit code: $code', exitCode: code);
-    });
+        final int code = await daemon.onExit;
+        if (code != 0)
+          throwToolExit('Daemon exited with non-zero exit code: $code', exitCode: code);
+      },
+      overrides: <Type, Generator>{
+        Logger: () => notifyingLogger,
+      },
+    );
   }
 }
 
@@ -810,10 +813,12 @@
   dynamic _runInZone(AppDomain domain, dynamic method()) {
     _logger ??= new _AppRunLogger(domain, this, parent: logToStdout ? logger : null);
 
-    final AppContext appContext = new AppContext();
-    appContext.setVariable(Logger, _logger);
-    appContext.setVariable(Stdio, const Stdio());
-    return appContext.runInZone(method);
+    return context.run<dynamic>(
+      body: method,
+      overrides: <Type, Generator>{
+        Logger: () => _logger,
+      },
+    );
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 5d8bc67..9abaec3 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -4,38 +4,73 @@
 
 import 'dart:async';
 
-import 'package:process/process.dart';
+import 'package:quiver/time.dart';
 
+import 'android/android_sdk.dart';
+import 'android/android_studio.dart';
+import 'android/android_workflow.dart';
+import 'artifacts.dart';
+import 'asset.dart';
+import 'base/build.dart';
 import 'base/config.dart';
 import 'base/context.dart';
-import 'base/file_system.dart';
+import 'base/flags.dart';
 import 'base/io.dart';
 import 'base/logger.dart';
 import 'base/os.dart';
 import 'base/platform.dart';
+import 'base/port_scanner.dart';
 import 'base/utils.dart';
 import 'cache.dart';
-import 'disabled_usage.dart';
+import 'devfs.dart';
+import 'device.dart';
+import 'doctor.dart';
+import 'ios/cocoapods.dart';
+import 'ios/ios_workflow.dart';
+import 'ios/mac.dart';
+import 'ios/simulators.dart';
+import 'ios/xcodeproj.dart';
+import 'run_hot.dart';
 import 'usage.dart';
+import 'version.dart';
 
-typedef Future<Null> Runner(List<String> args);
-
-Future<Null> runInContext(List<String> args, Runner runner) {
-  final AppContext executableContext = new AppContext();
-  executableContext.setVariable(Logger, new StdoutLogger());
-  return executableContext.runInZone(() {
-    // Initialize the context with some defaults.
-    // This list must be kept in sync with lib/executable.dart.
-    context.putIfAbsent(BotDetector, () => const BotDetector());
-    context.putIfAbsent(Stdio, () => const Stdio());
-    context.putIfAbsent(Platform, () => const LocalPlatform());
-    context.putIfAbsent(FileSystem, () => const LocalFileSystem());
-    context.putIfAbsent(ProcessManager, () => const LocalProcessManager());
-    context.putIfAbsent(Logger, () => new StdoutLogger());
-    context.putIfAbsent(Cache, () => new Cache());
-    context.putIfAbsent(Config, () => new Config());
-    context.putIfAbsent(OperatingSystemUtils, () => new OperatingSystemUtils());
-    context.putIfAbsent(Usage, () => new DisabledUsage());
-    return runner(args);
-  });
+Future<T> runInContext<T>(
+  FutureOr<T> runner(), {
+  Map<Type, dynamic> overrides,
+}) async {
+  return await context.run<Future<T>>(
+    name: 'global fallbacks',
+    body: () async => await runner(),
+    overrides: overrides,
+    fallbacks: <Type, Generator>{
+      AndroidSdk: AndroidSdk.locateAndroidSdk,
+      AndroidStudio: AndroidStudio.latestValid,
+      AndroidWorkflow: () => new AndroidWorkflow(),
+      Artifacts: () => new CachedArtifacts(),
+      AssetBundleFactory: () => AssetBundleFactory.defaultInstance,
+      BotDetector: () => const BotDetector(),
+      Cache: () => new Cache(),
+      Clock: () => const Clock(),
+      CocoaPods: () => const CocoaPods(),
+      Config: () => new Config(),
+      DevFSConfig: () => new DevFSConfig(),
+      DeviceManager: () => new DeviceManager(),
+      Doctor: () => new Doctor(),
+      Flags: () => const EmptyFlags(),
+      FlutterVersion: () => new FlutterVersion(const Clock()),
+      GenSnapshot: () => const GenSnapshot(),
+      HotRunnerConfig: () => new HotRunnerConfig(),
+      IMobileDevice: () => const IMobileDevice(),
+      IOSSimulatorUtils: () => new IOSSimulatorUtils(),
+      IOSWorkflow: () => const IOSWorkflow(),
+      Logger: () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger(),
+      OperatingSystemUtils: () => new OperatingSystemUtils(),
+      PortScanner: () => const HostPortScanner(),
+      SimControl: () => new SimControl(),
+      Stdio: () => const Stdio(),
+      Usage: () => new Usage(),
+      Xcode: () => new Xcode(),
+      XcodeProjectInterpreter: () => new XcodeProjectInterpreter(),
+    },
+  );
 }
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index 2e1ca55..1a70543 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -197,7 +197,7 @@
 }
 
 abstract class DoctorValidator {
-  DoctorValidator(this.title);
+  const DoctorValidator(this.title);
 
   final String title;
 
diff --git a/packages/flutter_tools/lib/src/ios/cocoapods.dart b/packages/flutter_tools/lib/src/ios/cocoapods.dart
index e020c17..1307f00 100644
--- a/packages/flutter_tools/lib/src/ios/cocoapods.dart
+++ b/packages/flutter_tools/lib/src/ios/cocoapods.dart
@@ -31,7 +31,7 @@
   brew upgrade cocoapods
   pod setup''';
 
-CocoaPods get cocoaPods => context.putIfAbsent(CocoaPods, () => const CocoaPods());
+CocoaPods get cocoaPods => context[CocoaPods];
 
 class CocoaPods {
   const CocoaPods();
diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
index d561ff0..c44ce74 100644
--- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -13,10 +13,10 @@
 import 'cocoapods.dart';
 import 'mac.dart';
 
-IOSWorkflow get iosWorkflow => context.putIfAbsent(IOSWorkflow, () => new IOSWorkflow());
+IOSWorkflow get iosWorkflow => context[IOSWorkflow];
 
 class IOSWorkflow extends DoctorValidator implements Workflow {
-  IOSWorkflow() : super('iOS toolchain - develop for iOS devices');
+  const IOSWorkflow() : super('iOS toolchain - develop for iOS devices');
 
   @override
   bool get appliesToHostPlatform => platform.isMacOS;
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 89497eb..7356716 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -35,9 +35,9 @@
 // Homebrew.
 const PythonModule kPythonSix = const PythonModule('six');
 
-IMobileDevice get iMobileDevice => context.putIfAbsent(IMobileDevice, () => const IMobileDevice());
+IMobileDevice get iMobileDevice => context[IMobileDevice];
 
-Xcode get xcode => context.putIfAbsent(Xcode, () => new Xcode());
+Xcode get xcode => context[Xcode];
 
 class PythonModule {
   const PythonModule(this.name);
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index e35d6aa..0144a2e 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -85,10 +85,7 @@
   localsFile.writeAsStringSync(localsBuffer.toString());
 }
 
-XcodeProjectInterpreter get xcodeProjectInterpreter => context.putIfAbsent(
-  XcodeProjectInterpreter,
-  () => new XcodeProjectInterpreter(),
-);
+XcodeProjectInterpreter get xcodeProjectInterpreter => context[XcodeProjectInterpreter];
 
 /// Interpreter of Xcode projects.
 class XcodeProjectInterpreter {
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index c65e9a7..bd9ca25 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -222,46 +222,49 @@
   /// and [runCommand] to execute the command
   /// so that this method can record and report the overall time to analytics.
   @override
-  Future<Null> run() async {
+  Future<Null> run() {
     final DateTime startTime = clock.now();
 
-    context.setVariable(FlutterCommand, this);
+    return context.run<Future<Null>>(
+      name: 'command',
+      overrides: <Type, Generator>{FlutterCommand: () => this},
+      body: () async {
+        if (flutterUsage.isFirstRun)
+          flutterUsage.printWelcome();
 
-    if (flutterUsage.isFirstRun)
-      flutterUsage.printWelcome();
+        FlutterCommandResult commandResult;
+        try {
+          commandResult = await verifyThenRunCommand();
+        } on ToolExit {
+          commandResult = const FlutterCommandResult(ExitStatus.fail);
+          rethrow;
+        } finally {
+          final DateTime endTime = clock.now();
+          printTrace('"flutter $name" took ${getElapsedAsMilliseconds(endTime.difference(startTime))}.');
+          if (usagePath != null) {
+            final List<String> labels = <String>[];
+            if (commandResult?.exitStatus != null)
+              labels.add(getEnumName(commandResult.exitStatus));
+            if (commandResult?.timingLabelParts?.isNotEmpty ?? false)
+              labels.addAll(commandResult.timingLabelParts);
 
-    FlutterCommandResult commandResult;
-    try {
-      commandResult = await verifyThenRunCommand();
-    } on ToolExit {
-      commandResult = const FlutterCommandResult(ExitStatus.fail);
-      rethrow;
-    } finally {
-      final DateTime endTime = clock.now();
-      printTrace('"flutter $name" took ${getElapsedAsMilliseconds(endTime.difference(startTime))}.');
-      if (usagePath != null) {
-        final List<String> labels = <String>[];
-        if (commandResult?.exitStatus != null)
-          labels.add(getEnumName(commandResult.exitStatus));
-        if (commandResult?.timingLabelParts?.isNotEmpty ?? false)
-          labels.addAll(commandResult.timingLabelParts);
-
-        final String label = labels
-            .where((String label) => !isBlank(label))
-            .join('-');
-        flutterUsage.sendTiming(
-          'flutter',
-          name,
-          // If the command provides its own end time, use it. Otherwise report
-          // the duration of the entire execution.
-          (commandResult?.endTimeOverride ?? endTime).difference(startTime),
-          // Report in the form of `success-[parameter1-parameter2]`, all of which
-          // can be null if the command doesn't provide a FlutterCommandResult.
-          label: label == '' ? null : label,
-        );
-      }
-    }
-
+            final String label = labels
+                .where((String label) => !isBlank(label))
+                .join('-');
+            flutterUsage.sendTiming(
+              'flutter',
+              name,
+              // If the command provides its own end time, use it. Otherwise report
+              // the duration of the entire execution.
+              (commandResult?.endTimeOverride ?? endTime).difference(startTime),
+              // Report in the form of `success-[parameter1-parameter2]`, all of which
+              // can be null if the command doesn't provide a FlutterCommandResult.
+              label: label == '' ? null : label,
+            );
+          }
+        }
+      },
+    );
   }
 
   /// Perform validation then call [runCommand] to execute the command.
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index 14b3ac4..940309a 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -7,8 +7,10 @@
 
 import 'package:args/args.dart';
 import 'package:args/command_runner.dart';
+import 'package:file/file.dart';
+import 'package:platform/platform.dart';
+import 'package:process/process.dart';
 
-import '../android/android_sdk.dart';
 import '../artifacts.dart';
 import '../base/common.dart';
 import '../base/context.dart';
@@ -168,12 +170,14 @@
 
   @override
   Future<Null> runCommand(ArgResults topLevelResults) async {
-    context.setVariable(Flags, new Flags(topLevelResults));
+    final Map<Type, dynamic> contextOverrides = <Type, dynamic>{
+      Flags: new Flags(topLevelResults),
+    };
 
     // Check for verbose.
     if (topLevelResults['verbose']) {
       // Override the logger.
-      context.setVariable(Logger, new VerboseLogger(context[Logger]));
+      contextOverrides[Logger] = new VerboseLogger(logger);
     }
 
     String recordTo = topLevelResults['record-to'];
@@ -214,9 +218,11 @@
       recordTo = recordTo.trim();
       if (recordTo.isEmpty)
         throwToolExit('record-to location not specified');
-      enableRecordingProcessManager(recordTo);
-      enableRecordingFileSystem(recordTo);
-      await enableRecordingPlatform(recordTo);
+      contextOverrides.addAll(<Type, dynamic>{
+        ProcessManager: getRecordingProcessManager(recordTo),
+        FileSystem: getRecordingFileSystem(recordTo),
+        Platform: await getRecordingPlatform(recordTo),
+      });
       VMService.enableRecordingConnection(recordTo);
     }
 
@@ -224,66 +230,74 @@
       replayFrom = replayFrom.trim();
       if (replayFrom.isEmpty)
         throwToolExit('replay-from location not specified');
-      await enableReplayProcessManager(replayFrom);
-      enableReplayFileSystem(replayFrom);
-      await enableReplayPlatform(replayFrom);
+      contextOverrides.addAll(<Type, dynamic>{
+        ProcessManager: await getReplayProcessManager(replayFrom),
+        FileSystem: getReplayFileSystem(replayFrom),
+        Platform: await getReplayPlatform(replayFrom),
+      });
       VMService.enableReplayConnection(replayFrom);
     }
 
-    logger.quiet = topLevelResults['quiet'];
-
-    if (topLevelResults.wasParsed('color'))
-      logger.supportsColor = topLevelResults['color'];
-
-    // We must set Cache.flutterRoot early because other features use it (e.g.
-    // enginePath's initializer uses it).
-    final String flutterRoot = topLevelResults['flutter-root'] ?? _defaultFlutterRoot;
-    Cache.flutterRoot = fs.path.normalize(fs.path.absolute(flutterRoot));
-
-    if (platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true')
-      await Cache.lock();
-
-    if (topLevelResults['suppress-analytics'])
-      flutterUsage.suppressAnalytics = true;
-
-    _checkFlutterCopy();
-    await FlutterVersion.instance.ensureVersionFile();
-    if (topLevelResults.command?.name != 'upgrade') {
-      await FlutterVersion.instance.checkFlutterVersionFreshness();
-    }
-
-    if (topLevelResults.wasParsed('packages'))
-      PackageMap.globalPackagesPath = fs.path.normalize(fs.path.absolute(topLevelResults['packages']));
-
-    // See if the user specified a specific device.
-    deviceManager.specifiedDeviceId = topLevelResults['device-id'];
-
     // Set up the tooling configuration.
     final String enginePath = _findEnginePath(topLevelResults);
     if (enginePath != null) {
-      Artifacts.useLocalEngine(enginePath, _findEngineBuildPath(topLevelResults, enginePath));
+      contextOverrides.addAll(<Type, dynamic>{
+        Artifacts: Artifacts.getLocalEngine(enginePath, _findEngineBuildPath(topLevelResults, enginePath)),
+      });
     }
 
-    // The Android SDK could already have been set by tests.
-    context.putIfAbsent(AndroidSdk, AndroidSdk.locateAndroidSdk);
+    await context.run<Future<Null>>(
+      overrides: contextOverrides.map<Type, Generator>((Type type, dynamic value) {
+        return new MapEntry<Type, Generator>(type, () => value);
+      }),
+      body: () async {
+        logger.quiet = topLevelResults['quiet'];
 
-    if (topLevelResults['version']) {
-      flutterUsage.sendCommand('version');
-      String status;
-      if (topLevelResults['machine']) {
-        status = const JsonEncoder.withIndent('  ').convert(FlutterVersion.instance.toJson());
-      } else {
-        status = FlutterVersion.instance.toString();
-      }
-      printStatus(status);
-      return;
-    }
+        if (topLevelResults.wasParsed('color'))
+          logger.supportsColor = topLevelResults['color'];
 
-    if (topLevelResults['machine']) {
-      throwToolExit('The --machine flag is only valid with the --version flag.', exitCode: 2);
-    }
+        // We must set Cache.flutterRoot early because other features use it (e.g.
+        // enginePath's initializer uses it).
+        final String flutterRoot = topLevelResults['flutter-root'] ?? _defaultFlutterRoot;
+        Cache.flutterRoot = fs.path.normalize(fs.path.absolute(flutterRoot));
 
-    await super.runCommand(topLevelResults);
+        if (platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true')
+          await Cache.lock();
+
+        if (topLevelResults['suppress-analytics'])
+          flutterUsage.suppressAnalytics = true;
+
+        _checkFlutterCopy();
+        await FlutterVersion.instance.ensureVersionFile();
+        if (topLevelResults.command?.name != 'upgrade') {
+          await FlutterVersion.instance.checkFlutterVersionFreshness();
+        }
+
+        if (topLevelResults.wasParsed('packages'))
+          PackageMap.globalPackagesPath = fs.path.normalize(fs.path.absolute(topLevelResults['packages']));
+
+        // See if the user specified a specific device.
+        deviceManager.specifiedDeviceId = topLevelResults['device-id'];
+
+        if (topLevelResults['version']) {
+          flutterUsage.sendCommand('version');
+          String status;
+          if (topLevelResults['machine']) {
+            status = const JsonEncoder.withIndent('  ').convert(FlutterVersion.instance.toJson());
+          } else {
+            status = FlutterVersion.instance.toString();
+          }
+          printStatus(status);
+          return;
+        }
+
+        if (topLevelResults['machine']) {
+          throwToolExit('The --machine flag is only valid with the --version flag.', exitCode: 2);
+        }
+
+        await super.runCommand(topLevelResults);
+      },
+    );
   }
 
   String _tryEnginePath(String enginePath) {
diff --git a/packages/flutter_tools/lib/src/usage.dart b/packages/flutter_tools/lib/src/usage.dart
index e1efafb..1202a19 100644
--- a/packages/flutter_tools/lib/src/usage.dart
+++ b/packages/flutter_tools/lib/src/usage.dart
@@ -49,7 +49,7 @@
   }
 
   /// Returns [Usage] active in the current app context.
-  static Usage get instance => context.putIfAbsent(Usage, () => new Usage());
+  static Usage get instance => context[Usage];
 
   Analytics _analytics;
 
diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart
index 30b74c0..7e88904 100644
--- a/packages/flutter_tools/lib/src/version.dart
+++ b/packages/flutter_tools/lib/src/version.dart
@@ -167,7 +167,7 @@
       await _run(<String>['git', 'remote', 'remove', _kVersionCheckRemote]);
   }
 
-  static FlutterVersion get instance => context.putIfAbsent(FlutterVersion, () => new FlutterVersion(const Clock()));
+  static FlutterVersion get instance => context[FlutterVersion];
 
   /// Return a short string for the version (e.g. `master/0.0.59-pre.92`, `scroll_refactor/a76bc8e22b`).
   String getVersionString({bool redactUnknownBranches: false}) {
diff --git a/packages/flutter_tools/test/base/build_test.dart b/packages/flutter_tools/test/base/build_test.dart
index 1c14819..14a98d1 100644
--- a/packages/flutter_tools/test/base/build_test.dart
+++ b/packages/flutter_tools/test/base/build_test.dart
@@ -432,26 +432,30 @@
         snapshotPath: 'output.snapshot',
         depfileContent: 'output.snapshot : main.dart other.dart',
       );
-      context.setVariable(GenSnapshot, genSnapshot);
 
-      await fs.file('main.dart').writeAsString('import "other.dart";\nvoid main() {}');
-      await fs.file('other.dart').writeAsString('import "main.dart";\nvoid main() {}');
-      await fs.file('output.snapshot').create();
-      await fs.file('output.snapshot.d').writeAsString('output.snapshot : main.dart');
-      await writeFingerprint(files: <String, String>{
-        'main.dart': 'bc096b33f14dde5e0ffaf93a1d03395c',
-        'other.dart': 'e0c35f083f0ad76b2d87100ec678b516',
-        'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',
-      });
-      await buildSnapshot(mainPath: 'other.dart');
+      await context.run(
+        overrides: <Type, Generator>{GenSnapshot: () => genSnapshot},
+        body: () async {
+          await fs.file('main.dart').writeAsString('import "other.dart";\nvoid main() {}');
+          await fs.file('other.dart').writeAsString('import "main.dart";\nvoid main() {}');
+          await fs.file('output.snapshot').create();
+          await fs.file('output.snapshot.d').writeAsString('output.snapshot : main.dart');
+          await writeFingerprint(files: <String, String>{
+            'main.dart': 'bc096b33f14dde5e0ffaf93a1d03395c',
+            'other.dart': 'e0c35f083f0ad76b2d87100ec678b516',
+            'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',
+          });
+          await buildSnapshot(mainPath: 'other.dart');
 
-      expect(genSnapshot.callCount, 1);
-      expectFingerprintHas(
-        entryPoint: 'other.dart',
-        checksums: <String, String>{
-          'main.dart': 'bc096b33f14dde5e0ffaf93a1d03395c',
-          'other.dart': 'e0c35f083f0ad76b2d87100ec678b516',
-          'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',
+          expect(genSnapshot.callCount, 1);
+          expectFingerprintHas(
+            entryPoint: 'other.dart',
+            checksums: <String, String>{
+              'main.dart': 'bc096b33f14dde5e0ffaf93a1d03395c',
+              'other.dart': 'e0c35f083f0ad76b2d87100ec678b516',
+              'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',
+            },
+          );
         },
       );
     }, overrides: contextOverrides);
diff --git a/packages/flutter_tools/test/base/context_test.dart b/packages/flutter_tools/test/base/context_test.dart
index 365e3fa..72dde9e 100644
--- a/packages/flutter_tools/test/base/context_test.dart
+++ b/packages/flutter_tools/test/base/context_test.dart
@@ -2,100 +2,274 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:flutter_tools/src/base/context.dart' hide context;
-import 'package:flutter_tools/src/base/context.dart' as pkg;
-import 'package:flutter_tools/src/base/logger.dart';
-import 'package:flutter_tools/src/globals.dart';
+import 'dart:async';
+
+import 'package:flutter_tools/src/base/context.dart';
 import 'package:test/test.dart';
 
 void main() {
   group('AppContext', () {
-    test('error', () async {
-      final AppContext context = new AppContext();
-      final BufferLogger mockLogger = new BufferLogger();
-      context.setVariable(Logger, mockLogger);
+    group('global getter', () {
+      bool called;
 
-      await context.runInZone(() {
-        printError('foo bar');
+      setUp(() {
+        called = false;
       });
 
-      expect(mockLogger.errorText, 'foo bar\n');
-      expect(mockLogger.statusText, '');
-      expect(mockLogger.traceText, '');
-    });
-
-    test('status', () async {
-      final AppContext context = new AppContext();
-      final BufferLogger mockLogger = new BufferLogger();
-      context.setVariable(Logger, mockLogger);
-
-      await context.runInZone(() {
-        printStatus('foo bar');
+      test('returns non-null context in the root zone', () {
+        expect(context, isNotNull);
       });
 
-      expect(mockLogger.errorText, '');
-      expect(mockLogger.statusText, 'foo bar\n');
-      expect(mockLogger.traceText, '');
-    });
-
-    test('trace', () async {
-      final AppContext context = new AppContext();
-      final BufferLogger mockLogger = new BufferLogger();
-      context.setVariable(Logger, mockLogger);
-
-      await context.runInZone(() {
-        printTrace('foo bar');
+      test('returns root context in child of root zone if zone was manually created', () {
+        final Zone rootZone = Zone.current;
+        final AppContext rootContext = context;
+        runZoned(() {
+          expect(Zone.current, isNot(rootZone));
+          expect(Zone.current.parent, rootZone);
+          expect(context, rootContext);
+          called = true;
+        });
+        expect(called, isTrue);
       });
 
-      expect(mockLogger.errorText, '');
-      expect(mockLogger.statusText, '');
-      expect(mockLogger.traceText, 'foo bar\n');
-    });
+      test('returns child context after run', () {
+        final AppContext rootContext = context;
+        rootContext.run(name: 'child', body: () {
+          expect(context, isNot(rootContext));
+          expect(context.name, 'child');
+          called = true;
+        });
+        expect(called, isTrue);
+      });
 
-    test('awaitNestedZones', () async {
-      final AppContext outerContext = new AppContext();
-      await outerContext.runInZone(() async {
-        final AppContext middleContext = new AppContext();
-        await middleContext.runInZone(() async {
-          final AppContext innerContext = new AppContext();
-          await innerContext.runInZone(() async {
-            expect(innerContext.getVariable(String), isNull);
+      test('returns grandchild context after nested run', () {
+        final AppContext rootContext = context;
+        rootContext.run(name: 'child', body: () {
+          final AppContext childContext = context;
+          childContext.run(name: 'grandchild', body: () {
+            expect(context, isNot(rootContext));
+            expect(context, isNot(childContext));
+            expect(context.name, 'grandchild');
+            called = true;
           });
         });
+        expect(called, isTrue);
+      });
+
+      test('scans up zone hierarchy for first context', () {
+        final AppContext rootContext = context;
+        rootContext.run(name: 'child', body: () {
+          final AppContext childContext = context;
+          runZoned(() {
+            expect(context, isNot(rootContext));
+            expect(context, same(childContext));
+            expect(context.name, 'child');
+            called = true;
+          });
+        });
+        expect(called, isTrue);
       });
     });
 
-    test('fireAndForgetNestedZones', () async {
-      final AppContext outerContext = new AppContext();
-      outerContext.runInZone(() async {
-        final AppContext middleContext = new AppContext();
-        middleContext.runInZone(() async {
-          final AppContext innerContext = new AppContext();
-          innerContext.runInZone(() async {
-            expect(innerContext.getVariable(String), isNull);
-          });
-        });
+    group('operator[]', () {
+      test('still finds values if async code runs after body has finished', () async {
+        final Completer<void> outer = new Completer<void>();
+        final Completer<void> inner = new Completer<void>();
+        String value;
+        context.run<void>(
+          body: () {
+            outer.future.then((_) {
+              value = context[String];
+              inner.complete();
+            });
+          },
+          fallbacks: <Type, Generator>{
+            String: () => 'value',
+          },
+        );
+        expect(value, isNull);
+        outer.complete();
+        await inner.future;
+        expect(value, 'value');
+      });
+
+      test('caches generated override values', () {
+        int consultationCount = 0;
+        String value;
+        context.run(
+          body: () {
+            final StringBuffer buf = new StringBuffer(context[String]);
+            buf.write(context[String]);
+            context.run(body: () {
+              buf.write(context[String]);
+            });
+            value = buf.toString();
+          },
+          overrides: <Type, Generator>{
+            String: () {
+              consultationCount++;
+              return 'v';
+            },
+          },
+        );
+        expect(value, 'vvv');
+        expect(consultationCount, 1);
+      });
+
+      test('caches generated fallback values', () {
+        int consultationCount = 0;
+        String value;
+        context.run(
+          body: () {
+            final StringBuffer buf = new StringBuffer(context[String]);
+            buf.write(context[String]);
+            context.run(body: () {
+              buf.write(context[String]);
+            });
+            value = buf.toString();
+          },
+          fallbacks: <Type, Generator>{
+            String: () {
+              consultationCount++;
+              return 'v';
+            },
+          },
+        );
+        expect(value, 'vvv');
+        expect(consultationCount, 1);
+      });
+
+      test('returns null if generated value is null', () {
+        final String value = context.run(
+          body: () => context[String],
+          overrides: <Type, Generator>{
+            String: () => null,
+          },
+        );
+        expect(value, isNull);
+      });
+
+      test('throws if generator has dependency cycle', () async {
+        final Future<String> value = context.run<Future<String>>(
+          body: () async {
+            return context[String];
+          },
+          fallbacks: <Type, Generator>{
+            int: () => int.parse(context[String]),
+            String: () => '${context[double]}',
+            double: () => context[int] * 1.0,
+          },
+        );
+        try {
+          await value;
+          fail('ContextDependencyCycleException expected but not thrown.');
+        } on ContextDependencyCycleException catch (e) {
+          expect(e.cycle, <Type>[String, double, int]);
+          expect(e.toString(), 'Dependency cycle detected: String -> double -> int');
+        }
       });
     });
 
-    test('overriddenValuesInNestedZones', () async {
-      expect(pkg.context, isNull);
-      final AppContext outerContext = new AppContext();
-      outerContext.setVariable(String, 'outer');
-      outerContext.runInZone(() async {
-        expect(pkg.context[String], 'outer');
-        final AppContext middleContext = new AppContext();
-        middleContext.setVariable(String, 'middle');
-        middleContext.runInZone(() async {
-          expect(pkg.context[String], 'middle');
-          final AppContext innerContext = new AppContext();
-          innerContext.setVariable(String, 'inner');
-          innerContext.runInZone(() async {
-            expect(pkg.context[String], 'inner');
-          });
-          expect(pkg.context[String], 'middle');
+    group('run', () {
+      test('returns the value returned by body', () async {
+        expect(context.run<int>(body: () => 123), 123);
+        expect(context.run<String>(body: () => 'value'), 'value');
+        expect(await context.run<Future<int>>(body: () async => 456), 456);
+      });
+
+      test('passes name to child context', () {
+        context.run(name: 'child', body: () {
+          expect(context.name, 'child');
         });
-        expect(pkg.context[String], 'outer');
+      });
+
+      group('fallbacks', () {
+        bool called;
+
+        setUp(() {
+          called = false;
+        });
+
+        test('are applied after parent context is consulted', () {
+          final String value = context.run<String>(
+            body: () {
+              return context.run<String>(
+                body: () {
+                  called = true;
+                  return context[String];
+                },
+                fallbacks: <Type, Generator>{
+                  String: () => 'child',
+                },
+              );
+            },
+          );
+          expect(called, isTrue);
+          expect(value, 'child');
+        });
+
+        test('are not applied if parent context supplies value', () {
+          bool childConsulted = false;
+          final String value = context.run<String>(
+            body: () {
+              return context.run<String>(
+                body: () {
+                  called = true;
+                  return context[String];
+                },
+                fallbacks: <Type, Generator>{
+                  String: () {
+                    childConsulted = true;
+                    return 'child';
+                  },
+                },
+              );
+            },
+            fallbacks: <Type, Generator>{
+              String: () => 'parent',
+            },
+          );
+          expect(called, isTrue);
+          expect(value, 'parent');
+          expect(childConsulted, isFalse);
+        });
+
+        test('may depend on one another', () {
+          final String value = context.run<String>(
+            body: () {
+              return context[String];
+            },
+            fallbacks: <Type, Generator>{
+              int: () => 123,
+              String: () => '-${context[int]}-',
+            },
+          );
+          expect(value, '-123-');
+        });
+      });
+
+      group('overrides', () {
+        test('intercept consultation of parent context', () {
+          bool parentConsulted = false;
+          final String value = context.run<String>(
+            body: () {
+              return context.run<String>(
+                body: () => context[String],
+                overrides: <Type, Generator>{
+                  String: () => 'child',
+                },
+              );
+            },
+            fallbacks: <Type, Generator>{
+              String: () {
+                parentConsulted = true;
+                return 'parent';
+              },
+            },
+          );
+          expect(value, 'child');
+          expect(parentConsulted, isFalse);
+        });
       });
     });
   });
diff --git a/packages/flutter_tools/test/base/flags_test.dart b/packages/flutter_tools/test/base/flags_test.dart
index 9779d3f..4074201 100644
--- a/packages/flutter_tools/test/base/flags_test.dart
+++ b/packages/flutter_tools/test/base/flags_test.dart
@@ -32,11 +32,6 @@
   });
 
   group('flags', () {
-    test('returns no-op flags when not inside Flutter runner', () {
-      expect(flags, isNotNull);
-      expect(flags['foo'], isNull);
-    });
-
     testUsingContext('returns null for undefined flags', () async {
       await runCommand(<String>[], () {
         expect(flags['undefined-flag'], isNull);
diff --git a/packages/flutter_tools/test/commands/daemon_test.dart b/packages/flutter_tools/test/commands/daemon_test.dart
index 07962c8..ddd0ba7 100644
--- a/packages/flutter_tools/test/commands/daemon_test.dart
+++ b/packages/flutter_tools/test/commands/daemon_test.dart
@@ -5,7 +5,6 @@
 import 'dart:async';
 
 import 'package:flutter_tools/src/android/android_workflow.dart';
-import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/commands/daemon.dart';
 import 'package:flutter_tools/src/globals.dart';
@@ -18,14 +17,11 @@
 
 void main() {
   Daemon daemon;
-  AppContext appContext;
   NotifyingLogger notifyingLogger;
 
   group('daemon', () {
     setUp(() {
-      appContext = new AppContext();
       notifyingLogger = new NotifyingLogger();
-      appContext.setVariable(Logger, notifyingLogger);
     });
 
     tearDown(() {
@@ -51,51 +47,51 @@
       commands.close();
     });
 
-    testUsingContext('printError should send daemon.logMessage event', () {
-      return appContext.runInZone(() async {
-        final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
-        final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
-        daemon = new Daemon(
-          commands.stream,
-          responses.add,
-          notifyingLogger: notifyingLogger
-        );
-        printError('daemon.logMessage test');
-        final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> map) {
-          return map['event'] == 'daemon.logMessage' && map['params']['level'] == 'error';
-        });
-        expect(response['id'], isNull);
-        expect(response['event'], 'daemon.logMessage');
-        final Map<String, String> logMessage = response['params'];
-        expect(logMessage['level'], 'error');
-        expect(logMessage['message'], 'daemon.logMessage test');
-        responses.close();
-        commands.close();
+    testUsingContext('printError should send daemon.logMessage event', () async {
+      final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
+      final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
+      daemon = new Daemon(
+        commands.stream,
+        responses.add,
+        notifyingLogger: notifyingLogger,
+      );
+      printError('daemon.logMessage test');
+      final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> map) {
+        return map['event'] == 'daemon.logMessage' && map['params']['level'] == 'error';
       });
+      expect(response['id'], isNull);
+      expect(response['event'], 'daemon.logMessage');
+      final Map<String, String> logMessage = response['params'];
+      expect(logMessage['level'], 'error');
+      expect(logMessage['message'], 'daemon.logMessage test');
+      responses.close();
+      commands.close();
+    }, overrides: <Type, Generator>{
+      Logger: () => notifyingLogger,
     });
 
     testUsingContext('printStatus should log to stdout when logToStdout is enabled', () async {
       final StringBuffer buffer = new StringBuffer();
 
       await runZoned(() async {
-        return appContext.runInZone(() async {
-          final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
-          final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
-          daemon = new Daemon(
-            commands.stream,
-            responses.add,
-            notifyingLogger: notifyingLogger,
-            logToStdout: true
-          );
-          printStatus('daemon.logMessage test');
-          // Service the event loop.
-          await new Future<Null>.value();
-        });
+        final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
+        final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
+        daemon = new Daemon(
+          commands.stream,
+          responses.add,
+          notifyingLogger: notifyingLogger,
+          logToStdout: true,
+        );
+        printStatus('daemon.logMessage test');
+        // Service the event loop.
+        await new Future<Null>.value();
       }, zoneSpecification: new ZoneSpecification(print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
         buffer.writeln(line);
       }));
 
       expect(buffer.toString().trim(), 'daemon.logMessage test');
+    }, overrides: <Type, Generator>{
+      Logger: () => notifyingLogger,
     });
 
     testUsingContext('daemon.shutdown command should stop daemon', () async {
diff --git a/packages/flutter_tools/test/crash_reporting_test.dart b/packages/flutter_tools/test/crash_reporting_test.dart
index de3b23d..cb77182 100644
--- a/packages/flutter_tools/test/crash_reporting_test.dart
+++ b/packages/flutter_tools/test/crash_reporting_test.dart
@@ -36,7 +36,7 @@
 
     tearDown(() {
       tools.crashFileSystem = const LocalFileSystem();
-      tools.writelnStderr = stderr.writeln;
+      tools.writelnStderr = const Stdio().stderr.writeln;
       restoreExitFunction();
     });
 
diff --git a/packages/flutter_tools/test/dart/pub_get_test.dart b/packages/flutter_tools/test/dart/pub_get_test.dart
index 2dcbe6d..f69c246 100644
--- a/packages/flutter_tools/test/dart/pub_get_test.dart
+++ b/packages/flutter_tools/test/dart/pub_get_test.dart
@@ -28,7 +28,7 @@
   testUsingContext('pub get 69', () async {
     String error;
 
-    final MockProcessManager processMock = context.getVariable(ProcessManager);
+    final MockProcessManager processMock = context[ProcessManager];
 
     new FakeAsync().run((FakeAsync time) {
       expect(processMock.lastPubEnvironment, isNull);
@@ -96,8 +96,8 @@
   testUsingContext('pub cache in root is used', () async {
     String error;
 
-    final MockProcessManager processMock = context.getVariable(ProcessManager);
-    final MockFileSystem fsMock = context.getVariable(FileSystem);
+    final MockProcessManager processMock = context[ProcessManager];
+    final MockFileSystem fsMock = context[FileSystem];
 
     new FakeAsync().run((FakeAsync time) {
       MockDirectory.findCache = true;
@@ -123,7 +123,7 @@
   testUsingContext('pub cache in environment is used', () async {
     String error;
 
-    final MockProcessManager processMock = context.getVariable(ProcessManager);
+    final MockProcessManager processMock = context[ProcessManager];
 
     new FakeAsync().run((FakeAsync time) {
       MockDirectory.findCache = true;
diff --git a/packages/flutter_tools/test/devfs_test.dart b/packages/flutter_tools/test/devfs_test.dart
index 01eba00..cccb9b4 100644
--- a/packages/flutter_tools/test/devfs_test.dart
+++ b/packages/flutter_tools/test/devfs_test.dart
@@ -27,7 +27,7 @@
   Directory tempDir;
   String basePath;
   DevFS devFS;
-  final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
+  final AssetBundle assetBundle = AssetBundleFactory.defaultInstance.createBundle();
 
   setUpAll(() {
     fs = new MemoryFileSystem();
diff --git a/packages/flutter_tools/test/replay/common.dart b/packages/flutter_tools/test/replay/common.dart
deleted file mode 100644
index 2463d0a..0000000
--- a/packages/flutter_tools/test/replay/common.dart
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:file/local.dart';
-import 'package:flutter_tools/runner.dart' as tools;
-import 'package:flutter_tools/src/cache.dart';
-import 'package:flutter_tools/src/base/context.dart';
-import 'package:flutter_tools/src/base/io.dart' as io;
-import 'package:flutter_tools/src/base/port_scanner.dart';
-import 'package:flutter_tools/src/runner/flutter_command.dart';
-import 'package:test/test.dart';
-
-import '../src/common.dart';
-import '../src/context.dart';
-
-/// Runs the specified [testMethod] in a minimal `AppContext` that is set up
-/// to redirect log output to a `BufferLogger` to avoid spamming `stdout`.
-///
-/// Test methods will generally want to use [expectProcessExits] in their method
-/// bodies.
-void testReplay(
-  String description,
-  dynamic testMethod(), {
-  Timeout timeout,
-  Map<Type, Generator> overrides: const <Type, Generator>{},
-  bool skip,
-}) {
-  setUp(() {
-    io.setExitFunctionForTests();
-  });
-
-  tearDown(() {
-    io.restoreExitFunction();
-  });
-
-  testUsingContext(
-    description,
-    testMethod,
-    timeout: timeout,
-    overrides: overrides,
-    skip: skip,
-    initializeContext: (AppContext testContext) {
-      testContext.putIfAbsent(PortScanner, () => new MockPortScanner());
-    },
-  );
-}
-
-/// Expects that the specified [command] to Flutter tools exits with the
-/// specified [exitCode] (defaults to zero). It is expected that callers will
-/// be running in a test via [testReplay].
-///
-/// [command] should be the list of arguments that are passed to the `flutter`
-/// command-line tool. For example:
-///
-/// ```
-///   <String>[
-///     'run',
-///     '--no-hot',
-///     '--no-resident',
-///   ]
-/// ```
-void expectProcessExits(
-  FlutterCommand command, {
-  List<String> args: const <String>[],
-  dynamic exitCode: 0,
-}) {
-  final Future<Null> runFuture = tools.run(
-    <String>[command.name]..addAll(args),
-    <FlutterCommand>[command],
-    reportCrashes: false,
-    flutterVersion: 'test',
-  );
-  expect(runFuture, throwsProcessExit(exitCode));
-}
-
-/// The base path of the replay tests.
-String get replayBase {
-  return const LocalFileSystem().path.joinAll(<String>[
-    Cache.flutterRoot,
-    'packages',
-    'flutter_tools',
-    'test',
-    'replay',
-  ]);
-}
diff --git a/packages/flutter_tools/test/resident_runner_test.dart b/packages/flutter_tools/test/resident_runner_test.dart
index c620bb6..44e88c9 100644
--- a/packages/flutter_tools/test/resident_runner_test.dart
+++ b/packages/flutter_tools/test/resident_runner_test.dart
@@ -43,39 +43,41 @@
 }
 
 void main() {
-  TestRunner testRunner;
-
-  setUp(() {
+  TestRunner createTestRunner() {
     // TODO(jacobr): make these tests run with `previewDart2: true` and
     // `trackWidgetCreation: true` as well as the default flags.
     // Currently the TestRunner is not properly configured to be able to run
     // with `previewDart2: true` due to missing resources.
-    testRunner = new TestRunner(
+    return new TestRunner(
       <FlutterDevice>[new FlutterDevice(
         new MockDevice(),
         previewDart2: false,
         trackWidgetCreation: false,
       )],
     );
-  });
+  }
 
   group('keyboard input handling', () {
     testUsingContext('single help character', () async {
+      final TestRunner testRunner = createTestRunner();
       expect(testRunner.hasHelpBeenPrinted, isFalse);
       await testRunner.processTerminalInput('h');
       expect(testRunner.hasHelpBeenPrinted, isTrue);
     });
     testUsingContext('help character surrounded with newlines', () async {
+      final TestRunner testRunner = createTestRunner();
       expect(testRunner.hasHelpBeenPrinted, isFalse);
       await testRunner.processTerminalInput('\nh\n');
       expect(testRunner.hasHelpBeenPrinted, isTrue);
     });
     testUsingContext('reload character with trailing newline', () async {
+      final TestRunner testRunner = createTestRunner();
       expect(testRunner.receivedCommand, isNull);
       await testRunner.processTerminalInput('r\n');
       expect(testRunner.receivedCommand, equals('r'));
     });
     testUsingContext('newlines', () async {
+      final TestRunner testRunner = createTestRunner();
       expect(testRunner.receivedCommand, isNull);
       await testRunner.processTerminalInput('\n\n');
       expect(testRunner.receivedCommand, equals(''));
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart
index 01931b9..eabb563 100644
--- a/packages/flutter_tools/test/src/context.dart
+++ b/packages/flutter_tools/test/src/context.dart
@@ -3,77 +3,46 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:io' as io;
 
 import 'package:flutter_tools/src/android/android_workflow.dart';
-import 'package:flutter_tools/src/artifacts.dart';
 import 'package:flutter_tools/src/base/config.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
 import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/base/os.dart';
-import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/base/port_scanner.dart';
-import 'package:flutter_tools/src/base/utils.dart';
 import 'package:flutter_tools/src/cache.dart';
-import 'package:flutter_tools/src/devfs.dart';
+import 'package:flutter_tools/src/context_runner.dart';
 import 'package:flutter_tools/src/device.dart';
 import 'package:flutter_tools/src/doctor.dart';
-import 'package:flutter_tools/src/ios/mac.dart';
 import 'package:flutter_tools/src/ios/simulators.dart';
 import 'package:flutter_tools/src/ios/xcodeproj.dart';
-import 'package:flutter_tools/src/run_hot.dart';
 import 'package:flutter_tools/src/usage.dart';
 import 'package:flutter_tools/src/version.dart';
 import 'package:mockito/mockito.dart';
-import 'package:process/process.dart';
 import 'package:quiver/time.dart';
 import 'package:test/test.dart';
 
 import 'common.dart';
 
+export 'package:flutter_tools/src/base/context.dart' show Generator;
+
 /// Return the test logger. This assumes that the current Logger is a BufferLogger.
 BufferLogger get testLogger => context[Logger];
 
 MockDeviceManager get testDeviceManager => context[DeviceManager];
 MockDoctor get testDoctor => context[Doctor];
 
-typedef dynamic Generator();
-
 typedef void ContextInitializer(AppContext testContext);
 
-void _defaultInitializeContext(AppContext testContext) {
-  testContext
-    ..putIfAbsent(DeviceManager, () => new MockDeviceManager())
-    ..putIfAbsent(DevFSConfig, () => new DevFSConfig())
-    ..putIfAbsent(Doctor, () => new MockDoctor())
-    ..putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig())
-    ..putIfAbsent(Cache, () => new Cache())
-    ..putIfAbsent(Artifacts, () => new CachedArtifacts())
-    ..putIfAbsent(OperatingSystemUtils, () => new MockOperatingSystemUtils())
-    ..putIfAbsent(PortScanner, () => new MockPortScanner())
-    ..putIfAbsent(Xcode, () => new Xcode())
-    ..putIfAbsent(XcodeProjectInterpreter, () => new MockXcodeProjectInterpreter())
-    ..putIfAbsent(IOSSimulatorUtils, () {
-      final MockIOSSimulatorUtils mock = new MockIOSSimulatorUtils();
-      when(mock.getAttachedDevices()).thenReturn(<IOSSimulator>[]);
-      return mock;
-    })
-    ..putIfAbsent(SimControl, () => new MockSimControl())
-    ..putIfAbsent(Usage, () => new MockUsage())
-    ..putIfAbsent(FlutterVersion, () => new MockFlutterVersion())
-    ..putIfAbsent(Clock, () => const Clock())
-    ..putIfAbsent(HttpClient, () => new MockHttpClient());
-}
-
 void testUsingContext(String description, dynamic testMethod(), {
   Timeout timeout,
   Map<Type, Generator> overrides: const <Type, Generator>{},
-  ContextInitializer initializeContext: _defaultInitializeContext,
   String testOn,
   bool skip, // should default to `false`, but https://github.com/dart-lang/test/issues/545 doesn't allow this
 }) {
-
   // Ensure we don't rely on the default [Config] constructor which will
   // leak a sticky $HOME/.flutter_settings behind!
   Directory configDir;
@@ -89,45 +58,58 @@
   }
 
   test(description, () async {
-    final AppContext testContext = new AppContext();
+    await runInContext<dynamic>(() {
+      return context.run<Future<dynamic>>(
+        name: 'mocks',
+        overrides: <Type, Generator>{
+          Config: () => buildConfig(fs),
+          DeviceManager: () => new MockDeviceManager(),
+          Doctor: () => new MockDoctor(),
+          FlutterVersion: () => new MockFlutterVersion(),
+          HttpClient: () => new MockHttpClient(),
+          IOSSimulatorUtils: () {
+            final MockIOSSimulatorUtils mock = new MockIOSSimulatorUtils();
+            when(mock.getAttachedDevices()).thenReturn(<IOSSimulator>[]);
+            return mock;
+          },
+          Logger: () => new BufferLogger(),
+          OperatingSystemUtils: () => new MockOperatingSystemUtils(),
+          PortScanner: () => new MockPortScanner(),
+          SimControl: () => new MockSimControl(),
+          Usage: () => new MockUsage(),
+          XcodeProjectInterpreter: () => new MockXcodeProjectInterpreter(),
+        },
+        body: () {
+          final String flutterRoot = getFlutterRoot();
 
-    // The context always starts with these value since others depend on them.
-    testContext
-      ..putIfAbsent(BotDetector, () => const BotDetector())
-      ..putIfAbsent(Stdio, () => const Stdio())
-      ..putIfAbsent(Platform, () => const LocalPlatform())
-      ..putIfAbsent(FileSystem, () => const LocalFileSystem())
-      ..putIfAbsent(ProcessManager, () => const LocalProcessManager())
-      ..putIfAbsent(Logger, () => new BufferLogger())
-      ..putIfAbsent(Config, () => buildConfig(testContext[FileSystem]));
+          return runZoned(() {
+            try {
+              return context.run<Future<dynamic>>(
+                // Apply the overrides to the test context in the zone since their
+                // instantiation may reference items already stored on the context.
+                overrides: overrides,
+                name: 'test-specific overrides',
+                body: () async {
+                  // Provide a sane default for the flutterRoot directory. Individual
+                  // tests can override this either in the test or during setup.
+                  Cache.flutterRoot ??= flutterRoot;
 
-    // Apply the initializer after seeding the base value above.
-    initializeContext(testContext);
-
-    final String flutterRoot = getFlutterRoot();
-
-    try {
-      return await testContext.runInZone(() async {
-        // Apply the overrides to the test context in the zone since their
-        // instantiation may reference items already stored on the context.
-        overrides.forEach((Type type, dynamic value()) {
-          context.setVariable(type, value());
-        });
-
-        // Provide a sane default for the flutterRoot directory. Individual
-        // tests can override this either in the test or during setup.
-        Cache.flutterRoot ??= flutterRoot;
-
-        return await testMethod();
-      }, onError: (dynamic error, StackTrace stackTrace) {
-        _printBufferedErrors(testContext);
-        throw error;
-      });
-    } catch (error) {
-      _printBufferedErrors(testContext);
-      rethrow;
-    }
-
+                  return await testMethod();
+                },
+              );
+            } catch (error) {
+              _printBufferedErrors(context);
+              rethrow;
+            }
+          }, onError: (dynamic error, StackTrace stackTrace) {
+            io.stdout.writeln(error);
+            io.stdout.writeln(stackTrace);
+            _printBufferedErrors(context);
+            throw error;
+          });
+        },
+      );
+    });
   }, timeout: timeout, testOn: testOn, skip: skip);
 }
 
diff --git a/packages/flutter_tools/test/version_test.dart b/packages/flutter_tools/test/version_test.dart
index 4e53722..8495328 100644
--- a/packages/flutter_tools/test/version_test.dart
+++ b/packages/flutter_tools/test/version_test.dart
@@ -25,28 +25,62 @@
 final DateTime _stampOutOfDate = _testClock.agoBy(FlutterVersion.kCheckAgeConsideredUpToDate * 2);
 
 void main() {
-  group('$FlutterVersion', () {
-    ProcessManager mockProcessManager;
+  MockProcessManager mockProcessManager;
+  MockCache mockCache;
 
+  setUp(() {
+    mockProcessManager = new MockProcessManager();
+    mockCache = new MockCache();
+
+    when(mockProcessManager.runSync(
+      <String>['git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{u}'],
+      workingDirectory: any,
+      environment: any,
+    )).thenReturn(new ProcessResult(101, 0, 'channel', ''));
+    when(mockProcessManager.runSync(
+      <String>['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
+      workingDirectory: any,
+      environment: any,
+    )).thenReturn(new ProcessResult(102, 0, 'branch', ''));
+    when(mockProcessManager.runSync(
+      <String>['git', 'log', '-n', '1', '--pretty=format:%H'],
+      workingDirectory: any,
+      environment: any,
+    )).thenReturn(new ProcessResult(103, 0, '1234abcd', ''));
+    when(mockProcessManager.runSync(
+      <String>['git', 'log', '-n', '1', '--pretty=format:%ar'],
+      workingDirectory: any,
+      environment: any,
+    )).thenReturn(new ProcessResult(104, 0, '1 second ago', ''));
+    when(mockProcessManager.runSync(
+      <String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
+      workingDirectory: any,
+      environment: any,
+    )).thenReturn(new ProcessResult(105, 0, 'v0.1.2-3-1234abcd', ''));
+  });
+
+  group('$FlutterVersion', () {
     setUpAll(() {
       Cache.disableLocking();
       FlutterVersion.kPauseToLetUserReadTheMessage = Duration.zero;
     });
 
-    setUp(() {
-      mockProcessManager = new MockProcessManager();
-    });
-
-    testFlutterVersion('prints nothing when Flutter installation looks fresh', () async {
-      fakeData(localCommitDate: _upToDateVersion);
+    testUsingContext('prints nothing when Flutter installation looks fresh', () async {
+      fakeData(mockProcessManager, mockCache, localCommitDate: _upToDateVersion);
       await FlutterVersion.instance.checkFlutterVersionFreshness();
       _expectVersionMessage('');
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('prints nothing when Flutter installation looks out-of-date by is actually up-to-date', () async {
+    testUsingContext('prints nothing when Flutter installation looks out-of-date by is actually up-to-date', () async {
       final FlutterVersion version = FlutterVersion.instance;
 
       fakeData(
+        mockProcessManager,
+        mockCache,
         localCommitDate: _outOfDateVersion,
         stamp: new VersionCheckStamp(
           lastTimeVersionWasChecked: _stampOutOfDate,
@@ -59,12 +93,18 @@
 
       await version.checkFlutterVersionFreshness();
       _expectVersionMessage('');
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('does not ping server when version stamp is up-to-date', () async {
+    testUsingContext('does not ping server when version stamp is up-to-date', () async {
       final FlutterVersion version = FlutterVersion.instance;
 
       fakeData(
+        mockProcessManager,
+        mockCache,
         localCommitDate: _outOfDateVersion,
         stamp: new VersionCheckStamp(
           lastTimeVersionWasChecked: _stampUpToDate,
@@ -75,18 +115,24 @@
 
       await version.checkFlutterVersionFreshness();
       _expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(_outOfDateVersion)));
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('does not print warning if printed recently', () async {
+    testUsingContext('does not print warning if printed recently', () async {
       final FlutterVersion version = FlutterVersion.instance;
 
       fakeData(
-          localCommitDate: _outOfDateVersion,
-          stamp: new VersionCheckStamp(
-              lastTimeVersionWasChecked: _stampUpToDate,
-              lastKnownRemoteVersion: _upToDateVersion,
-          ),
-          expectSetStamp: true,
+        mockProcessManager,
+        mockCache,
+        localCommitDate: _outOfDateVersion,
+        stamp: new VersionCheckStamp(
+            lastTimeVersionWasChecked: _stampUpToDate,
+            lastKnownRemoteVersion: _upToDateVersion,
+        ),
+        expectSetStamp: true,
       );
 
       await version.checkFlutterVersionFreshness();
@@ -95,16 +141,22 @@
 
       await version.checkFlutterVersionFreshness();
       _expectVersionMessage('');
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('pings server when version stamp is missing then does not', () async {
+    testUsingContext('pings server when version stamp is missing then does not', () async {
       final FlutterVersion version = FlutterVersion.instance;
 
       fakeData(
-          localCommitDate: _outOfDateVersion,
-          remoteCommitDate: _upToDateVersion,
-          expectSetStamp: true,
-          expectServerPing: true,
+        mockProcessManager,
+        mockCache,
+        localCommitDate: _outOfDateVersion,
+        remoteCommitDate: _upToDateVersion,
+        expectSetStamp: true,
+        expectServerPing: true,
       );
 
       await version.checkFlutterVersionFreshness();
@@ -112,42 +164,60 @@
 
       // Immediate subsequent check is not expected to ping the server.
       fakeData(
+        mockProcessManager,
+        mockCache,
         localCommitDate: _outOfDateVersion,
         stamp: await VersionCheckStamp.load(),
       );
       await version.checkFlutterVersionFreshness();
       _expectVersionMessage('');
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('pings server when version stamp is out-of-date', () async {
+    testUsingContext('pings server when version stamp is out-of-date', () async {
       final FlutterVersion version = FlutterVersion.instance;
 
       fakeData(
-          localCommitDate: _outOfDateVersion,
-          stamp: new VersionCheckStamp(
-              lastTimeVersionWasChecked: _stampOutOfDate,
-              lastKnownRemoteVersion: _testClock.ago(days: 2),
-          ),
-          remoteCommitDate: _upToDateVersion,
-          expectSetStamp: true,
-          expectServerPing: true,
+        mockProcessManager,
+        mockCache,
+        localCommitDate: _outOfDateVersion,
+        stamp: new VersionCheckStamp(
+            lastTimeVersionWasChecked: _stampOutOfDate,
+            lastKnownRemoteVersion: _testClock.ago(days: 2),
+        ),
+        remoteCommitDate: _upToDateVersion,
+        expectSetStamp: true,
+        expectServerPing: true,
       );
 
       await version.checkFlutterVersionFreshness();
       _expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(_outOfDateVersion)));
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('ignores network issues', () async {
+    testUsingContext('ignores network issues', () async {
       final FlutterVersion version = FlutterVersion.instance;
 
       fakeData(
-          localCommitDate: _outOfDateVersion,
-          errorOnFetch: true,
-          expectServerPing: true,
+        mockProcessManager,
+        mockCache,
+        localCommitDate: _outOfDateVersion,
+        errorOnFetch: true,
+        expectServerPing: true,
       );
 
       await version.checkFlutterVersionFreshness();
       _expectVersionMessage('');
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
     testUsingContext('versions comparison', () async {
@@ -182,23 +252,35 @@
       expect(stamp.lastTimeWarningWasPrinted, isNull);
     }
 
-    testFlutterVersion('loads blank when stamp file missing', () async {
-      fakeData();
+    testUsingContext('loads blank when stamp file missing', () async {
+      fakeData(mockProcessManager, mockCache);
       _expectDefault(await VersionCheckStamp.load());
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('loads blank when stamp file is malformed JSON', () async {
-      fakeData(stampJson: '<');
+    testUsingContext('loads blank when stamp file is malformed JSON', () async {
+      fakeData(mockProcessManager, mockCache, stampJson: '<');
       _expectDefault(await VersionCheckStamp.load());
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('loads blank when stamp file is well-formed but invalid JSON', () async {
-      fakeData(stampJson: '[]');
+    testUsingContext('loads blank when stamp file is well-formed but invalid JSON', () async {
+      fakeData(mockProcessManager, mockCache, stampJson: '[]');
       _expectDefault(await VersionCheckStamp.load());
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('loads valid JSON', () async {
-      fakeData(stampJson: '''
+    testUsingContext('loads valid JSON', () async {
+      fakeData(mockProcessManager, mockCache, stampJson: '''
       {
         "lastKnownRemoteVersion": "${_testClock.ago(days: 1)}",
         "lastTimeVersionWasChecked": "${_testClock.ago(days: 2)}",
@@ -210,10 +292,14 @@
       expect(stamp.lastKnownRemoteVersion, _testClock.ago(days: 1));
       expect(stamp.lastTimeVersionWasChecked, _testClock.ago(days: 2));
       expect(stamp.lastTimeWarningWasPrinted, _testClock.now());
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('stores version stamp', () async {
-      fakeData(expectSetStamp: true);
+    testUsingContext('stores version stamp', () async {
+      fakeData(mockProcessManager, mockCache, expectSetStamp: true);
 
       _expectDefault(await VersionCheckStamp.load());
 
@@ -228,10 +314,14 @@
       expect(storedStamp.lastKnownRemoteVersion, _testClock.ago(days: 1));
       expect(storedStamp.lastTimeVersionWasChecked, _testClock.ago(days: 2));
       expect(storedStamp.lastTimeWarningWasPrinted, _testClock.now());
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
 
-    testFlutterVersion('overwrites individual fields', () async {
-      fakeData(expectSetStamp: true);
+    testUsingContext('overwrites individual fields', () async {
+      fakeData(mockProcessManager, mockCache, expectSetStamp: true);
 
       _expectDefault(await VersionCheckStamp.load());
 
@@ -250,6 +340,10 @@
       expect(storedStamp.lastKnownRemoteVersion, _testClock.ago(days: 1));
       expect(storedStamp.lastTimeVersionWasChecked, _testClock.ago(days: 2));
       expect(storedStamp.lastTimeWarningWasPrinted, _testClock.now());
+    }, overrides: <Type, Generator>{
+      FlutterVersion: () => new FlutterVersion(_testClock),
+      ProcessManager: () => mockProcessManager,
+      Cache: () => mockCache,
     });
   });
 }
@@ -260,17 +354,9 @@
   logger.clear();
 }
 
-void testFlutterVersion(String description, dynamic testMethod()) {
-  testUsingContext(
-    description,
-    testMethod,
-    overrides: <Type, Generator>{
-      FlutterVersion: () => new FlutterVersion(_testClock),
-    },
-  );
-}
-
-void fakeData({
+void fakeData(
+  ProcessManager pm,
+  Cache cache, {
   DateTime localCommitDate,
   DateTime remoteCommitDate,
   VersionCheckStamp stamp,
@@ -279,12 +365,6 @@
   bool expectSetStamp: false,
   bool expectServerPing: false,
 }) {
-  final MockProcessManager pm = new MockProcessManager();
-  context.setVariable(ProcessManager, pm);
-
-  final MockCache cache = new MockCache();
-  context.setVariable(Cache, cache);
-
   ProcessResult success(String standardOutput) {
     return new ProcessResult(1, 0, standardOutput, '');
   }