Commit 3ba17136 authored by Devon Carew's avatar Devon Carew

add a restart command to the daemon protocol (#4385)

* refactor the --resident run option into a separate file

* update daemon to run --resident apps

* re-plumbing daemon start

* send app logs

* update tests

* review changes

* fix test runner

* remove PackageMap.createGlobalInstance; rely on the ctor

* review comments
parent 68ba5bfd
...@@ -6,7 +6,7 @@ import 'dart:async'; ...@@ -6,7 +6,7 @@ import 'dart:async';
final AppContext _defaultContext = new AppContext(); final AppContext _defaultContext = new AppContext();
typedef void ErrorHandler(dynamic error); typedef void ErrorHandler(dynamic error, StackTrace stackTrace);
/// A singleton for application functionality. This singleton can be different /// A singleton for application functionality. This singleton can be different
/// on a per-Zone basis. /// on a per-Zone basis.
...@@ -17,6 +17,7 @@ AppContext get context { ...@@ -17,6 +17,7 @@ AppContext get context {
class AppContext { class AppContext {
Map<Type, dynamic> _instances = <Type, dynamic>{}; Map<Type, dynamic> _instances = <Type, dynamic>{};
Zone _zone;
bool isSet(Type type) { bool isSet(Type type) {
if (_instances.containsKey(type)) if (_instances.containsKey(type))
...@@ -30,7 +31,7 @@ class AppContext { ...@@ -30,7 +31,7 @@ class AppContext {
if (_instances.containsKey(type)) if (_instances.containsKey(type))
return _instances[type]; return _instances[type];
AppContext parent = _calcParent(Zone.current); AppContext parent = _calcParent(_zone ?? Zone.current);
return parent?.getVariable(type); return parent?.getVariable(type);
} }
...@@ -58,11 +59,22 @@ class AppContext { ...@@ -58,11 +59,22 @@ class AppContext {
} }
} }
dynamic runInZone(dynamic method(), { ErrorHandler onError }) { dynamic runInZone(dynamic method(), {
ZoneBinaryCallback<dynamic, dynamic, StackTrace> onError
}) {
return runZoned( return runZoned(
method, () => _run(method),
zoneValues: <String, dynamic>{ 'context': this }, zoneValues: <String, dynamic>{ 'context': this },
onError: onError onError: onError
); );
} }
dynamic _run(dynamic method()) async {
try {
_zone = Zone.current;
return await method();
} finally {
_zone = null;
}
}
} }
...@@ -214,7 +214,6 @@ class AnsiTerminal { ...@@ -214,7 +214,6 @@ class AnsiTerminal {
String writeBold(String str) => supportsColor ? '$_bold$str$_reset' : str; String writeBold(String str) => supportsColor ? '$_bold$str$_reset' : str;
set singleCharMode(bool value) { set singleCharMode(bool value) {
stdin.echoMode = !value;
stdin.lineMode = !value; stdin.lineMode = !value;
} }
......
...@@ -78,6 +78,13 @@ String getSizeAsMB(int bytesLength) { ...@@ -78,6 +78,13 @@ String getSizeAsMB(int bytesLength) {
return '${(bytesLength / (1024 * 1024)).toStringAsFixed(1)}MB'; return '${(bytesLength / (1024 * 1024)).toStringAsFixed(1)}MB';
} }
/// Return a relative path if [fullPath] is contained by the cwd, else return an
/// absolute path.
String getDisplayPath(String fullPath) {
String cwd = Directory.current.path + Platform.pathSeparator;
return fullPath.startsWith(cwd) ? fullPath.substring(cwd.length) : fullPath;
}
/// A class to maintain a list of items, fire events when items are added or /// A class to maintain a list of items, fire events when items are added or
/// removed, and calculate a diff of changes when a new list of items is /// removed, and calculate a diff of changes when a new list of items is
/// available. /// available.
......
...@@ -22,6 +22,16 @@ enum BuildMode { ...@@ -22,6 +22,16 @@ enum BuildMode {
String getModeName(BuildMode mode) => getEnumName(mode); String getModeName(BuildMode mode) => getEnumName(mode);
BuildMode getBuildModeForName(String mode) {
if (mode == 'debug')
return BuildMode.debug;
if (mode == 'profile')
return BuildMode.profile;
if (mode == 'release')
return BuildMode.release;
return null;
}
// Returns true if the selected build mode uses ahead-of-time compilation. // Returns true if the selected build mode uses ahead-of-time compilation.
bool isAotBuildMode(BuildMode mode) { bool isAotBuildMode(BuildMode mode) {
return mode == BuildMode.profile || mode == BuildMode.release; return mode == BuildMode.profile || mode == BuildMode.release;
......
...@@ -13,8 +13,8 @@ import '../base/utils.dart'; ...@@ -13,8 +13,8 @@ import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../dart/sdk.dart'; import '../dart/sdk.dart';
import '../globals.dart'; import '../globals.dart';
import '../run.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import 'run.dart';
const String _kDefaultAotOutputDir = 'build/aot'; const String _kDefaultAotOutputDir = 'build/aot';
......
...@@ -10,17 +10,17 @@ import 'package:path/path.dart' as path; ...@@ -10,17 +10,17 @@ import 'package:path/path.dart' as path;
import '../android/android_sdk.dart'; import '../android/android_sdk.dart';
import '../base/file_system.dart' show ensureDirectoryExists; import '../base/file_system.dart' show ensureDirectoryExists;
import '../base/os.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/os.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../flx.dart' as flx; import '../flx.dart' as flx;
import '../globals.dart'; import '../globals.dart';
import '../run.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../services.dart'; import '../services.dart';
import 'build_aot.dart'; import 'build_aot.dart';
import 'run.dart';
export '../android/android_device.dart' show AndroidDevice; export '../android/android_device.dart' show AndroidDevice;
......
...@@ -20,6 +20,7 @@ import '../dart/sdk.dart'; ...@@ -20,6 +20,7 @@ import '../dart/sdk.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import '../ios/simulators.dart' show SimControl, IOSSimulatorUtils; import '../ios/simulators.dart' show SimControl, IOSSimulatorUtils;
import '../run.dart';
import 'build_apk.dart' as build_apk; import 'build_apk.dart' as build_apk;
import 'run.dart'; import 'run.dart';
......
...@@ -11,8 +11,8 @@ import '../base/process.dart'; ...@@ -11,8 +11,8 @@ import '../base/process.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../flx.dart' as flx; import '../flx.dart' as flx;
import '../globals.dart'; import '../globals.dart';
import '../run.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import 'run.dart';
const String _kDefaultBundlePath = 'build/app.flx'; const String _kDefaultBundlePath = 'build/app.flx';
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import '../application_package.dart'; import '../application_package.dart';
import '../build_info.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
...@@ -23,6 +24,11 @@ class StopCommand extends FlutterCommand { ...@@ -23,6 +24,11 @@ class StopCommand extends FlutterCommand {
Future<int> runInProject() async { Future<int> runInProject() async {
Device device = deviceForCommand; Device device = deviceForCommand;
ApplicationPackage app = applicationPackages.getPackageForPlatform(device.platform); ApplicationPackage app = applicationPackages.getPackageForPlatform(device.platform);
if (app == null) {
String platformName = getNameForTargetPlatform(device.platform);
printError('No Flutter application for $platformName found in the current directory.');
return 1;
}
printStatus('Stopping apps on ${device.name}.'); printStatus('Stopping apps on ${device.name}.');
return await device.stopApp(app) ? 0 : 1; return await device.stopApp(app) ? 0 : 1;
} }
......
...@@ -8,6 +8,7 @@ import 'dart:io'; ...@@ -8,6 +8,7 @@ import 'dart:io';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:test/src/executable.dart' as executable; // ignore: implementation_imports import 'package:test/src/executable.dart' as executable; // ignore: implementation_imports
import '../dart/package_map.dart';
import '../globals.dart'; import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../test/flutter_platform.dart' as loader; import '../test/flutter_platform.dart' as loader;
...@@ -58,6 +59,7 @@ class TestCommand extends FlutterCommand { ...@@ -58,6 +59,7 @@ class TestCommand extends FlutterCommand {
try { try {
if (testDirectory != null) { if (testDirectory != null) {
printTrace('switching to directory $testDirectory to run tests'); printTrace('switching to directory $testDirectory to run tests');
PackageMap.globalPackagesPath = path.normalize(path.absolute(PackageMap.globalPackagesPath));
Directory.current = testDirectory; Directory.current = testDirectory;
} }
printTrace('running test package with arguments: $testArgs'); printTrace('running test package with arguments: $testArgs');
......
...@@ -147,3 +147,52 @@ class Tracing { ...@@ -147,3 +147,52 @@ class Tracing {
return timeline.response; return timeline.response;
} }
} }
/// Download the startup trace information from the given observatory client and
/// store it to build/start_up_info.json.
Future<Null> downloadStartupTrace(Observatory observatory) async {
Tracing tracing = new Tracing(observatory);
Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline(
waitForFirstFrame: true
);
int extractInstantEventTimestamp(String eventName) {
List<Map<String, dynamic>> events = timeline['traceEvents'];
Map<String, dynamic> event = events.firstWhere(
(Map<String, dynamic> event) => event['name'] == eventName, orElse: () => null
);
return event == null ? null : event['ts'];
}
int engineEnterTimestampMicros = extractInstantEventTimestamp(kFlutterEngineMainEnterEventName);
int frameworkInitTimestampMicros = extractInstantEventTimestamp(kFrameworkInitEventName);
int firstFrameTimestampMicros = extractInstantEventTimestamp(kFirstUsefulFrameEventName);
if (engineEnterTimestampMicros == null) {
printError('Engine start event is missing in the timeline. Cannot compute startup time.');
return null;
}
if (firstFrameTimestampMicros == null) {
printError('First frame event is missing in the timeline. Cannot compute startup time.');
return null;
}
File traceInfoFile = new File('build/start_up_info.json');
int timeToFirstFrameMicros = firstFrameTimestampMicros - engineEnterTimestampMicros;
Map<String, dynamic> traceInfo = <String, dynamic>{
'engineEnterTimestampMicros': engineEnterTimestampMicros,
'timeToFirstFrameMicros': timeToFirstFrameMicros,
};
if (frameworkInitTimestampMicros != null) {
traceInfo['timeToFrameworkInitMicros'] = frameworkInitTimestampMicros - engineEnterTimestampMicros;
traceInfo['timeAfterFrameworkInitMicros'] = firstFrameTimestampMicros - frameworkInitTimestampMicros;
}
traceInfoFile.writeAsStringSync(toPrettyJson(traceInfo));
printStatus('Time to first frame: ${timeToFirstFrameMicros ~/ 1000}ms.');
printStatus('Saved startup trace info in ${traceInfoFile.path}.');
}
...@@ -17,6 +17,14 @@ Map<String, Uri> _parse(String packagesPath) { ...@@ -17,6 +17,14 @@ Map<String, Uri> _parse(String packagesPath) {
class PackageMap { class PackageMap {
PackageMap(this.packagesPath); PackageMap(this.packagesPath);
static String get globalPackagesPath => _globalPackagesPath ?? kPackagesFileName;
static set globalPackagesPath(String value) {
_globalPackagesPath = value;
}
static String _globalPackagesPath;
final String packagesPath; final String packagesPath;
Map<String, Uri> get map { Map<String, Uri> get map {
...@@ -26,8 +34,6 @@ class PackageMap { ...@@ -26,8 +34,6 @@ class PackageMap {
} }
Map<String, Uri> _map; Map<String, Uri> _map;
static PackageMap instance;
String checkValid() { String checkValid() {
if (FileSystemEntity.isFileSync(packagesPath)) if (FileSystemEntity.isFileSync(packagesPath))
return null; return null;
......
...@@ -321,7 +321,7 @@ abstract class DevicePortForwarder { ...@@ -321,7 +321,7 @@ abstract class DevicePortForwarder {
/// Forward [hostPort] on the host to [devicePort] on the device. /// Forward [hostPort] on the host to [devicePort] on the device.
/// If [hostPort] is null, will auto select a host port. /// If [hostPort] is null, will auto select a host port.
/// Returns a Future that completes with the host port. /// Returns a Future that completes with the host port.
Future<int> forward(int devicePort, { int hostPort: null }); Future<int> forward(int devicePort, { int hostPort });
/// Stops forwarding [forwardedPort]. /// Stops forwarding [forwardedPort].
Future<Null> unforward(ForwardedPort forwardedPort); Future<Null> unforward(ForwardedPort forwardedPort);
......
...@@ -46,7 +46,7 @@ Future<int> createSnapshot({ ...@@ -46,7 +46,7 @@ Future<int> createSnapshot({
final List<String> args = <String>[ final List<String> args = <String>[
tools.getHostToolPath(HostTool.SkySnapshot), tools.getHostToolPath(HostTool.SkySnapshot),
mainPath, mainPath,
'--packages=${PackageMap.instance.packagesPath}', '--packages=${path.absolute(PackageMap.globalPackagesPath)}',
'--snapshot=$snapshotPath' '--snapshot=$snapshotPath'
]; ];
if (depfilePath != null) if (depfilePath != null)
......
// Copyright 2016 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 'dart:io';
import 'package:path/path.dart' as path;
import 'application_package.dart';
import 'base/logger.dart';
import 'base/utils.dart';
import 'build_info.dart';
import 'commands/build_apk.dart';
import 'commands/install.dart';
import 'commands/trace.dart';
import 'device.dart';
import 'globals.dart';
import 'observatory.dart';
/// Given the value of the --target option, return the path of the Dart file
/// where the app's main function should be.
String findMainDartFile([String target]) {
if (target == null)
target = '';
String targetPath = path.absolute(target);
if (FileSystemEntity.isDirectorySync(targetPath))
return path.join(targetPath, 'lib', 'main.dart');
else
return targetPath;
}
// TODO: split out the cli part of the UI from this class
class RunAndStayResident {
RunAndStayResident(
this.device, {
this.target,
this.debuggingOptions,
this.usesTerminalUI: true
});
final Device device;
final String target;
final DebuggingOptions debuggingOptions;
final bool usesTerminalUI;
ApplicationPackage _package;
String _mainPath;
LaunchResult _result;
Completer<int> _exitCompleter = new Completer<int>();
StreamSubscription<String> _loggingSubscription;
Observatory observatory;
/// Start the app and keep the process running during its lifetime.
Future<int> run({
bool traceStartup: false,
bool benchmark: false,
Completer<int> observatoryPortCompleter
}) {
// Don't let uncaught errors kill the process.
return runZoned(() {
return _run(
traceStartup: traceStartup,
benchmark: benchmark,
observatoryPortCompleter: observatoryPortCompleter
);
}, onError: (dynamic error, StackTrace stackTrace) {
printError('Exception from flutter run: $error', stackTrace);
});
}
Future<bool> restart() async {
if (observatory == null) {
printError('Debugging is not enabled.');
return false;
} else {
Status status = logger.startProgress('Re-starting application...');
Future<Event> extensionAddedEvent = observatory.onExtensionEvent
.where((Event event) => event.extensionKind == 'Flutter.FrameworkInitialization')
.first;
bool restartResult = await device.restartApp(
_package,
_result,
mainPath: _mainPath,
observatory: observatory
);
status.stop(showElapsedTime: true);
if (restartResult) {
// TODO(devoncarew): We should restore the route here.
await extensionAddedEvent;
}
return restartResult;
}
}
Future<Null> stop() {
_stopLogger();
return _stopApp();
}
Future<int> _run({
bool traceStartup: false,
bool benchmark: false,
Completer<int> observatoryPortCompleter
}) async {
_mainPath = findMainDartFile(target);
if (!FileSystemEntity.isFileSync(_mainPath)) {
String message = 'Tried to run $_mainPath, but that file does not exist.';
if (target == null)
message += '\nConsider using the -t option to specify the Dart file to start.';
printError(message);
return 1;
}
_package = getApplicationPackageForPlatform(device.platform);
if (_package == null) {
String message = 'No application found for ${device.platform}.';
String hint = getMissingPackageHintForPlatform(device.platform);
if (hint != null)
message += '\n$hint';
printError(message);
return 1;
}
Stopwatch startTime = new Stopwatch()..start();
// TODO(devoncarew): We shouldn't have to do type checks here.
if (device is AndroidDevice) {
printTrace('Running build command.');
int result = await buildApk(
device.platform,
target: target,
buildMode: debuggingOptions.buildMode
);
if (result != 0)
return result;
}
// TODO(devoncarew): Move this into the device.startApp() impls.
if (_package != null) {
printTrace("Stopping app '${_package.name}' on ${device.name}.");
// We don't wait for the stop command to complete.
device.stopApp(_package);
}
// Allow any stop commands from above to start work.
await new Future<Duration>.delayed(Duration.ZERO);
// TODO(devoncarew): This fails for ios devices - we haven't built yet.
if (device is AndroidDevice) {
printTrace('Running install command.');
if (!(installApp(device, _package)))
return 1;
}
Map<String, dynamic> platformArgs;
if (traceStartup != null)
platformArgs = <String, dynamic>{ 'trace-startup': traceStartup };
printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...');
_loggingSubscription = device.logReader.logLines.listen((String line) {
if (!line.contains('Observatory listening on http') && !line.contains('Diagnostic server listening on http'))
printStatus(line);
});
_result = await device.startApp(
_package,
debuggingOptions.buildMode,
mainPath: _mainPath,
debuggingOptions: debuggingOptions,
platformArgs: platformArgs
);
if (!_result.started) {
printError('Error running application on ${device.name}.');
await _loggingSubscription.cancel();
return 2;
}
startTime.stop();
if (observatoryPortCompleter != null && _result.hasObservatory)
observatoryPortCompleter.complete(_result.observatoryPort);
// Connect to observatory.
if (debuggingOptions.debuggingEnabled) {
observatory = await Observatory.connect(_result.observatoryPort);
printTrace('Connected to observatory port: ${_result.observatoryPort}.');
observatory.populateIsolateInfo();
observatory.onExtensionEvent.listen((Event event) {
printTrace(event.toString());
});
observatory.onIsolateEvent.listen((Event event) {
printTrace(event.toString());
});
if (benchmark)
await observatory.waitFirstIsolate;
// Listen for observatory connection close.
observatory.done.whenComplete(() {
if (!_exitCompleter.isCompleted) {
printStatus('Application finished.');
_exitCompleter.complete(0);
}
});
}
printStatus('Application running.');
if (observatory != null && traceStartup) {
printStatus('Downloading startup trace info...');
await downloadStartupTrace(observatory);
if (!_exitCompleter.isCompleted)
_exitCompleter.complete(0);
} else {
if (usesTerminalUI) {
if (!logger.quiet)
_printHelp();
terminal.singleCharMode = true;
terminal.onCharInput.listen((String code) {
String lower = code.toLowerCase();
if (lower == 'h' || code == AnsiTerminal.KEY_F1) {
// F1, help
_printHelp();
} else if (lower == 'r' || code == AnsiTerminal.KEY_F5) {
// F5, restart
restart();
} else if (lower == 'q' || code == AnsiTerminal.KEY_F10) {
// F10, exit
_stopApp();
}
});
}
ProcessSignal.SIGINT.watch().listen((ProcessSignal signal) {
_resetTerminal();
_stopLogger();
_stopApp();
});
ProcessSignal.SIGTERM.watch().listen((ProcessSignal signal) {
_resetTerminal();
_stopLogger();
_stopApp();
});
}
if (benchmark) {
await new Future<Null>.delayed(new Duration(seconds: 4));
// Touch the file.
File mainFile = new File(_mainPath);
mainFile.writeAsBytesSync(mainFile.readAsBytesSync());
Stopwatch restartTime = new Stopwatch()..start();
bool restarted = await restart();
restartTime.stop();
writeRunBenchmarkFile(startTime, restarted ? restartTime : null);
await new Future<Null>.delayed(new Duration(seconds: 2));
stop();
}
return _exitCompleter.future.then((int exitCode) async {
_resetTerminal();
_stopLogger();
return exitCode;
});
}
void _printHelp() {
printStatus('Type "h" or F1 for help, "r" or F5 to restart the app, and "q", F10, or ctrl-c to quit.');
}
void _stopLogger() {
_loggingSubscription?.cancel();
}
void _resetTerminal() {
if (usesTerminalUI)
terminal.singleCharMode = false;
}
Future<Null> _stopApp() {
if (observatory != null && !observatory.isClosed) {
if (observatory.isolates.isNotEmpty) {
observatory.flutterExit(observatory.firstIsolateId);
return new Future<Null>.delayed(new Duration(milliseconds: 100));
}
}
if (!_exitCompleter.isCompleted)
_exitCompleter.complete(0);
return new Future<Null>.value();
}
}
String getMissingPackageHintForPlatform(TargetPlatform platform) {
switch (platform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_x64:
return 'Is your project missing an android/AndroidManifest.xml?';
case TargetPlatform.ios:
return 'Is your project missing an ios/Info.plist?';
default:
return null;
}
}
void writeRunBenchmarkFile(Stopwatch startTime, [Stopwatch restartTime]) {
final String benchmarkOut = 'refresh_benchmark.json';
Map<String, dynamic> data = <String, dynamic>{
'start': startTime.elapsedMilliseconds,
'time': (restartTime ?? startTime).elapsedMilliseconds // time and restart are the same
};
if (restartTime != null)
data['restart'] = restartTime.elapsedMilliseconds;
new File(benchmarkOut).writeAsStringSync(toPrettyJson(data));
printStatus('Run benchmark written to $benchmarkOut ($data).');
}
...@@ -193,7 +193,7 @@ abstract class FlutterCommand extends Command { ...@@ -193,7 +193,7 @@ abstract class FlutterCommand extends Command {
// Validate the current package map only if we will not be running "pub get" later. // Validate the current package map only if we will not be running "pub get" later.
if (!(_usesPubOption && argResults['pub'])) { if (!(_usesPubOption && argResults['pub'])) {
String error = PackageMap.instance.checkValid(); String error = new PackageMap(PackageMap.globalPackagesPath).checkValid();
if (error != null) { if (error != null) {
printError(error); printError(error);
return false; return false;
......
...@@ -148,9 +148,8 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -148,9 +148,8 @@ class FlutterCommandRunner extends CommandRunner {
if (!_checkFlutterCopy()) if (!_checkFlutterCopy())
return new Future<int>.value(1); return new Future<int>.value(1);
PackageMap.instance = new PackageMap(path.normalize(path.absolute( if (globalResults.wasParsed('packages'))
globalResults.wasParsed('packages') ? globalResults['packages'] : kPackagesFileName PackageMap.globalPackagesPath = path.normalize(path.absolute(globalResults['packages']));
)));
// See if the user specified a specific device. // See if the user specified a specific device.
deviceManager.specifiedDeviceId = globalResults['device-id']; deviceManager.specifiedDeviceId = globalResults['device-id'];
...@@ -191,7 +190,7 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -191,7 +190,7 @@ class FlutterCommandRunner extends CommandRunner {
if (engineSourcePath == null && globalResults['local-engine'] != null) { if (engineSourcePath == null && globalResults['local-engine'] != null) {
try { try {
Uri engineUri = PackageMap.instance.map[kFlutterEnginePackageName]; Uri engineUri = new PackageMap(PackageMap.globalPackagesPath).map[kFlutterEnginePackageName];
engineSourcePath = path.dirname(path.dirname(path.dirname(path.dirname(engineUri.path)))); engineSourcePath = path.dirname(path.dirname(path.dirname(path.dirname(engineUri.path))));
bool dirExists = FileSystemEntity.isDirectorySync(path.join(engineSourcePath, 'out')); bool dirExists = FileSystemEntity.isDirectorySync(path.join(engineSourcePath, 'out'));
if (engineSourcePath == '/' || engineSourcePath.isEmpty || !dirExists) if (engineSourcePath == '/' || engineSourcePath.isEmpty || !dirExists)
......
...@@ -30,7 +30,7 @@ Future<Null> parseServiceConfigs( ...@@ -30,7 +30,7 @@ Future<Null> parseServiceConfigs(
) async { ) async {
Map<String, Uri> packageMap; Map<String, Uri> packageMap;
try { try {
packageMap = PackageMap.instance.map; packageMap = new PackageMap(PackageMap.globalPackagesPath).map;
} on FormatException catch(e) { } on FormatException catch(e) {
printTrace('Invalid ".packages" file while parsing service configs:\n$e'); printTrace('Invalid ".packages" file while parsing service configs:\n$e');
return; return;
......
...@@ -15,6 +15,7 @@ import 'package:test/src/runner/plugin/platform.dart'; // ignore: implementation ...@@ -15,6 +15,7 @@ import 'package:test/src/runner/plugin/platform.dart'; // ignore: implementation
import 'package:test/src/runner/plugin/hack_register_platform.dart' as hack; // ignore: implementation_imports import 'package:test/src/runner/plugin/hack_register_platform.dart' as hack; // ignore: implementation_imports
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../globals.dart';
final String _kSkyShell = Platform.environment['SKY_SHELL']; final String _kSkyShell = Platform.environment['SKY_SHELL'];
const String _kHost = '127.0.0.1'; const String _kHost = '127.0.0.1';
...@@ -46,12 +47,15 @@ Future<_ServerInfo> _startServer() async { ...@@ -46,12 +47,15 @@ Future<_ServerInfo> _startServer() async {
Future<Process> _startProcess(String mainPath, { String packages }) { Future<Process> _startProcess(String mainPath, { String packages }) {
assert(shellPath != null || _kSkyShell != null); // Please provide the path to the shell in the SKY_SHELL environment variable. assert(shellPath != null || _kSkyShell != null); // Please provide the path to the shell in the SKY_SHELL environment variable.
return Process.start(shellPath ?? _kSkyShell, <String>[ String executable = shellPath ?? _kSkyShell;
List<String> arguments = <String>[
'--enable-checked-mode', '--enable-checked-mode',
'--non-interactive', '--non-interactive',
'--packages=$packages', '--packages=$packages',
mainPath, mainPath
], environment: <String, String>{ 'FLUTTER_TEST': 'true' }); ];
printTrace('$executable ${arguments.join(' ')}');
return Process.start(executable, arguments, environment: <String, String>{ 'FLUTTER_TEST': 'true' });
} }
void _attachStandardStreams(Process process) { void _attachStandardStreams(Process process) {
...@@ -102,7 +106,7 @@ void main() { ...@@ -102,7 +106,7 @@ void main() {
'''); ''');
Process process = await _startProcess( Process process = await _startProcess(
listenerFile.path, packages: PackageMap.instance.packagesPath listenerFile.path, packages: PackageMap.globalPackagesPath
); );
_attachStandardStreams(process); _attachStandardStreams(process);
......
...@@ -14,7 +14,7 @@ void main() { ...@@ -14,7 +14,7 @@ void main() {
BufferLogger mockLogger = new BufferLogger(); BufferLogger mockLogger = new BufferLogger();
context[Logger] = mockLogger; context[Logger] = mockLogger;
context.runInZone(() { await context.runInZone(() {
printError('foo bar'); printError('foo bar');
}); });
...@@ -28,7 +28,7 @@ void main() { ...@@ -28,7 +28,7 @@ void main() {
BufferLogger mockLogger = new BufferLogger(); BufferLogger mockLogger = new BufferLogger();
context[Logger] = mockLogger; context[Logger] = mockLogger;
context.runInZone(() { await context.runInZone(() {
printStatus('foo bar'); printStatus('foo bar');
}); });
...@@ -42,7 +42,7 @@ void main() { ...@@ -42,7 +42,7 @@ void main() {
BufferLogger mockLogger = new BufferLogger(); BufferLogger mockLogger = new BufferLogger();
context[Logger] = mockLogger; context[Logger] = mockLogger;
context.runInZone(() { await context.runInZone(() {
printTrace('foo bar'); printTrace('foo bar');
}); });
......
...@@ -94,6 +94,44 @@ void main() { ...@@ -94,6 +94,44 @@ void main() {
}); });
}); });
_testUsingContext('daemon.start', () async {
DaemonCommand command = new DaemonCommand();
applyMocksToCommand(command);
StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
daemon = new Daemon(
commands.stream,
(Map<String, dynamic> result) => responses.add(result),
daemonCommand: command,
notifyingLogger: notifyingLogger
);
commands.add(<String, dynamic>{ 'id': 0, 'method': 'app.start' });
Map<String, dynamic> response = await responses.stream.where(_notEvent).first;
expect(response['id'], 0);
expect(response['error'], contains('deviceId is required'));
});
_testUsingContext('daemon.restart', () async {
DaemonCommand command = new DaemonCommand();
applyMocksToCommand(command);
StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
daemon = new Daemon(
commands.stream,
(Map<String, dynamic> result) => responses.add(result),
daemonCommand: command,
notifyingLogger: notifyingLogger
);
commands.add(<String, dynamic>{ 'id': 0, 'method': 'app.restart' });
Map<String, dynamic> response = await responses.stream.where(_notEvent).first;
expect(response['id'], 0);
expect(response['error'], contains('appId is required'));
});
_testUsingContext('daemon.stop', () async { _testUsingContext('daemon.stop', () async {
DaemonCommand command = new DaemonCommand(); DaemonCommand command = new DaemonCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
...@@ -110,7 +148,7 @@ void main() { ...@@ -110,7 +148,7 @@ void main() {
commands.add(<String, dynamic>{ 'id': 0, 'method': 'app.stop' }); commands.add(<String, dynamic>{ 'id': 0, 'method': 'app.stop' });
Map<String, dynamic> response = await responses.stream.where(_notEvent).first; Map<String, dynamic> response = await responses.stream.where(_notEvent).first;
expect(response['id'], 0); expect(response['id'], 0);
expect(response['error'], contains('deviceId is required')); expect(response['error'], contains('appId is required'));
}); });
_testUsingContext('device.getDevices', () async { _testUsingContext('device.getDevices', () async {
......
...@@ -12,7 +12,7 @@ Process daemon; ...@@ -12,7 +12,7 @@ Process daemon;
// version: print version // version: print version
// shutdown: terminate the server // shutdown: terminate the server
// start: start an app // start: start an app
// stopAll: stop any running app // stop: stop a running app
// devices: list devices // devices: list devices
Future<Null> main() async { Future<Null> main() async {
...@@ -27,18 +27,44 @@ Future<Null> main() async { ...@@ -27,18 +27,44 @@ Future<Null> main() async {
stdout.write('> '); stdout.write('> ');
stdin.transform(UTF8.decoder).transform(const LineSplitter()).listen((String line) { stdin.transform(UTF8.decoder).transform(const LineSplitter()).listen((String line) {
List<String> words = line.split(' ');
if (line == 'version' || line == 'v') { if (line == 'version' || line == 'v') {
_send(<String, dynamic>{'method': 'daemon.version'}); _send(<String, dynamic>{'method': 'daemon.version'});
} else if (line == 'shutdown' || line == 'q') { } else if (line == 'shutdown' || line == 'q') {
_send(<String, dynamic>{'method': 'daemon.shutdown'}); _send(<String, dynamic>{'method': 'daemon.shutdown'});
} else if (line == 'start') { } else if (words.first == 'start') {
_send(<String, dynamic>{'method': 'app.start'}); _send(<String, dynamic>{
} else if (line == 'stopAll') { 'method': 'app.start',
_send(<String, dynamic>{'method': 'app.stopAll'}); 'params': <String, dynamic> {
'deviceId': words[1],
'projectDirectory': words[2]
}
});
} else if (words.first == 'stop') {
if (words.length > 1) {
_send(<String, dynamic>{
'method': 'app.stop',
'params': <String, dynamic> { 'appId': words[1] }
});
} else {
_send(<String, dynamic>{'method': 'app.stop'});
}
} else if (words.first == 'restart') {
if (words.length > 1) {
_send(<String, dynamic>{
'method': 'app.restart',
'params': <String, dynamic> { 'appId': words[1] }
});
} else {
_send(<String, dynamic>{'method': 'app.restart'});
}
} else if (line == 'devices') { } else if (line == 'devices') {
_send(<String, dynamic>{'method': 'device.getDevices'}); _send(<String, dynamic>{'method': 'device.getDevices'});
} else if (line == 'enable') {
_send(<String, dynamic>{'method': 'device.enable'});
} else { } else {
print('command not understood: $line'); _send(<String, dynamic>{'method': line.trim()});
} }
stdout.write('> '); stdout.write('> ');
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment