Commit b0dca796 authored by Devon Carew's avatar Devon Carew

Flutter run (#3553)

* rework flutter run

* fix npe with --debug-port

* connect to obs and exit when that conneciton closes

* update todos
parent cbe650a7
...@@ -2,5 +2,6 @@ ...@@ -2,5 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
const int observatoryDefaultPort = 8181; const int defaultObservatoryPort = 8100;
const int diagnosticDefaultPort = 8182; const int defaultDiagnosticPort = 8101;
const int defaultDrivePort = 8183;
...@@ -109,3 +109,30 @@ Future<int> findAvailablePort() async { ...@@ -109,3 +109,30 @@ Future<int> findAvailablePort() async {
await socket.close(); await socket.close();
return port; return port;
} }
const int _kMaxSearchIterations = 5;
/// This method will attempt to return a port close to or the same as
/// [defaultPort]. Failing that, it will return any available port.
Future<int> findPreferredPort(int defaultPort, { int searchStep: 2 }) async {
int iterationCount = 0;
while (iterationCount < _kMaxSearchIterations) {
int port = defaultPort + iterationCount * searchStep;
if (await _isPortAvailable(port))
return port;
iterationCount++;
}
return findAvailablePort();
}
Future<bool> _isPortAvailable(int port) async {
try {
ServerSocket socket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, port);
await socket.close();
return true;
} catch (error) {
return false;
}
}
...@@ -267,8 +267,7 @@ class AppDomain extends Domain { ...@@ -267,8 +267,7 @@ class AppDomain extends Domain {
command.toolchain, command.toolchain,
stop: true, stop: true,
target: args['target'], target: args['target'],
route: args['route'], route: args['route']
checked: args['checked'] ?? true
); );
if (result != 0) if (result != 0)
......
...@@ -11,6 +11,7 @@ import 'package:test/src/executable.dart' as executable; // ignore: implementati ...@@ -11,6 +11,7 @@ import 'package:test/src/executable.dart' as executable; // ignore: implementati
import '../android/android_device.dart' show AndroidDevice; import '../android/android_device.dart' show AndroidDevice;
import '../application_package.dart'; import '../application_package.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/common.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
...@@ -60,8 +61,9 @@ class DriveCommand extends RunCommandBase { ...@@ -60,8 +61,9 @@ class DriveCommand extends RunCommandBase {
); );
argParser.addOption('debug-port', argParser.addOption('debug-port',
defaultsTo: '8183', defaultsTo: defaultDrivePort.toString(),
help: 'Listen to the given port for a debug connection.'); help: 'Listen to the given port for a debug connection.'
);
} }
@override @override
...@@ -261,25 +263,22 @@ Future<int> startApp(DriveCommand command) async { ...@@ -261,25 +263,22 @@ Future<int> startApp(DriveCommand command) async {
await command.device.installApp(package); await command.device.installApp(package);
printTrace('Starting application.'); printTrace('Starting application.');
bool started = await command.device.startApp( LaunchResult result = await command.device.startApp(
package, package,
command.toolchain, command.toolchain,
mainPath: mainPath, mainPath: mainPath,
route: command.route, route: command.route,
checked: command.checked, debuggingOptions: new DebuggingOptions.enabled(
clearLogs: true, checked: command.checked,
startPaused: true, startPaused: true,
observatoryPort: command.debugPort, observatoryPort: command.debugPort
),
platformArgs: <String, dynamic>{ platformArgs: <String, dynamic>{
'trace-startup': command.traceStartup, 'trace-startup': command.traceStartup,
} }
); );
if (started && command.device.supportsStartPaused) { return result.started ? 0 : 2;
await delayUntilObservatoryAvailable('localhost', command.debugPort);
}
return started ? 0 : 2;
} }
/// Runs driver tests. /// Runs driver tests.
......
...@@ -7,6 +7,7 @@ import 'dart:io'; ...@@ -7,6 +7,7 @@ import 'dart:io';
import '../base/os.dart'; import '../base/os.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import 'run.dart'; import 'run.dart';
...@@ -62,7 +63,7 @@ class ListenCommand extends RunCommandBase { ...@@ -62,7 +63,7 @@ class ListenCommand extends RunCommandBase {
target: target, target: target,
install: firstTime, install: firstTime,
stop: true, stop: true,
checked: checked, debuggingOptions: new DebuggingOptions.enabled(checked: checked),
traceStartup: traceStartup, traceStartup: traceStartup,
route: route route: route
); );
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:io';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
...@@ -40,17 +41,33 @@ class LogsCommand extends FlutterCommand { ...@@ -40,17 +41,33 @@ class LogsCommand extends FlutterCommand {
printStatus('Showing $logReader logs:'); printStatus('Showing $logReader logs:');
Completer<int> exitCompleter = new Completer<int>();
// Start reading. // Start reading.
if (!logReader.isReading) StreamSubscription<String> subscription = logReader.logLines.listen(
await logReader.start(); printStatus,
onDone: () {
exitCompleter.complete(0);
},
onError: (dynamic error) {
exitCompleter.complete(error is int ? error : 1);
}
);
StreamSubscription<String> subscription = logReader.lines.listen(printStatus); // When terminating, close down the log reader.
ProcessSignal.SIGINT.watch().listen((ProcessSignal signal) {
subscription.cancel();
printStatus('');
exitCompleter.complete(0);
});
ProcessSignal.SIGTERM.watch().listen((ProcessSignal signal) {
subscription.cancel();
exitCompleter.complete(0);
});
// Wait for the log reader to be finished. // Wait for the log reader to be finished.
int result = await logReader.finished; int result = await exitCompleter.future;
subscription.cancel(); subscription.cancel();
if (result != 0) if (result != 0)
printError('Error listening to $logReader logs.'); printError('Error listening to $logReader logs.');
return result; return result;
......
...@@ -39,7 +39,8 @@ class RefreshCommand extends FlutterCommand { ...@@ -39,7 +39,8 @@ class RefreshCommand extends FlutterCommand {
String snapshotPath = path.join(tempDir.path, 'snapshot_blob.bin'); String snapshotPath = path.join(tempDir.path, 'snapshot_blob.bin');
int result = await toolchain.compiler.createSnapshot( int result = await toolchain.compiler.createSnapshot(
mainPath: argResults['target'], snapshotPath: snapshotPath mainPath: argResults['target'],
snapshotPath: snapshotPath
); );
if (result != 0) { if (result != 0) {
printError('Failed to run the Flutter compiler. Exit code: $result'); printError('Failed to run the Flutter compiler. Exit code: $result');
......
...@@ -16,7 +16,7 @@ class SkiaCommand extends FlutterCommand { ...@@ -16,7 +16,7 @@ class SkiaCommand extends FlutterCommand {
argParser.addOption('output-file', help: 'Write the Skia picture file to this path.'); argParser.addOption('output-file', help: 'Write the Skia picture file to this path.');
argParser.addOption('skiaserve', help: 'Post the picture to a skiaserve debugger at this URL.'); argParser.addOption('skiaserve', help: 'Post the picture to a skiaserve debugger at this URL.');
argParser.addOption('diagnostic-port', argParser.addOption('diagnostic-port',
defaultsTo: diagnosticDefaultPort.toString(), defaultsTo: defaultDiagnosticPort.toString(),
help: 'Local port where the diagnostic server is listening.'); help: 'Local port where the diagnostic server is listening.');
} }
......
...@@ -18,7 +18,7 @@ class TraceCommand extends FlutterCommand { ...@@ -18,7 +18,7 @@ class TraceCommand extends FlutterCommand {
argParser.addOption('duration', argParser.addOption('duration',
defaultsTo: '10', abbr: 'd', help: 'Duration in seconds to trace.'); defaultsTo: '10', abbr: 'd', help: 'Duration in seconds to trace.');
argParser.addOption('debug-port', argParser.addOption('debug-port',
defaultsTo: observatoryDefaultPort.toString(), defaultsTo: defaultObservatoryPort.toString(),
help: 'Local port where the observatory is listening.'); help: 'Local port where the observatory is listening.');
} }
......
...@@ -10,6 +10,7 @@ import 'android/android_device.dart'; ...@@ -10,6 +10,7 @@ import 'android/android_device.dart';
import 'application_package.dart'; import 'application_package.dart';
import 'base/common.dart'; import 'base/common.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'base/os.dart';
import 'build_configuration.dart'; import 'build_configuration.dart';
import 'globals.dart'; import 'globals.dart';
import 'ios/devices.dart'; import 'ios/devices.dart';
...@@ -176,16 +177,12 @@ abstract class Device { ...@@ -176,16 +177,12 @@ abstract class Device {
/// ///
/// [platformArgs] allows callers to pass platform-specific arguments to the /// [platformArgs] allows callers to pass platform-specific arguments to the
/// start call. /// start call.
Future<bool> startApp( Future<LaunchResult> startApp(
ApplicationPackage package, ApplicationPackage package,
Toolchain toolchain, { Toolchain toolchain, {
String mainPath, String mainPath,
String route, String route,
bool checked: true, DebuggingOptions debuggingOptions,
bool clearLogs: false,
bool startPaused: false,
int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort,
Map<String, dynamic> platformArgs Map<String, dynamic> platformArgs
}); });
...@@ -229,6 +226,68 @@ abstract class Device { ...@@ -229,6 +226,68 @@ abstract class Device {
} }
} }
class DebuggingOptions {
DebuggingOptions.enabled({
this.checked: true,
this.startPaused: false,
this.observatoryPort,
this.diagnosticPort
}) : debuggingEnabled = true;
DebuggingOptions.disabled() :
debuggingEnabled = false,
checked = false,
startPaused = false,
observatoryPort = null,
diagnosticPort = null;
final bool debuggingEnabled;
final bool checked;
final bool startPaused;
final int observatoryPort;
final int diagnosticPort;
bool get hasObservatoryPort => observatoryPort != null;
/// Return the user specified observatory port. If that isn't available,
/// return [defaultObservatoryPort], or a port close to that one.
Future<int> findBestObservatoryPort() {
if (hasObservatoryPort)
return new Future<int>.value(observatoryPort);
return findPreferredPort(observatoryPort ?? defaultObservatoryPort);
}
bool get hasDiagnosticPort => diagnosticPort != null;
/// Return the user specified diagnostic port. If that isn't available,
/// return [defaultObservatoryPort], or a port close to that one.
Future<int> findBestDiagnosticPort() {
return findPreferredPort(diagnosticPort ?? defaultDiagnosticPort);
}
}
class LaunchResult {
LaunchResult.succeeded({ this.observatoryPort, this.diagnosticPort }) : started = true;
LaunchResult.failed() : started = false, observatoryPort = null, diagnosticPort = null;
bool get hasObservatory => observatoryPort != null;
final bool started;
final int observatoryPort;
final int diagnosticPort;
@override
String toString() {
StringBuffer buf = new StringBuffer('started=$started');
if (observatoryPort != null)
buf.write(', observatory=$observatoryPort');
if (diagnosticPort != null)
buf.write(', diagnostic=$diagnosticPort');
return buf.toString();
}
}
class ForwardedPort { class ForwardedPort {
ForwardedPort(this.hostPort, this.devicePort); ForwardedPort(this.hostPort, this.devicePort);
...@@ -248,40 +307,18 @@ abstract class DevicePortForwarder { ...@@ -248,40 +307,18 @@ 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: null });
/// Stops forwarding [forwardedPort]. /// Stops forwarding [forwardedPort].
Future<Null> unforward(ForwardedPort forwardedPort); Future<Null> unforward(ForwardedPort forwardedPort);
} }
/// Read the log for a particular device. Subclasses must implement `hashCode` /// Read the log for a particular device.
/// and `operator ==` so that log readers that read from the same location can be
/// de-duped. For example, two Android devices will both try and log using
/// `adb logcat`; we don't want to display two identical log streams.
abstract class DeviceLogReader { abstract class DeviceLogReader {
String get name; String get name;
/// A broadcast stream where each element in the string is a line of log /// A broadcast stream where each element in the string is a line of log output.
/// output. Stream<String> get logLines;
Stream<String> get lines;
/// Start reading logs from the device.
Future<Null> start();
/// Actively reading lines from the log?
bool get isReading;
/// Actively stop reading logs from the device.
Future<Null> stop();
/// Completes when the log is finished.
Future<int> get finished;
@override
int get hashCode;
@override
bool operator ==(dynamic other);
@override @override
String toString() => name; String toString() => name;
......
...@@ -9,7 +9,6 @@ import 'dart:io'; ...@@ -9,7 +9,6 @@ import 'dart:io';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../build_configuration.dart'; import '../build_configuration.dart';
...@@ -154,19 +153,15 @@ class IOSDevice extends Device { ...@@ -154,19 +153,15 @@ class IOSDevice extends Device {
} }
@override @override
Future<bool> startApp( Future<LaunchResult> startApp(
ApplicationPackage app, ApplicationPackage app,
Toolchain toolchain, { Toolchain toolchain, {
String mainPath, String mainPath,
String route, String route,
bool checked: true, DebuggingOptions debuggingOptions,
bool clearLogs: false,
bool startPaused: false,
int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort,
Map<String, dynamic> platformArgs Map<String, dynamic> platformArgs
}) async { }) async {
// TODO(chinmaygarde): Use checked, mainPath, route, clearLogs. // TODO(chinmaygarde): Use checked, mainPath, route.
// TODO(devoncarew): Handle startPaused, debugPort. // TODO(devoncarew): Handle startPaused, debugPort.
printTrace('Building ${app.name} for $id'); printTrace('Building ${app.name} for $id');
...@@ -174,7 +169,7 @@ class IOSDevice extends Device { ...@@ -174,7 +169,7 @@ class IOSDevice extends Device {
bool buildResult = await buildIOSXcodeProject(app, buildForDevice: true); bool buildResult = await buildIOSXcodeProject(app, buildForDevice: true);
if (!buildResult) { if (!buildResult) {
printError('Could not build the precompiled application for the device.'); printError('Could not build the precompiled application for the device.');
return false; return new LaunchResult.failed();
} }
// Step 2: Check that the application exists at the specified path. // Step 2: Check that the application exists at the specified path.
...@@ -182,7 +177,7 @@ class IOSDevice extends Device { ...@@ -182,7 +177,7 @@ class IOSDevice extends Device {
bool bundleExists = bundle.existsSync(); bool bundleExists = bundle.existsSync();
if (!bundleExists) { if (!bundleExists) {
printError('Could not find the built application bundle at ${bundle.path}.'); printError('Could not find the built application bundle at ${bundle.path}.');
return false; return new LaunchResult.failed();
} }
// Step 3: Attempt to install the application on the device. // Step 3: Attempt to install the application on the device.
...@@ -197,10 +192,10 @@ class IOSDevice extends Device { ...@@ -197,10 +192,10 @@ class IOSDevice extends Device {
if (installationResult != 0) { if (installationResult != 0) {
printError('Could not install ${bundle.path} on $id.'); printError('Could not install ${bundle.path} on $id.');
return false; return new LaunchResult.failed();
} }
return true; return new LaunchResult.succeeded();
} }
@override @override
...@@ -266,90 +261,46 @@ class IOSDevice extends Device { ...@@ -266,90 +261,46 @@ class IOSDevice extends Device {
} }
class _IOSDeviceLogReader extends DeviceLogReader { class _IOSDeviceLogReader extends DeviceLogReader {
_IOSDeviceLogReader(this.device); _IOSDeviceLogReader(this.device) {
_linesController = new StreamController<String>.broadcast(
onListen: _start,
onCancel: _stop
);
}
final IOSDevice device; final IOSDevice device;
final StreamController<String> _linesStreamController = StreamController<String> _linesController;
new StreamController<String>.broadcast();
Process _process; Process _process;
StreamSubscription<String> _stdoutSubscription;
StreamSubscription<String> _stderrSubscription;
@override @override
Stream<String> get lines => _linesStreamController.stream; Stream<String> get logLines => _linesController.stream;
@override @override
String get name => device.name; String get name => device.name;
@override void _start() {
bool get isReading => _process != null; runCommand(<String>[device.loggerPath]).then((Process process) {
_process = process;
@override _process.stdout.transform(UTF8.decoder).transform(const LineSplitter()).listen(_onLine);
Future<int> get finished { _process.stderr.transform(UTF8.decoder).transform(const LineSplitter()).listen(_onLine);
return _process != null ? _process.exitCode : new Future<int>.value(0);
}
@override
Future<Null> start() async {
if (_process != null) {
throw new StateError(
'_IOSDeviceLogReader must be stopped before it can be started.'
);
}
_process = await runCommand(<String>[device.loggerPath]);
_stdoutSubscription =
_process.stdout.transform(UTF8.decoder)
.transform(const LineSplitter()).listen(_onLine);
_stderrSubscription =
_process.stderr.transform(UTF8.decoder)
.transform(const LineSplitter()).listen(_onLine);
_process.exitCode.then(_onExit);
}
@override
Future<Null> stop() async {
if (_process == null) {
throw new StateError(
'_IOSDeviceLogReader must be started before it can be stopped.'
);
}
_stdoutSubscription?.cancel();
_stdoutSubscription = null;
_stderrSubscription?.cancel();
_stderrSubscription = null;
await _process.kill();
_process = null;
}
void _onExit(int exitCode) { _process.exitCode.then((int code) {
_stdoutSubscription?.cancel(); if (_linesController.hasListener)
_stdoutSubscription = null; _linesController.close();
_stderrSubscription?.cancel(); });
_stderrSubscription = null; });
_process = null;
} }
RegExp _runnerRegex = new RegExp(r'Runner'); static final RegExp _runnerRegex = new RegExp(r'FlutterRunner');
void _onLine(String line) { void _onLine(String line) {
if (!_runnerRegex.hasMatch(line)) if (_runnerRegex.hasMatch(line))
return; _linesController.add(line);
_linesStreamController.add(line);
} }
@override void _stop() {
int get hashCode => name.hashCode; _process?.kill();
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! _IOSDeviceLogReader)
return false;
return other.name == name;
} }
} }
......
...@@ -8,28 +8,30 @@ import 'device.dart'; ...@@ -8,28 +8,30 @@ import 'device.dart';
/// Discover service protocol ports on devices. /// Discover service protocol ports on devices.
class ServiceProtocolDiscovery { class ServiceProtocolDiscovery {
static const String kObservatoryService = 'Observatory'; /// [logReader] - a [DeviceLogReader] to look for service messages in.
static const String kDiagnosticService = 'Diagnostic server';
/// [logReader] A [DeviceLogReader] to look for service messages in.
ServiceProtocolDiscovery(DeviceLogReader logReader, String serviceName) ServiceProtocolDiscovery(DeviceLogReader logReader, String serviceName)
: _logReader = logReader, : _logReader = logReader, _serviceName = serviceName {
_serviceName = serviceName {
assert(_logReader != null); assert(_logReader != null);
if (!_logReader.isReading) _subscription = _logReader.logLines.listen(_onLine);
_logReader.start();
_logReader.lines.listen(_onLine);
} }
static const String kObservatoryService = 'Observatory';
static const String kDiagnosticService = 'Diagnostic server';
final DeviceLogReader _logReader; final DeviceLogReader _logReader;
final String _serviceName; final String _serviceName;
Completer<int> _completer = new Completer<int>(); Completer<int> _completer = new Completer<int>();
StreamSubscription<String> _subscription;
/// The [Future] returned by this function will complete when the next service /// The [Future] returned by this function will complete when the next service
/// protocol port is found. /// protocol port is found.
Future<int> nextPort() => _completer.future; Future<int> nextPort() => _completer.future;
void cancel() {
_subscription.cancel();
}
void _onLine(String line) { void _onLine(String line) {
int portNumber = 0; int portNumber = 0;
if (line.contains('$_serviceName listening on http://')) { if (line.contains('$_serviceName listening on http://')) {
...@@ -48,6 +50,7 @@ class ServiceProtocolDiscovery { ...@@ -48,6 +50,7 @@ class ServiceProtocolDiscovery {
void _located(int port) { void _located(int port) {
assert(_completer != null); assert(_completer != null);
assert(!_completer.isCompleted); assert(!_completer.isCompleted);
_completer.complete(port); _completer.complete(port);
_completer = new Completer<int>(); _completer = new Completer<int>();
} }
......
...@@ -26,6 +26,9 @@ class SnapshotCompiler { ...@@ -26,6 +26,9 @@ class SnapshotCompiler {
String depfilePath, String depfilePath,
String buildOutputPath String buildOutputPath
}) { }) {
assert(mainPath != null);
assert(snapshotPath != null);
final List<String> args = [ final List<String> args = [
_path, _path,
mainPath, mainPath,
......
...@@ -15,36 +15,39 @@ void main() { ...@@ -15,36 +15,39 @@ void main() {
MockDeviceLogReader logReader = new MockDeviceLogReader(); MockDeviceLogReader logReader = new MockDeviceLogReader();
ServiceProtocolDiscovery discoverer = ServiceProtocolDiscovery discoverer =
new ServiceProtocolDiscovery(logReader, ServiceProtocolDiscovery.kObservatoryService); new ServiceProtocolDiscovery(logReader, ServiceProtocolDiscovery.kObservatoryService);
// Get next port future. // Get next port future.
Future<int> nextPort = discoverer.nextPort(); Future<int> nextPort = discoverer.nextPort();
expect(nextPort, isNotNull); expect(nextPort, isNotNull);
// Inject some lines. // Inject some lines.
logReader.addLine('HELLO WORLD'); logReader.addLine('HELLO WORLD');
logReader.addLine( logReader.addLine('Observatory listening on http://127.0.0.1:9999');
'Observatory listening on http://127.0.0.1:9999');
// Await the port. // Await the port.
expect(await nextPort, 9999); expect(await nextPort, 9999);
// Get next port future. // Get next port future.
nextPort = discoverer.nextPort(); nextPort = discoverer.nextPort();
logReader.addLine( logReader.addLine('Observatory listening on http://127.0.0.1:3333');
'Observatory listening on http://127.0.0.1:3333');
expect(await nextPort, 3333); expect(await nextPort, 3333);
// Get next port future. // Get next port future.
nextPort = discoverer.nextPort(); nextPort = discoverer.nextPort();
// Inject some bad lines. // Inject some bad lines.
logReader.addLine('Observatory listening on http://127.0.0.1'); logReader.addLine('Observatory listening on http://127.0.0.1');
logReader.addLine('Observatory listening on http://127.0.0.1:'); logReader.addLine('Observatory listening on http://127.0.0.1:');
logReader.addLine( logReader.addLine('Observatory listening on http://127.0.0.1:apple');
'Observatory listening on http://127.0.0.1:apple');
int port = await nextPort.timeout( int port = await nextPort.timeout(
const Duration(milliseconds: 100), onTimeout: () => 77); const Duration(milliseconds: 100), onTimeout: () => 77);
// Expect the timeout port. // Expect the timeout port.
expect(port, 77); expect(port, 77);
// Get next port future. // Get next port future.
nextPort = discoverer.nextPort(); nextPort = discoverer.nextPort();
logReader.addLine( logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:52584');
'I/flutter : Observatory listening on http://127.0.0.1:52584');
expect(await nextPort, 52584); expect(await nextPort, 52584);
discoverer.cancel();
}); });
}); });
} }
...@@ -63,38 +63,12 @@ class MockDeviceLogReader extends DeviceLogReader { ...@@ -63,38 +63,12 @@ class MockDeviceLogReader extends DeviceLogReader {
@override @override
String get name => 'MockLogReader'; String get name => 'MockLogReader';
final StreamController<String> _linesStreamController = final StreamController<String> _linesController = new StreamController<String>.broadcast();
new StreamController<String>.broadcast();
final Completer<int> _finishedCompleter = new Completer<int>();
@override
Stream<String> get lines => _linesStreamController.stream;
void addLine(String line) {
_linesStreamController.add(line);
}
bool _started = false;
@override @override
Future<Null> start() async { Stream<String> get logLines => _linesController.stream;
assert(!_started);
_started = true;
}
@override void addLine(String line) => _linesController.add(line);
bool get isReading => _started;
@override
Future<Null> stop() {
assert(_started);
_started = false;
return new Future<Null>.value();
}
@override
Future<int> get finished => _finishedCompleter.future;
} }
void applyMocksToCommand(FlutterCommand command) { void applyMocksToCommand(FlutterCommand command) {
......
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