diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 2556eca6026..3df613eb452 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -407,12 +407,12 @@ class AndroidDevice extends Device { if (debuggingOptions.buildMode == BuildMode.debug) { final List deviceUris = await Future.wait( - >[observatoryDiscovery.nextUri(), diagnosticDiscovery.nextUri()] + >[observatoryDiscovery.uri, diagnosticDiscovery.uri] ); observatoryUri = deviceUris[0]; diagnosticUri = deviceUris[1]; } else if (debuggingOptions.buildMode == BuildMode.profile) { - observatoryUri = await observatoryDiscovery.nextUri(); + observatoryUri = await observatoryDiscovery.uri; } return new LaunchResult.succeeded( diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 9cb85566b14..8710c6f7370 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -281,10 +281,10 @@ class IOSDevice extends Device { final ProtocolDiscovery diagnosticDiscovery = new ProtocolDiscovery.diagnosticService( getLogReader(app: app), portForwarder: portForwarder, hostPort: debuggingOptions.diagnosticPort); - final Future forwardObsUri = observatoryDiscovery.nextUri(); + final Future forwardObsUri = observatoryDiscovery.uri; Future forwardDiagUri; if (debuggingOptions.buildMode == BuildMode.debug) { - forwardDiagUri = diagnosticDiscovery.nextUri(); + forwardDiagUri = diagnosticDiscovery.uri; } else { forwardDiagUri = new Future.value(null); } diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index c4721aabbb4..d4ec64f4f50 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -489,7 +489,7 @@ class IOSSimulator extends Device { printTrace('Waiting for observatory port to be available...'); try { - final Uri deviceUri = await observatoryDiscovery.nextUri(); + final Uri deviceUri = await observatoryDiscovery.uri; return new LaunchResult.succeeded(observatoryUri: deviceUri); } catch (error) { printError('Error waiting for a debug connection: $error'); diff --git a/packages/flutter_tools/lib/src/protocol_discovery.dart b/packages/flutter_tools/lib/src/protocol_discovery.dart index c4a6e12635c..d3f3654abe4 100644 --- a/packages/flutter_tools/lib/src/protocol_discovery.dart +++ b/packages/flutter_tools/lib/src/protocol_discovery.dart @@ -9,72 +9,62 @@ import 'base/port_scanner.dart'; import 'device.dart'; import 'globals.dart'; -/// Discover service protocol on a device -/// and forward the service protocol device port to the host. +/// Discovers a specific service protocol on a device, and forward the service +/// protocol device port to the host. class ProtocolDiscovery { - /// [logReader] - a [DeviceLogReader] to look for service messages in. - ProtocolDiscovery(DeviceLogReader logReader, String serviceName, - {this.portForwarder, this.hostPort, this.defaultHostPort}) - : _logReader = logReader, _serviceName = serviceName { + ProtocolDiscovery._( + DeviceLogReader logReader, + String serviceName, { + this.portForwarder, + this.hostPort, + this.defaultHostPort, + }) : _logReader = logReader, _serviceName = serviceName { assert(_logReader != null); - _subscription = _logReader.logLines.listen(_onLine); assert(portForwarder == null || defaultHostPort != null); + _deviceLogSubscription = _logReader.logLines.listen(_onLine); } factory ProtocolDiscovery.observatory(DeviceLogReader logReader, {DevicePortForwarder portForwarder, int hostPort}) => - new ProtocolDiscovery(logReader, kObservatoryService, + new ProtocolDiscovery._(logReader, _kObservatoryService, portForwarder: portForwarder, hostPort: hostPort, defaultHostPort: kDefaultObservatoryPort); factory ProtocolDiscovery.diagnosticService(DeviceLogReader logReader, {DevicePortForwarder portForwarder, int hostPort}) => - new ProtocolDiscovery(logReader, kDiagnosticService, + new ProtocolDiscovery._(logReader, _kDiagnosticService, portForwarder: portForwarder, hostPort: hostPort, defaultHostPort: kDefaultDiagnosticPort); - static const String kObservatoryService = 'Observatory'; - static const String kDiagnosticService = 'Diagnostic server'; + static const String _kObservatoryService = 'Observatory'; + static const String _kDiagnosticService = 'Diagnostic server'; final DeviceLogReader _logReader; final String _serviceName; final DevicePortForwarder portForwarder; - int hostPort; + final int hostPort; final int defaultHostPort; + final Completer _completer = new Completer(); - Completer _completer = new Completer(); - StreamSubscription _subscription; + StreamSubscription _deviceLogSubscription; - /// The [Future] returned by this function will complete when the next service - /// Uri is found. - Future nextUri() async { - final Uri deviceUri = await _completer.future.timeout( - const Duration(seconds: 60), onTimeout: () { - throwToolExit('Timeout while attempting to retrieve Uri for $_serviceName'); - } - ); - printTrace('$_serviceName Uri on device: $deviceUri'); - Uri hostUri; - if (portForwarder != null) { - final int devicePort = deviceUri.port; - hostPort ??= await portScanner.findPreferredPort(defaultHostPort); - hostPort = await portForwarder - .forward(devicePort, hostPort: hostPort) - .timeout(const Duration(seconds: 60), onTimeout: () { - throwToolExit('Timeout while atempting to foward device port $devicePort for $_serviceName'); - }); - printTrace('Forwarded host port $hostPort to device port $devicePort for $_serviceName'); - hostUri = deviceUri.replace(port: hostPort); - } else { - hostUri = deviceUri; - } - return hostUri; + /// The discovered service URI. + Future get uri { + return _completer.future + .timeout(const Duration(seconds: 60), onTimeout: () { + throwToolExit('Timeout while attempting to retrieve Uri for $_serviceName'); + }).whenComplete(() { + _stopScrapingLogs(); + }); } - void cancel() { - _subscription.cancel(); + Future cancel() => _stopScrapingLogs(); + + Future _stopScrapingLogs() async { + await _deviceLogSubscription?.cancel(); + _deviceLogSubscription = null; } void _onLine(String line) { @@ -84,19 +74,35 @@ class ProtocolDiscovery { if (index >= 0) { try { uri = Uri.parse(line.substring(index + prefix.length)); - } catch (_) { - // Ignore errors. + } catch (error) { + _stopScrapingLogs(); + _completer.completeError(error); } } - if (uri != null) - _located(uri); + + if (uri != null) { + assert(!_completer.isCompleted); + _stopScrapingLogs(); + _completer.complete(_forwardPort(uri)); + } } - void _located(Uri uri) { - assert(_completer != null); - assert(!_completer.isCompleted); + Future _forwardPort(Uri deviceUri) async { + printTrace('$_serviceName Uri on device: $deviceUri'); + Uri hostUri = deviceUri; - _completer.complete(uri); - _completer = new Completer(); + if (portForwarder != null) { + final int devicePort = deviceUri.port; + int hostPort = this.hostPort ?? await portScanner.findPreferredPort(defaultHostPort); + hostPort = await portForwarder + .forward(devicePort, hostPort: hostPort) + .timeout(const Duration(seconds: 60), onTimeout: () { + throwToolExit('Timeout while atempting to foward device port $devicePort for $_serviceName'); + }); + printTrace('Forwarded host port $hostPort to device port $devicePort for $_serviceName'); + hostUri = deviceUri.replace(port: hostPort); + } + + return hostUri; } } diff --git a/packages/flutter_tools/test/protocol_discovery_test.dart b/packages/flutter_tools/test/protocol_discovery_test.dart index ed743973845..d9237e05453 100644 --- a/packages/flutter_tools/test/protocol_discovery_test.dart +++ b/packages/flutter_tools/test/protocol_discovery_test.dart @@ -13,74 +13,113 @@ import 'src/mocks.dart'; void main() { group('service_protocol discovery', () { - testUsingContext('no port forwarding', () async { - final MockDeviceLogReader logReader = new MockDeviceLogReader(); - final ProtocolDiscovery discoverer = - new ProtocolDiscovery(logReader, ProtocolDiscovery.kObservatoryService); + MockDeviceLogReader logReader; + ProtocolDiscovery discoverer; - // Get next port future. - Future nextUri = discoverer.nextUri(); - expect(nextUri, isNotNull); + group('no port forwarding', () { + /// 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. + /// + /// Note that these issues do not pertain to real code and are a test-only + /// concern, since in real code, the zone is set-up in `main()`. + /// + /// See also: [runZoned] + void initialize() { + logReader = new MockDeviceLogReader(); + discoverer = new ProtocolDiscovery.observatory(logReader); + } - // Inject some lines. - logReader.addLine('HELLO WORLD'); - logReader.addLine('Observatory listening on http://127.0.0.1:9999'); - // Await the port. - Uri uri = await nextUri; - expect(uri.port, 9999); - expect('$uri', 'http://127.0.0.1:9999'); + tearDown(() { + discoverer.cancel(); + logReader.dispose(); + }); - // Get next port future. - nextUri = discoverer.nextUri(); - logReader.addLine('Observatory listening on http://127.0.0.1:3333'); - uri = await nextUri; - expect(uri.port, 3333); - expect('$uri', 'http://127.0.0.1:3333'); + testUsingContext('returns non-null uri future', () async { + initialize(); + expect(discoverer.uri, isNotNull); + }); - // Get next port future. - nextUri = discoverer.nextUri(); - // Inject a bad line. - logReader.addLine('Observatory listening on http://127.0.0.1:apple'); - final Uri timeoutUri = Uri.parse('http://timeout'); - final Uri actualUri = await nextUri.timeout( - const Duration(milliseconds: 100), onTimeout: () => timeoutUri); - expect(actualUri, timeoutUri); + testUsingContext('discovers uri if logs already produced output', () async { + initialize(); + logReader.addLine('HELLO WORLD'); + logReader.addLine('Observatory listening on http://127.0.0.1:9999'); + final Uri uri = await discoverer.uri; + expect(uri.port, 9999); + expect('$uri', 'http://127.0.0.1:9999'); + }); - // Get next port future. - nextUri = discoverer.nextUri(); - logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:52584'); - uri = await nextUri; - expect(uri.port, 52584); - expect('$uri', 'http://127.0.0.1:52584'); + testUsingContext('discovers uri if logs not yet produced output', () async { + initialize(); + final Future uriFuture = discoverer.uri; + logReader.addLine('Observatory listening on http://127.0.0.1:3333'); + final Uri uri = await uriFuture; + expect(uri.port, 3333); + expect('$uri', 'http://127.0.0.1:3333'); + }); - // Get next port future. - nextUri = discoverer.nextUri(); - logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/'); - uri = await nextUri; - expect(uri.port, 54804); - expect('$uri', 'http://127.0.0.1:54804/PTwjm8Ii8qg=/'); + testUsingContext('uri throws if logs produce bad line', () async { + initialize(); + Timer.run(() { + logReader.addLine('Observatory listening on http://127.0.0.1:apple'); + }); + expect(discoverer.uri, throwsA(isFormatException)); + }); - // Get next port future. - nextUri = discoverer.nextUri(); - logReader.addLine('I/flutter : Observatory listening on http://somehost:54804/PTwjm8Ii8qg=/'); - uri = await nextUri; - expect(uri.port, 54804); - expect('$uri', 'http://somehost:54804/PTwjm8Ii8qg=/'); + testUsingContext('uri waits for correct log line', () async { + initialize(); + final Future uriFuture = discoverer.uri; + logReader.addLine('Observatory not listening...'); + final Uri timeoutUri = Uri.parse('http://timeout'); + final Uri actualUri = await uriFuture.timeout( + const Duration(milliseconds: 100), onTimeout: () => timeoutUri); + expect(actualUri, timeoutUri); + }); - discoverer.cancel(); - logReader.dispose(); + testUsingContext('discovers uri if log line contains Android prefix', () async { + initialize(); + logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:52584'); + final Uri uri = await discoverer.uri; + expect(uri.port, 52584); + expect('$uri', 'http://127.0.0.1:52584'); + }); + + testUsingContext('discovers uri if log line contains auth key', () async { + initialize(); + final Future uriFuture = discoverer.uri; + logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/'); + final Uri uri = await uriFuture; + expect(uri.port, 54804); + expect('$uri', 'http://127.0.0.1:54804/PTwjm8Ii8qg=/'); + }); + + testUsingContext('discovers uri if log line contains non-localhost', () async { + initialize(); + final Future uriFuture = discoverer.uri; + logReader.addLine('I/flutter : Observatory listening on http://somehost:54804/PTwjm8Ii8qg=/'); + final Uri uri = await uriFuture; + expect(uri.port, 54804); + expect('$uri', 'http://somehost:54804/PTwjm8Ii8qg=/'); + }); }); testUsingContext('port forwarding - default port', () async { final MockDeviceLogReader logReader = new MockDeviceLogReader(); - final ProtocolDiscovery discoverer = new ProtocolDiscovery( + final ProtocolDiscovery discoverer = new ProtocolDiscovery.observatory( logReader, - ProtocolDiscovery.kObservatoryService, portForwarder: new MockPortForwarder(99), - defaultHostPort: 54777); + hostPort: 54777); // Get next port future. - final Future nextUri = discoverer.nextUri(); + final Future nextUri = discoverer.uri; logReader.addLine('I/flutter : Observatory listening on http://somehost:54804/PTwjm8Ii8qg=/'); final Uri uri = await nextUri; expect(uri.port, 54777); @@ -92,15 +131,13 @@ void main() { testUsingContext('port forwarding - specified port', () async { final MockDeviceLogReader logReader = new MockDeviceLogReader(); - final ProtocolDiscovery discoverer = new ProtocolDiscovery( + final ProtocolDiscovery discoverer = new ProtocolDiscovery.observatory( logReader, - ProtocolDiscovery.kObservatoryService, portForwarder: new MockPortForwarder(99), - hostPort: 1243, - defaultHostPort: 192); + hostPort: 1243); // Get next port future. - final Future nextUri = discoverer.nextUri(); + final Future nextUri = discoverer.uri; logReader.addLine('I/flutter : Observatory listening on http://somehost:54804/PTwjm8Ii8qg=/'); final Uri uri = await nextUri; expect(uri.port, 1243);