Unverified Commit 7a0911b4 authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Revert "Attach looks at future observatory URIs (#44637)" (#45211)

This reverts commit 6d77996d.
parent a32fc986
...@@ -846,9 +846,25 @@ class _AdbLogReader extends DeviceLogReader { ...@@ -846,9 +846,25 @@ class _AdbLogReader extends DeviceLogReader {
@override @override
String get name => device.name; String get name => device.name;
DateTime _timeOrigin;
DateTime _adbTimestampToDateTime(String adbTimestamp) {
// The adb timestamp format is: mm-dd hours:minutes:seconds.milliseconds
// Dart's DateTime parse function accepts this format so long as we provide
// the year, resulting in:
// yyyy-mm-dd hours:minutes:seconds.milliseconds.
return DateTime.parse('${DateTime.now().year}-$adbTimestamp');
}
void _start() { void _start() {
// Start the adb logcat process and filter logs by the "flutter" tag. // Start the adb logcat process.
final List<String> args = <String>['shell', '-x', 'logcat', '-v', 'time', '-s', 'flutter']; final List<String> args = <String>['shell', '-x', 'logcat', '-v', 'time'];
final String lastTimestamp = device.lastLogcatTimestamp;
if (lastTimestamp != null) {
_timeOrigin = _adbTimestampToDateTime(lastTimestamp);
} else {
_timeOrigin = null;
}
processUtils.start(device.adbCommandForDevice(args)).then<void>((Process process) { processUtils.start(device.adbCommandForDevice(args)).then<void>((Process process) {
_process = process; _process = process;
// We expect logcat streams to occasionally contain invalid utf-8, // We expect logcat streams to occasionally contain invalid utf-8,
...@@ -898,7 +914,18 @@ class _AdbLogReader extends DeviceLogReader { ...@@ -898,7 +914,18 @@ class _AdbLogReader extends DeviceLogReader {
// mm-dd hh:mm:ss.milliseconds Priority/Tag( PID): .... // mm-dd hh:mm:ss.milliseconds Priority/Tag( PID): ....
void _onLine(String line) { void _onLine(String line) {
final Match timeMatch = AndroidDevice._timeRegExp.firstMatch(line); final Match timeMatch = AndroidDevice._timeRegExp.firstMatch(line);
if (timeMatch == null || line.length == timeMatch.end) { if (timeMatch == null) {
return;
}
if (_timeOrigin != null) {
final String timestamp = timeMatch.group(0);
final DateTime time = _adbTimestampToDateTime(timestamp);
if (!time.isAfter(_timeOrigin)) {
// Ignore log messages before the origin.
return;
}
}
if (line.length == timeMatch.end) {
return; return;
} }
// Chop off the time. // Chop off the time.
......
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart'; import '../base/context.dart';
...@@ -202,14 +200,16 @@ class AttachCommand extends FlutterCommand { ...@@ -202,14 +200,16 @@ class AttachCommand extends FlutterCommand {
notifyingLogger: NotifyingLogger(), logToStdout: true) notifyingLogger: NotifyingLogger(), logToStdout: true)
: null; : null;
Stream<Uri> observatoryUri; Uri observatoryUri;
bool usesIpv6 = ipv6; bool usesIpv6 = ipv6;
final String ipv6Loopback = InternetAddress.loopbackIPv6.address; final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
final String ipv4Loopback = InternetAddress.loopbackIPv4.address; final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback; final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;
bool attachLogger = false;
if (devicePort == null && debugUri == null) { if (devicePort == null && debugUri == null) {
if (device is FuchsiaDevice) { if (device is FuchsiaDevice) {
attachLogger = true;
final String module = stringArg('module'); final String module = stringArg('module');
if (module == null) { if (module == null) {
throwToolExit('\'--module\' is required for attaching to a Fuchsia device'); throwToolExit('\'--module\' is required for attaching to a Fuchsia device');
...@@ -218,7 +218,8 @@ class AttachCommand extends FlutterCommand { ...@@ -218,7 +218,8 @@ class AttachCommand extends FlutterCommand {
FuchsiaIsolateDiscoveryProtocol isolateDiscoveryProtocol; FuchsiaIsolateDiscoveryProtocol isolateDiscoveryProtocol;
try { try {
isolateDiscoveryProtocol = device.getIsolateDiscoveryProtocol(module); isolateDiscoveryProtocol = device.getIsolateDiscoveryProtocol(module);
observatoryUri = Stream<Uri>.fromFuture(isolateDiscoveryProtocol.uri).asBroadcastStream(); observatoryUri = await isolateDiscoveryProtocol.uri;
printStatus('Done.'); // FYI, this message is used as a sentinel in tests.
} catch (_) { } catch (_) {
isolateDiscoveryProtocol?.dispose(); isolateDiscoveryProtocol?.dispose();
final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList(); final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
...@@ -228,55 +229,85 @@ class AttachCommand extends FlutterCommand { ...@@ -228,55 +229,85 @@ class AttachCommand extends FlutterCommand {
rethrow; rethrow;
} }
} else if ((device is IOSDevice) || (device is IOSSimulator)) { } else if ((device is IOSDevice) || (device is IOSSimulator)) {
observatoryUri = Stream<Uri> observatoryUri = await MDnsObservatoryDiscovery.instance.getObservatoryUri(
.fromFuture( appId,
MDnsObservatoryDiscovery.instance.getObservatoryUri( device,
appId, usesIpv6: usesIpv6,
device, deviceVmservicePort: deviceVmservicePort,
usesIpv6: usesIpv6, );
deviceVmservicePort: deviceVmservicePort,
)
).asBroadcastStream();
} }
// If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery. // If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery.
if (observatoryUri == null) { if (observatoryUri == null) {
final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery observatoryDiscovery;
ProtocolDiscovery.observatory( try {
observatoryDiscovery = ProtocolDiscovery.observatory(
device.getLogReader(), device.getLogReader(),
portForwarder: device.portForwarder, portForwarder: device.portForwarder,
ipv6: ipv6, ipv6: ipv6,
devicePort: deviceVmservicePort, devicePort: deviceVmservicePort,
hostPort: hostVmservicePort, hostPort: hostVmservicePort,
); );
printStatus('Waiting for a connection from Flutter on ${device.name}...'); printStatus('Waiting for a connection from Flutter on ${device.name}...');
observatoryUri = observatoryDiscovery.uris; observatoryUri = await observatoryDiscovery.uri;
// Determine ipv6 status from the scanned logs. // Determine ipv6 status from the scanned logs.
usesIpv6 = observatoryDiscovery.ipv6; usesIpv6 = observatoryDiscovery.ipv6;
printStatus('Done.'); // FYI, this message is used as a sentinel in tests.
} catch (error) {
throwToolExit('Failed to establish a debug connection with ${device.name}: $error');
} finally {
await observatoryDiscovery?.cancel();
}
} }
} else { } else {
observatoryUri = Stream<Uri> observatoryUri = await buildObservatoryUri(
.fromFuture( device,
buildObservatoryUri( debugUri?.host ?? hostname,
device, devicePort ?? debugUri.port,
debugUri?.host ?? hostname, hostVmservicePort,
devicePort ?? debugUri.port, debugUri?.path,
hostVmservicePort, );
debugUri?.path,
)
).asBroadcastStream();
} }
terminal.usesTerminalUi = daemon == null;
try { try {
final bool useHot = getBuildInfo().isDebug;
final FlutterDevice flutterDevice = await FlutterDevice.create(
device,
flutterProject: flutterProject,
trackWidgetCreation: boolArg('track-widget-creation'),
fileSystemRoots: stringsArg('filesystem-root'),
fileSystemScheme: stringArg('filesystem-scheme'),
viewFilter: stringArg('isolate-filter'),
target: stringArg('target'),
targetModel: TargetModel(stringArg('target-model')),
buildMode: getBuildMode(),
dartDefines: dartDefines,
);
flutterDevice.observatoryUris = <Uri>[ observatoryUri ];
final List<FlutterDevice> flutterDevices = <FlutterDevice>[flutterDevice];
final DebuggingOptions debuggingOptions = DebuggingOptions.enabled(getBuildInfo());
terminal.usesTerminalUi = daemon == null;
final ResidentRunner runner = useHot ?
hotRunnerFactory.build(
flutterDevices,
target: targetFile,
debuggingOptions: debuggingOptions,
packagesFilePath: globalResults['packages'] as String,
projectRootPath: stringArg('project-root'),
dillOutputPath: stringArg('output-dill'),
ipv6: usesIpv6,
flutterProject: flutterProject,
)
: ColdRunner(
flutterDevices,
target: targetFile,
debuggingOptions: debuggingOptions,
ipv6: usesIpv6,
);
if (attachLogger) {
flutterDevice.startEchoingDeviceLog();
}
int result; int result;
if (daemon != null) { if (daemon != null) {
final ResidentRunner runner = await createResidentRunner(
observatoryUris: observatoryUri,
device: device,
flutterProject: flutterProject,
usesIpv6: usesIpv6,
);
AppInstance app; AppInstance app;
try { try {
app = await daemon.appDomain.launch( app = await daemon.appDomain.launch(
...@@ -293,34 +324,20 @@ class AttachCommand extends FlutterCommand { ...@@ -293,34 +324,20 @@ class AttachCommand extends FlutterCommand {
} }
result = await app.runner.waitForAppToFinish(); result = await app.runner.waitForAppToFinish();
assert(result != null); assert(result != null);
return; } else {
}
while (true) {
final ResidentRunner runner = await createResidentRunner(
observatoryUris: observatoryUri,
device: device,
flutterProject: flutterProject,
usesIpv6: usesIpv6,
);
final Completer<void> onAppStart = Completer<void>.sync(); final Completer<void> onAppStart = Completer<void>.sync();
TerminalHandler terminalHandler;
unawaited(onAppStart.future.whenComplete(() { unawaited(onAppStart.future.whenComplete(() {
terminalHandler = TerminalHandler(runner) TerminalHandler(runner)
..setupTerminal() ..setupTerminal()
..registerSignalHandlers(); ..registerSignalHandlers();
})); }));
result = await runner.attach( result = await runner.attach(
appStartedCompleter: onAppStart, appStartedCompleter: onAppStart,
); );
if (result != 0) {
throwToolExit(null, exitCode: result);
}
terminalHandler?.stop();
assert(result != null); assert(result != null);
if (runner.exited || !runner.isWaitingForObservatory) { }
break; if (result != 0) {
} throwToolExit(null, exitCode: result);
printStatus('Waiting for a new connection from Flutter on ${device.name}...');
} }
} finally { } finally {
final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList(); final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
...@@ -330,52 +347,6 @@ class AttachCommand extends FlutterCommand { ...@@ -330,52 +347,6 @@ class AttachCommand extends FlutterCommand {
} }
} }
Future<ResidentRunner> createResidentRunner({
@required Stream<Uri> observatoryUris,
@required Device device,
@required FlutterProject flutterProject,
@required bool usesIpv6,
}) async {
assert(observatoryUris != null);
assert(device != null);
assert(flutterProject != null);
assert(usesIpv6 != null);
final FlutterDevice flutterDevice = await FlutterDevice.create(
device,
flutterProject: flutterProject,
trackWidgetCreation: boolArg('track-widget-creation'),
fileSystemRoots: stringsArg('filesystem-root'),
fileSystemScheme: stringArg('filesystem-scheme'),
viewFilter: stringArg('isolate-filter'),
target: stringArg('target'),
targetModel: TargetModel(stringArg('target-model')),
buildMode: getBuildMode(),
dartDefines: dartDefines,
);
flutterDevice.observatoryUris = observatoryUris;
final List<FlutterDevice> flutterDevices = <FlutterDevice>[flutterDevice];
final DebuggingOptions debuggingOptions = DebuggingOptions.enabled(getBuildInfo());
return getBuildInfo().isDebug
? hotRunnerFactory.build(
flutterDevices,
target: targetFile,
debuggingOptions: debuggingOptions,
packagesFilePath: globalResults['packages'] as String,
projectRootPath: stringArg('project-root'),
dillOutputPath: stringArg('output-dill'),
ipv6: usesIpv6,
flutterProject: flutterProject,
)
: ColdRunner(
flutterDevices,
target: targetFile,
debuggingOptions: debuggingOptions,
ipv6: usesIpv6,
);
}
Future<void> _validateArguments() async { } Future<void> _validateArguments() async { }
} }
......
...@@ -11,7 +11,6 @@ import 'asset.dart'; ...@@ -11,7 +11,6 @@ import 'asset.dart';
import 'base/context.dart'; import 'base/context.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/io.dart'; import 'base/io.dart';
import 'base/net.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'bundle.dart'; import 'bundle.dart';
import 'compile.dart'; import 'compile.dart';
...@@ -266,20 +265,17 @@ class DevFSException implements Exception { ...@@ -266,20 +265,17 @@ class DevFSException implements Exception {
class _DevFSHttpWriter { class _DevFSHttpWriter {
_DevFSHttpWriter(this.fsName, VMService serviceProtocol) _DevFSHttpWriter(this.fsName, VMService serviceProtocol)
: httpAddress = serviceProtocol.httpAddress, : httpAddress = serviceProtocol.httpAddress;
_client = (context.get<HttpClientFactory>() == null)
? HttpClient()
: context.get<HttpClientFactory>()();
final String fsName; final String fsName;
final Uri httpAddress; final Uri httpAddress;
final HttpClient _client;
static const int kMaxInFlight = 6; static const int kMaxInFlight = 6;
int _inFlight = 0; int _inFlight = 0;
Map<Uri, DevFSContent> _outstanding; Map<Uri, DevFSContent> _outstanding;
Completer<void> _completer; Completer<void> _completer;
final HttpClient _client = HttpClient();
Future<void> write(Map<Uri, DevFSContent> entries) async { Future<void> write(Map<Uri, DevFSContent> entries) async {
_client.maxConnectionsPerHost = kMaxInFlight; _client.maxConnectionsPerHost = kMaxInFlight;
......
...@@ -17,23 +17,16 @@ class ProtocolDiscovery { ...@@ -17,23 +17,16 @@ class ProtocolDiscovery {
this.logReader, this.logReader,
this.serviceName, { this.serviceName, {
this.portForwarder, this.portForwarder,
this.throttleDuration,
this.hostPort, this.hostPort,
this.devicePort, this.devicePort,
this.ipv6, this.ipv6,
}) : assert(logReader != null) }) : assert(logReader != null) {
{ _deviceLogSubscription = logReader.logLines.listen(_handleLine);
_deviceLogSubscription = logReader.logLines.listen(
_handleLine,
onDone: _stopScrapingLogs,
);
_uriStreamController = StreamController<Uri>.broadcast();
} }
factory ProtocolDiscovery.observatory( factory ProtocolDiscovery.observatory(
DeviceLogReader logReader, { DeviceLogReader logReader, {
DevicePortForwarder portForwarder, DevicePortForwarder portForwarder,
Duration throttleDuration = const Duration(milliseconds: 200),
@required int hostPort, @required int hostPort,
@required int devicePort, @required int devicePort,
@required bool ipv6, @required bool ipv6,
...@@ -43,7 +36,6 @@ class ProtocolDiscovery { ...@@ -43,7 +36,6 @@ class ProtocolDiscovery {
logReader, logReader,
kObservatoryService, kObservatoryService,
portForwarder: portForwarder, portForwarder: portForwarder,
throttleDuration: throttleDuration,
hostPort: hostPort, hostPort: hostPort,
devicePort: devicePort, devicePort: devicePort,
ipv6: ipv6, ipv6: ipv6,
...@@ -57,70 +49,50 @@ class ProtocolDiscovery { ...@@ -57,70 +49,50 @@ class ProtocolDiscovery {
final int devicePort; final int devicePort;
final bool ipv6; final bool ipv6;
/// The time to wait before forwarding a new observatory URIs from [logReader]. final Completer<Uri> _completer = Completer<Uri>();
final Duration throttleDuration;
StreamSubscription<String> _deviceLogSubscription; StreamSubscription<String> _deviceLogSubscription;
StreamController<Uri> _uriStreamController;
/// The discovered service URI. /// The discovered service URI.
/// Use [uris] instead.
// TODO(egarciad): replace `uri` for `uris`.
Future<Uri> get uri {
return uris.first;
}
/// The discovered service URIs.
/// ///
/// When a new observatory URI is available in [logReader], /// Port forwarding is only attempted when this is invoked, in case we never
/// the URIs are forwarded at most once every [throttleDuration]. /// need to port forward.
/// Future<Uri> get uri async {
/// Port forwarding is only attempted when this is invoked, final Uri rawUri = await _completer.future;
/// for each observatory URI in the stream. return await _forwardPort(rawUri);
Stream<Uri> get uris {
return _uriStreamController.stream
.transform(_throttle<Uri>(
waitDuration: throttleDuration,
))
.asyncMap<Uri>(_forwardPort);
} }
Future<void> cancel() => _stopScrapingLogs(); Future<void> cancel() => _stopScrapingLogs();
Future<void> _stopScrapingLogs() async { Future<void> _stopScrapingLogs() async {
await _uriStreamController?.close();
await _deviceLogSubscription?.cancel(); await _deviceLogSubscription?.cancel();
_deviceLogSubscription = null; _deviceLogSubscription = null;
} }
Match _getPatternMatch(String line) { void _handleLine(String line) {
Uri uri;
final RegExp r = RegExp('${RegExp.escape(serviceName)} listening on ((http|\/\/)[a-zA-Z0-9:/=_\\-\.\\[\\]]+)'); final RegExp r = RegExp('${RegExp.escape(serviceName)} listening on ((http|\/\/)[a-zA-Z0-9:/=_\\-\.\\[\\]]+)');
return r.firstMatch(line); final Match match = r.firstMatch(line);
}
Uri _getObservatoryUri(String line) {
final Match match = _getPatternMatch(line);
if (match != null) { if (match != null) {
return Uri.parse(match[1]); try {
} uri = Uri.parse(match[1]);
return null; } on FormatException catch (error, stackTrace) {
} _stopScrapingLogs();
_completer.completeError(error, stackTrace);
void _handleLine(String line) { }
Uri uri;
try {
uri = _getObservatoryUri(line);
} on FormatException catch(error, stackTrace) {
_uriStreamController.addError(error, stackTrace);
} }
if (uri == null) { if (uri == null) {
return; return;
} }
if (devicePort != null && uri.port != devicePort) { if (devicePort != null && uri.port != devicePort) {
printTrace('skipping potential observatory $uri due to device port mismatch'); printTrace('skipping potential observatory $uri due to device port mismatch');
return; return;
} }
_uriStreamController.add(uri);
assert(!_completer.isCompleted);
_stopScrapingLogs();
_completer.complete(uri);
} }
Future<Uri> _forwardPort(Uri deviceUri) async { Future<Uri> _forwardPort(Uri deviceUri) async {
...@@ -138,43 +110,7 @@ class ProtocolDiscovery { ...@@ -138,43 +110,7 @@ class ProtocolDiscovery {
if (ipv6) { if (ipv6) {
hostUri = hostUri.replace(host: InternetAddress.loopbackIPv6.host); hostUri = hostUri.replace(host: InternetAddress.loopbackIPv6.host);
} }
return hostUri; return hostUri;
} }
} }
/// This transformer will produce an event at most once every [waitDuration].
///
/// For example, consider a `waitDuration` of `10ms`, and list of event names
/// and arrival times: `a (0ms), b (5ms), c (11ms), d (21ms)`.
/// The events `c` and `d` will be produced as a result.
StreamTransformer<S, S> _throttle<S>({
@required Duration waitDuration,
}) {
assert(waitDuration != null);
S latestLine;
int lastExecution;
Future<void> throttleFuture;
return StreamTransformer<S, S>
.fromHandlers(
handleData: (S value, EventSink<S> sink) {
latestLine = value;
final int currentTime = DateTime.now().millisecondsSinceEpoch;
lastExecution ??= currentTime;
final int remainingTime = currentTime - lastExecution;
final int nextExecutionTime = remainingTime > waitDuration.inMilliseconds
? 0
: waitDuration.inMilliseconds - remainingTime;
throttleFuture ??= Future<void>
.delayed(Duration(milliseconds: nextExecutionTime))
.whenComplete(() {
sink.add(latestLine);
throttleFuture = null;
lastExecution = DateTime.now().millisecondsSinceEpoch;
});
}
);
}
...@@ -131,20 +131,16 @@ class FlutterDevice { ...@@ -131,20 +131,16 @@ class FlutterDevice {
final Device device; final Device device;
final ResidentCompiler generator; final ResidentCompiler generator;
Stream<Uri> observatoryUris; List<Uri> observatoryUris;
List<VMService> vmServices; List<VMService> vmServices;
DevFS devFS; DevFS devFS;
ApplicationPackage package; ApplicationPackage package;
List<String> fileSystemRoots; List<String> fileSystemRoots;
String fileSystemScheme; String fileSystemScheme;
StreamSubscription<String> _loggingSubscription; StreamSubscription<String> _loggingSubscription;
bool _isListeningForObservatoryUri;
final String viewFilter; final String viewFilter;
final bool trackWidgetCreation; final bool trackWidgetCreation;
/// Whether the stream [observatoryUris] is still open.
bool get isWaitingForObservatory => _isListeningForObservatoryUri ?? false;
/// If the [reloadSources] parameter is not null the 'reloadSources' service /// If the [reloadSources] parameter is not null the 'reloadSources' service
/// will be registered. /// will be registered.
/// The 'reloadSources' service can be used by other Service Protocol clients /// The 'reloadSources' service can be used by other Service Protocol clients
...@@ -158,50 +154,23 @@ class FlutterDevice { ...@@ -158,50 +154,23 @@ class FlutterDevice {
ReloadSources reloadSources, ReloadSources reloadSources,
Restart restart, Restart restart,
CompileExpression compileExpression, CompileExpression compileExpression,
}) { }) async {
final Completer<void> completer = Completer<void>(); if (vmServices != null) {
StreamSubscription<void> subscription; return;
bool isWaitingForVm = false; }
final List<VMService> localVmServices = List<VMService>(observatoryUris.length);
subscription = observatoryUris.listen((Uri observatoryUri) async { for (int i = 0; i < observatoryUris.length; i += 1) {
// FYI, this message is used as a sentinel in tests. printTrace('Connecting to service protocol: ${observatoryUris[i]}');
printTrace('Connecting to service protocol: $observatoryUri'); localVmServices[i] = await VMService.connect(
isWaitingForVm = true; observatoryUris[i],
VMService service; reloadSources: reloadSources,
restart: restart,
try { compileExpression: compileExpression,
service = await VMService.connect( );
observatoryUri, printTrace('Successfully connected to service protocol: ${observatoryUris[i]}');
reloadSources: reloadSources, }
restart: restart, vmServices = localVmServices;
compileExpression: compileExpression, device.getLogReader(app: package).connectedVMServices = vmServices;
);
} on Exception catch (exception) {
printTrace('Fail to connect to service protocol: $observatoryUri: $exception');
if (!completer.isCompleted && !_isListeningForObservatoryUri) {
completer.completeError('failed to connect to $observatoryUri');
}
return;
}
if (completer.isCompleted) {
return;
}
printTrace('Successfully connected to service protocol: $observatoryUri');
vmServices = <VMService>[service];
device.getLogReader(app: package).connectedVMServices = vmServices;
completer.complete();
await subscription.cancel();
}, onError: (dynamic error) {
printTrace('Fail to handle observatory URI: $error');
}, onDone: () {
_isListeningForObservatoryUri = false;
if (!completer.isCompleted && !isWaitingForVm) {
completer.completeError('connection to device ended too early');
}
});
_isListeningForObservatoryUri = true;
return completer.future;
} }
Future<void> refreshViews() async { Future<void> refreshViews() async {
...@@ -252,7 +221,6 @@ class FlutterDevice { ...@@ -252,7 +221,6 @@ class FlutterDevice {
if (flutterViews.any((FlutterView view) { if (flutterViews.any((FlutterView view) {
return view != null && return view != null &&
view.uiIsolate != null && view.uiIsolate != null &&
view.uiIsolate.pauseEvent != null &&
view.uiIsolate.pauseEvent.isPauseEvent; view.uiIsolate.pauseEvent.isPauseEvent;
} }
)) { )) {
...@@ -463,13 +431,9 @@ class FlutterDevice { ...@@ -463,13 +431,9 @@ class FlutterDevice {
return 2; return 2;
} }
if (result.hasObservatory) { if (result.hasObservatory) {
observatoryUris = Stream<Uri> observatoryUris = <Uri>[result.observatoryUri];
.value(result.observatoryUri)
.asBroadcastStream();
} else { } else {
observatoryUris = const Stream<Uri> observatoryUris = <Uri>[];
.empty()
.asBroadcastStream();
} }
return 0; return 0;
} }
...@@ -527,13 +491,9 @@ class FlutterDevice { ...@@ -527,13 +491,9 @@ class FlutterDevice {
return 2; return 2;
} }
if (result.hasObservatory) { if (result.hasObservatory) {
observatoryUris = Stream<Uri> observatoryUris = <Uri>[result.observatoryUri];
.value(result.observatoryUri)
.asBroadcastStream();
} else { } else {
observatoryUris = const Stream<Uri> observatoryUris = <Uri>[];
.empty()
.asBroadcastStream();
} }
return 0; return 0;
} }
...@@ -653,21 +613,14 @@ abstract class ResidentRunner { ...@@ -653,21 +613,14 @@ abstract class ResidentRunner {
/// The parent location of the incremental artifacts. /// The parent location of the incremental artifacts.
@visibleForTesting @visibleForTesting
final Directory artifactDirectory; final Directory artifactDirectory;
final Completer<int> _finished = Completer<int>();
final String packagesFilePath; final String packagesFilePath;
final String projectRootPath; final String projectRootPath;
final String mainPath; final String mainPath;
final AssetBundle assetBundle; final AssetBundle assetBundle;
bool _exited = false; bool _exited = false;
Completer<int> _finished = Completer<int>(); bool hotMode ;
bool hotMode;
/// Returns true if every device is streaming observatory URIs.
bool get isWaitingForObservatory {
return flutterDevices.every((FlutterDevice device) {
return device.isWaitingForObservatory;
});
}
String get dillOutputPath => _dillOutputPath ?? fs.path.join(artifactDirectory.path, 'app.dill'); String get dillOutputPath => _dillOutputPath ?? fs.path.join(artifactDirectory.path, 'app.dill');
String getReloadPath({ bool fullRestart }) => mainPath + (fullRestart ? '' : '.incremental') + '.dill'; String getReloadPath({ bool fullRestart }) => mainPath + (fullRestart ? '' : '.incremental') + '.dill';
...@@ -678,9 +631,6 @@ abstract class ResidentRunner { ...@@ -678,9 +631,6 @@ abstract class ResidentRunner {
bool get isRunningRelease => debuggingOptions.buildInfo.isRelease; bool get isRunningRelease => debuggingOptions.buildInfo.isRelease;
bool get supportsServiceProtocol => isRunningDebug || isRunningProfile; bool get supportsServiceProtocol => isRunningDebug || isRunningProfile;
/// Returns [true] if the resident runner exited after invoking [exit()].
bool get exited => _exited;
/// Whether this runner can hot restart. /// Whether this runner can hot restart.
/// ///
/// To prevent scenarios where only a subset of devices are hot restarted, /// To prevent scenarios where only a subset of devices are hot restarted,
...@@ -912,8 +862,6 @@ abstract class ResidentRunner { ...@@ -912,8 +862,6 @@ abstract class ResidentRunner {
throw 'The service protocol is not enabled.'; throw 'The service protocol is not enabled.';
} }
_finished ??= Completer<int>();
bool viewFound = false; bool viewFound = false;
for (FlutterDevice device in flutterDevices) { for (FlutterDevice device in flutterDevices) {
await device.connect( await device.connect(
...@@ -964,25 +912,22 @@ abstract class ResidentRunner { ...@@ -964,25 +912,22 @@ abstract class ResidentRunner {
// User requested the application exit. // User requested the application exit.
return; return;
} }
if (_finished == null || _finished.isCompleted) { if (_finished.isCompleted) {
return; return;
} }
printStatus('Lost connection to device.'); printStatus('Lost connection to device.');
_finished.complete(0); _finished.complete(0);
_finished = null;
} }
void appFinished() { void appFinished() {
if (_finished == null || _finished.isCompleted) { if (_finished.isCompleted) {
return; return;
} }
printStatus('Application finished.'); printStatus('Application finished.');
_finished.complete(0); _finished.complete(0);
_finished = null;
} }
Future<int> waitForAppToFinish() async { Future<int> waitForAppToFinish() async {
_finished ??= Completer<int>();
final int exitCode = await _finished.future; final int exitCode = await _finished.future;
assert(exitCode != null); assert(exitCode != null);
await cleanupAtFinish(); await cleanupAtFinish();
...@@ -1100,33 +1045,15 @@ class TerminalHandler { ...@@ -1100,33 +1045,15 @@ class TerminalHandler {
subscription = terminal.keystrokes.listen(processTerminalInput); subscription = terminal.keystrokes.listen(processTerminalInput);
} }
final Map<io.ProcessSignal, Object> _signalTokens = <io.ProcessSignal, Object>{};
void _addSignalHandler(io.ProcessSignal signal, SignalHandler handler) {
_signalTokens[signal] = signals.addHandler(signal, handler);
}
void registerSignalHandlers() { void registerSignalHandlers() {
assert(residentRunner.stayResident); assert(residentRunner.stayResident);
signals.addHandler(io.ProcessSignal.SIGINT, _cleanUp);
_addSignalHandler(io.ProcessSignal.SIGINT, _cleanUp); signals.addHandler(io.ProcessSignal.SIGTERM, _cleanUp);
_addSignalHandler(io.ProcessSignal.SIGTERM, _cleanUp);
if (!residentRunner.supportsServiceProtocol || !residentRunner.supportsRestart) { if (!residentRunner.supportsServiceProtocol || !residentRunner.supportsRestart) {
return; return;
} }
_addSignalHandler(io.ProcessSignal.SIGUSR1, _handleSignal); signals.addHandler(io.ProcessSignal.SIGUSR1, _handleSignal);
_addSignalHandler(io.ProcessSignal.SIGUSR2, _handleSignal); signals.addHandler(io.ProcessSignal.SIGUSR2, _handleSignal);
}
/// Unregisters terminal signal and keystroke handlers.
void stop() {
assert(residentRunner.stayResident);
for (MapEntry<io.ProcessSignal, Object> entry in _signalTokens.entries) {
signals.removeHandler(entry.key, entry.value);
}
_signalTokens.clear();
subscription.cancel();
} }
/// Returns [true] if the input has been handled by this function. /// Returns [true] if the input has been handled by this function.
......
...@@ -83,7 +83,7 @@ class ColdRunner extends ResidentRunner { ...@@ -83,7 +83,7 @@ class ColdRunner extends ResidentRunner {
if (flutterDevices.first.observatoryUris != null) { if (flutterDevices.first.observatoryUris != null) {
// For now, only support one debugger connection. // For now, only support one debugger connection.
connectionInfoCompleter?.complete(DebugConnectionInfo( connectionInfoCompleter?.complete(DebugConnectionInfo(
httpUri: flutterDevices.first.vmServices.first.httpAddress, httpUri: flutterDevices.first.observatoryUris.first,
wsUri: flutterDevices.first.vmServices.first.wsAddress, wsUri: flutterDevices.first.vmServices.first.wsAddress,
)); ));
} }
...@@ -184,8 +184,9 @@ class ColdRunner extends ResidentRunner { ...@@ -184,8 +184,9 @@ class ColdRunner extends ResidentRunner {
for (FlutterDevice device in flutterDevices) { for (FlutterDevice device in flutterDevices) {
final String dname = device.device.name; final String dname = device.device.name;
if (device.observatoryUris != null) { if (device.observatoryUris != null) {
for (VMService vm in device.vmServices) { for (Uri uri in device.observatoryUris) {
printStatus('An Observatory debugger and profiler on $dname is available at: ${vm.wsAddress}'); printStatus('An Observatory debugger and profiler on $dname is available at $uri');
haveAnything = true;
} }
} }
} }
......
...@@ -180,7 +180,7 @@ class HotRunner extends ResidentRunner { ...@@ -180,7 +180,7 @@ class HotRunner extends ResidentRunner {
// Only handle one debugger connection. // Only handle one debugger connection.
connectionInfoCompleter.complete( connectionInfoCompleter.complete(
DebugConnectionInfo( DebugConnectionInfo(
httpUri: flutterDevices.first.vmServices.first.httpAddress, httpUri: flutterDevices.first.observatoryUris.first,
wsUri: flutterDevices.first.vmServices.first.wsAddress, wsUri: flutterDevices.first.vmServices.first.wsAddress,
baseUri: baseUris.first.toString(), baseUri: baseUris.first.toString(),
), ),
...@@ -987,8 +987,8 @@ class HotRunner extends ResidentRunner { ...@@ -987,8 +987,8 @@ class HotRunner extends ResidentRunner {
printStatus(message); printStatus(message);
for (FlutterDevice device in flutterDevices) { for (FlutterDevice device in flutterDevices) {
final String dname = device.device.name; final String dname = device.device.name;
for (VMService vm in device.vmServices) { for (Uri uri in device.observatoryUris) {
printStatus('An Observatory debugger and profiler on $dname is available at: ${vm.wsAddress}'); printStatus('An Observatory debugger and profiler on $dname is available at: $uri');
} }
} }
final String quitMessage = _didAttach final String quitMessage = _didAttach
......
...@@ -10,7 +10,6 @@ import 'package:file/file.dart'; ...@@ -10,7 +10,6 @@ import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/net.dart';
import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/vmservice.dart'; import 'package:flutter_tools/src/vmservice.dart';
...@@ -160,7 +159,6 @@ void main() { ...@@ -160,7 +159,6 @@ void main() {
verify(httpRequest.close()).called(kFailedAttempts + 1); verify(httpRequest.close()).called(kFailedAttempts + 1);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
HttpClientFactory: () => () => httpClient,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
}); });
...@@ -210,7 +208,6 @@ void main() { ...@@ -210,7 +208,6 @@ void main() {
expect(report.success, true); expect(report.success, true);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
HttpClient: () => () => HttpClient(),
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
...@@ -313,7 +310,6 @@ void main() { ...@@ -313,7 +310,6 @@ void main() {
expect(devFS.lastCompiled, isNot(previousCompile)); expect(devFS.lastCompiled, isNot(previousCompile));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
HttpClient: () => () => HttpClient(),
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
}); });
......
...@@ -6,7 +6,6 @@ import 'dart:async'; ...@@ -6,7 +6,6 @@ import 'dart:async';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/protocol_discovery.dart'; import 'package:flutter_tools/src/protocol_discovery.dart';
import 'package:quiver/testing/async.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
...@@ -17,47 +16,40 @@ void main() { ...@@ -17,47 +16,40 @@ void main() {
MockDeviceLogReader logReader; MockDeviceLogReader logReader;
ProtocolDiscovery discoverer; ProtocolDiscovery discoverer;
/// Performs test set-up functionality that must be performed as part of
/// the `test()` pass and not part of the `setUp()` pass.
///
/// This exists to make sure we're not creating an error that tries to
/// cross an error-zone boundary. Our use of `testUsingContext()` runs the
/// test code inside an error zone, but the `setUp()` code is not run in
/// any zone. This creates the potential for errors that try to cross
/// error-zone boundaries, which are considered uncaught.
///
/// This also exists for cases where our initialization requires access to
/// a `Context` object, which is only set up inside the zone.
///
/// These issues do not pertain to real code and are a test-only concern,
/// because in real code, the zone is set up in `main()`.
///
/// See also: [runZoned]
void initialize({
int devicePort,
Duration throttleDuration = const Duration(milliseconds: 200),
}) {
logReader = MockDeviceLogReader();
discoverer = ProtocolDiscovery.observatory(
logReader,
ipv6: false,
hostPort: null,
devicePort: devicePort,
throttleDuration: throttleDuration,
);
}
testUsingContext('returns non-null uri future', () async {
initialize();
expect(discoverer.uri, isNotNull);
});
group('no port forwarding', () { group('no port forwarding', () {
int devicePort;
/// Performs test set-up functionality that must be performed as part of
/// the `test()` pass and not part of the `setUp()` pass.
///
/// This exists to make sure we're not creating an error that tries to
/// cross an error-zone boundary. Our use of `testUsingContext()` runs the
/// test code inside an error zone, but the `setUp()` code is not run in
/// any zone. This creates the potential for errors that try to cross
/// error-zone boundaries, which are considered uncaught.
///
/// This also exists for cases where our initialization requires access to
/// a `Context` object, which is only set up inside the zone.
///
/// These issues do not pertain to real code and are a test-only concern,
/// because in real code, the zone is set up in `main()`.
///
/// See also: [runZoned]
void initialize() {
logReader = MockDeviceLogReader();
discoverer = ProtocolDiscovery.observatory(logReader, ipv6: false, hostPort: null, devicePort: devicePort);
}
tearDown(() { tearDown(() {
discoverer.cancel(); discoverer.cancel();
logReader.dispose(); logReader.dispose();
}); });
testUsingContext('returns non-null uri future', () async {
initialize();
expect(discoverer.uri, isNotNull);
});
testUsingContext('discovers uri if logs already produced output', () async { testUsingContext('discovers uri if logs already produced output', () async {
initialize(); initialize();
logReader.addLine('HELLO WORLD'); logReader.addLine('HELLO WORLD');
...@@ -86,7 +78,9 @@ void main() { ...@@ -86,7 +78,9 @@ void main() {
testUsingContext('uri throws if logs produce bad line', () async { testUsingContext('uri throws if logs produce bad line', () async {
initialize(); initialize();
logReader.addLine('Observatory listening on http://127.0.0.1:apple'); Timer.run(() {
logReader.addLine('Observatory listening on http://127.0.0.1:apple');
});
expect(discoverer.uri, throwsA(isFormatException)); expect(discoverer.uri, throwsA(isFormatException));
}); });
...@@ -129,7 +123,8 @@ void main() { ...@@ -129,7 +123,8 @@ void main() {
}); });
testUsingContext('skips uri if port does not match the requested vmservice - requested last', () async { testUsingContext('skips uri if port does not match the requested vmservice - requested last', () async {
initialize(devicePort: 12346); devicePort = 12346;
initialize();
final Future<Uri> uriFuture = discoverer.uri; final Future<Uri> uriFuture = discoverer.uri;
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
...@@ -139,7 +134,8 @@ void main() { ...@@ -139,7 +134,8 @@ void main() {
}); });
testUsingContext('skips uri if port does not match the requested vmservice - requested first', () async { testUsingContext('skips uri if port does not match the requested vmservice - requested first', () async {
initialize(devicePort: 12346); devicePort = 12346;
initialize();
final Future<Uri> uriFuture = discoverer.uri; final Future<Uri> uriFuture = discoverer.uri;
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');
...@@ -147,86 +143,6 @@ void main() { ...@@ -147,86 +143,6 @@ void main() {
expect(uri.port, 12346); expect(uri.port, 12346);
expect('$uri', 'http://127.0.0.1:12346/PTwjm8Ii8qg=/'); expect('$uri', 'http://127.0.0.1:12346/PTwjm8Ii8qg=/');
}); });
testUsingContext('first uri in the stream is the last one from the log', () async {
initialize();
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');
final Uri uri = await discoverer.uris.first;
expect(uri.port, 12345);
expect('$uri', 'http://127.0.0.1:12345/PTwjm8Ii8qg=/');
});
testUsingContext('first uri in the stream is the last one from the log that matches the port', () async {
initialize(devicePort: 12345);
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12344/PTwjm8Ii8qg=/');
final Uri uri = await discoverer.uris.first;
expect(uri.port, 12345);
expect('$uri', 'http://127.0.0.1:12345/PTwjm8Ii8qg=/');
});
testUsingContext('uris in the stream are throttled', () async {
const Duration kThrottleDuration = Duration(milliseconds: 10);
FakeAsync().run((FakeAsync time) {
initialize(throttleDuration: kThrottleDuration);
final List<Uri> discoveredUris = <Uri>[];
discoverer.uris.listen((Uri uri) {
discoveredUris.add(uri);
});
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');
time.elapse(kThrottleDuration);
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12344/PTwjm8Ii8qg=/');
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12343/PTwjm8Ii8qg=/');
time.elapse(kThrottleDuration);
expect(discoveredUris.length, 2);
expect(discoveredUris[0].port, 12345);
expect('${discoveredUris[0]}', 'http://127.0.0.1:12345/PTwjm8Ii8qg=/');
expect(discoveredUris[1].port, 12343);
expect('${discoveredUris[1]}', 'http://127.0.0.1:12343/PTwjm8Ii8qg=/');
});
});
testUsingContext('uris in the stream are throttled when they match the port', () async {
const Duration kThrottleTimeInMilliseconds = Duration(milliseconds: 10);
FakeAsync().run((FakeAsync time) {
initialize(
devicePort: 12345,
throttleDuration: kThrottleTimeInMilliseconds,
);
final List<Uri> discoveredUris = <Uri>[];
discoverer.uris.listen((Uri uri) {
discoveredUris.add(uri);
});
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');
time.elapse(kThrottleTimeInMilliseconds);
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qc=/');
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12344/PTwjm8Ii8qf=/');
time.elapse(kThrottleTimeInMilliseconds);
expect(discoveredUris.length, 2);
expect(discoveredUris[0].port, 12345);
expect('${discoveredUris[0]}', 'http://127.0.0.1:12345/PTwjm8Ii8qg=/');
expect(discoveredUris[1].port, 12345);
expect('${discoveredUris[1]}', 'http://127.0.0.1:12345/PTwjm8Ii8qc=/');
});
});
}); });
group('port forwarding', () { group('port forwarding', () {
...@@ -248,7 +164,7 @@ void main() { ...@@ -248,7 +164,7 @@ void main() {
expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/'); expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/');
await discoverer.cancel(); await discoverer.cancel();
await logReader.dispose(); logReader.dispose();
}); });
testUsingContext('specified port', () async { testUsingContext('specified port', () async {
...@@ -269,7 +185,7 @@ void main() { ...@@ -269,7 +185,7 @@ void main() {
expect('$uri', 'http://127.0.0.1:1243/PTwjm8Ii8qg=/'); expect('$uri', 'http://127.0.0.1:1243/PTwjm8Ii8qg=/');
await discoverer.cancel(); await discoverer.cancel();
await logReader.dispose(); logReader.dispose();
}); });
testUsingContext('specified port zero', () async { testUsingContext('specified port zero', () async {
...@@ -290,7 +206,7 @@ void main() { ...@@ -290,7 +206,7 @@ void main() {
expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/'); expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/');
await discoverer.cancel(); await discoverer.cancel();
await logReader.dispose(); logReader.dispose();
}); });
testUsingContext('ipv6', () async { testUsingContext('ipv6', () async {
...@@ -311,7 +227,7 @@ void main() { ...@@ -311,7 +227,7 @@ void main() {
expect('$uri', 'http://[::1]:54777/PTwjm8Ii8qg=/'); expect('$uri', 'http://[::1]:54777/PTwjm8Ii8qg=/');
await discoverer.cancel(); await discoverer.cancel();
await logReader.dispose(); logReader.dispose();
}); });
testUsingContext('ipv6 with Ascii Escape code', () async { testUsingContext('ipv6 with Ascii Escape code', () async {
...@@ -332,7 +248,7 @@ void main() { ...@@ -332,7 +248,7 @@ void main() {
expect('$uri', 'http://[::1]:54777/PTwjm8Ii8qg=/'); expect('$uri', 'http://[::1]:54777/PTwjm8Ii8qg=/');
await discoverer.cancel(); await discoverer.cancel();
await logReader.dispose(); logReader.dispose();
}); });
}); });
}); });
......
...@@ -95,7 +95,9 @@ void main() { ...@@ -95,7 +95,9 @@ void main() {
when(mockFlutterView.uiIsolate).thenReturn(mockIsolate); when(mockFlutterView.uiIsolate).thenReturn(mockIsolate);
when(mockFlutterView.runFromSource(any, any, any)).thenAnswer((Invocation invocation) async {}); when(mockFlutterView.runFromSource(any, any, any)).thenAnswer((Invocation invocation) async {});
when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { }); when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { });
when(mockFlutterDevice.observatoryUris).thenAnswer((_) => Stream<Uri>.value(testUri)); when(mockFlutterDevice.observatoryUris).thenReturn(<Uri>[
testUri,
]);
when(mockFlutterDevice.connect( when(mockFlutterDevice.connect(
reloadSources: anyNamed('reloadSources'), reloadSources: anyNamed('reloadSources'),
restart: anyNamed('restart'), restart: anyNamed('restart'),
...@@ -634,7 +636,7 @@ void main() { ...@@ -634,7 +636,7 @@ void main() {
final TestFlutterDevice flutterDevice = TestFlutterDevice( final TestFlutterDevice flutterDevice = TestFlutterDevice(
mockDevice, mockDevice,
<FlutterView>[], <FlutterView>[],
observatoryUris: Stream<Uri>.value(testUri), observatoryUris: <Uri>[ testUri ]
); );
await flutterDevice.connect(); await flutterDevice.connect();
...@@ -655,7 +657,7 @@ class MockUsage extends Mock implements Usage {} ...@@ -655,7 +657,7 @@ class MockUsage extends Mock implements Usage {}
class MockProcessManager extends Mock implements ProcessManager {} class MockProcessManager extends Mock implements ProcessManager {}
class MockServiceEvent extends Mock implements ServiceEvent {} class MockServiceEvent extends Mock implements ServiceEvent {}
class TestFlutterDevice extends FlutterDevice { class TestFlutterDevice extends FlutterDevice {
TestFlutterDevice(Device device, this.views, { Stream<Uri> observatoryUris }) TestFlutterDevice(Device device, this.views, { List<Uri> observatoryUris })
: super(device, buildMode: BuildMode.debug, trackWidgetCreation: false) { : super(device, buildMode: BuildMode.debug, trackWidgetCreation: false) {
_observatoryUris = observatoryUris; _observatoryUris = observatoryUris;
} }
...@@ -664,8 +666,8 @@ class TestFlutterDevice extends FlutterDevice { ...@@ -664,8 +666,8 @@ class TestFlutterDevice extends FlutterDevice {
final List<FlutterView> views; final List<FlutterView> views;
@override @override
Stream<Uri> get observatoryUris => _observatoryUris; List<Uri> get observatoryUris => _observatoryUris;
Stream<Uri> _observatoryUris; List<Uri> _observatoryUris;
} }
class ThrowingForwardingFileSystem extends ForwardingFileSystem { class ThrowingForwardingFileSystem extends ForwardingFileSystem {
......
...@@ -533,12 +533,6 @@ class MockAndroidDevice extends Mock implements AndroidDevice { ...@@ -533,12 +533,6 @@ class MockAndroidDevice extends Mock implements AndroidDevice {
@override @override
bool isSupported() => true; bool isSupported() => true;
@override
bool get supportsHotRestart => true;
@override
bool get supportsFlutterExit => false;
@override @override
bool isSupportedForProject(FlutterProject flutterProject) => true; bool isSupportedForProject(FlutterProject flutterProject) => true;
} }
...@@ -569,33 +563,16 @@ class MockDeviceLogReader extends DeviceLogReader { ...@@ -569,33 +563,16 @@ class MockDeviceLogReader extends DeviceLogReader {
@override @override
String get name => 'MockLogReader'; String get name => 'MockLogReader';
StreamController<String> _cachedLinesController; final StreamController<String> _linesController = StreamController<String>.broadcast();
final List<String> _lineQueue = <String>[];
StreamController<String> get _linesController {
_cachedLinesController ??= StreamController<String>
.broadcast(onListen: () {
_lineQueue.forEach(_linesController.add);
_lineQueue.clear();
});
return _cachedLinesController;
}
@override @override
Stream<String> get logLines => _linesController.stream; Stream<String> get logLines => _linesController.stream;
void addLine(String line) { void addLine(String line) => _linesController.add(line);
if (_linesController.hasListener) {
_linesController.add(line);
} else {
_lineQueue.add(line);
}
}
@override @override
Future<void> dispose() async { void dispose() {
_lineQueue.clear(); _linesController.close();
await _linesController.close();
} }
} }
......
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