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, '');
}