Initial sketch of tools testbed (#31765)


diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart
index 70049bc..8c318bc 100644
--- a/packages/flutter_tools/test/src/mocks.dart
+++ b/packages/flutter_tools/test/src/mocks.dart
@@ -4,7 +4,7 @@
 
 import 'dart:async';
 import 'dart:convert';
-import 'dart:io' as io show IOSink;
+import 'dart:io' as io show IOSink, ProcessSignal;
 
 import 'package:flutter_tools/src/android/android_device.dart';
 import 'package:flutter_tools/src/android/android_sdk.dart' show AndroidSdk;
@@ -196,6 +196,38 @@
   final Stream<List<int>> stderr;
 }
 
+/// A fake process implemenation which can be provided all necessary values.
+class FakeProcess implements Process {
+  FakeProcess({
+    this.pid = 1,
+    Future<int> exitCode,
+    Stream<List<int>> stdin,
+    this.stdout = const Stream<List<int>>.empty(),
+    this.stderr = const Stream<List<int>>.empty(),
+  }) : exitCode = exitCode ?? Future<int>.value(0),
+       stdin = stdin ?? MemoryIOSink();
+
+  @override
+  final int pid;
+
+  @override
+  final Future<int> exitCode;
+
+  @override
+  final io.IOSink stdin;
+
+  @override
+  final Stream<List<int>> stdout;
+
+  @override
+  final Stream<List<int>> stderr;
+
+  @override
+  bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
+    return true;
+  }
+}
+
 /// A process that prompts the user to proceed, then asynchronously writes
 /// some lines to stdout before it exits.
 class PromptingProcess implements Process {
diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart
new file mode 100644
index 0000000..9582152
--- /dev/null
+++ b/packages/flutter_tools/test/src/testbed.dart
@@ -0,0 +1,86 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/context_runner.dart';
+
+export 'package:flutter_tools/src/base/context.dart' show Generator;
+
+final Map<Type, Generator> _testbedDefaults = <Type, Generator>{
+  FileSystem: () => MemoryFileSystem(),
+  Logger: () => BufferLogger(),
+};
+
+/// Manages interaction with the tool injection and runner system.
+///
+/// The Testbed automatically injects reasonable defaults through the context
+/// DI system such as a [BufferLogger] and a [MemoryFileSytem].
+///
+/// Example:
+///
+/// Testing that a filesystem operation works as expected
+///
+///     void main() {
+///       group('Example', () {
+///         Testbed testbed;
+///
+///         setUp(() {
+///           testbed = Testbed(setUp: () {
+///             fs.file('foo').createSync()
+///           });
+///         })
+///
+///         test('Can delete a file', () => testBed.run(() {
+///           expect(fs.file('foo').existsSync(), true);
+///           fs.file('foo').deleteSync();
+///           expect(fs.file('foo').existsSync(), false);
+///         }));
+///       });
+///     }
+///
+/// For a more detailed example, see the code in test_compiler_test.dart.
+class Testbed {
+  /// Creates a new [TestBed]
+  ///
+  /// `overrides` provides more overrides in addition to the test defaults.
+  /// `setup` may be provided to apply mocks within the tool managed zone,
+  /// including any specified overrides.
+  Testbed({Future<void> Function() setup, Map<Type, Generator> overrides})
+    : _setup = setup,
+      _overrides = overrides;
+
+
+  final Future<void> Function() _setup;
+  final Map<Type, Generator> _overrides;
+
+  /// Runs `test` within a tool zone.
+  FutureOr<T> run<T>(FutureOr<T> Function() test) {
+    final Map<Type, Generator> testOverrides = Map<Type, Generator>.from(_testbedDefaults);
+    if (_overrides != null) {
+      testOverrides.addAll(_overrides);
+    }
+    // Cache the original flutter root to restore after the test case.
+    final String originalFlutterRoot = Cache.flutterRoot;
+    return runInContext<T>(() {
+      return context.run<T>(
+        name: 'testbed',
+        overrides: testOverrides,
+        body: () async {
+          Cache.flutterRoot = '';
+          if (_setup != null) {
+            await _setup();
+          }
+          await test();
+          Cache.flutterRoot = originalFlutterRoot;
+        }
+      );
+    });
+  }
+}
\ No newline at end of file