// Copyright 2014 The Flutter 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 'package:fake_async/fake_async.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/device_port_forwarder.dart'; import 'package:flutter_tools/src/protocol_discovery.dart'; import '../src/common.dart'; import '../src/fake_devices.dart'; void main() { group('service_protocol discovery', () { late FakeDeviceLogReader logReader; late ProtocolDiscovery discoverer; setUp(() { logReader = FakeDeviceLogReader(); discoverer = ProtocolDiscovery.observatory( logReader, ipv6: false, throttleDuration: const Duration(milliseconds: 5), logger: BufferLogger.test(), ); }); testWithoutContext('returns non-null uri future', () async { expect(discoverer.uri, isNotNull); }); group('no port forwarding', () { tearDown(() { discoverer.cancel(); logReader.dispose(); }); testWithoutContext('discovers uri if logs already produced output', () async { logReader.addLine('HELLO WORLD'); logReader.addLine('The Dart VM service is 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'); }); testWithoutContext('does not discover uri with no host', () async { final Future<Uri?> pendingUri = discoverer.uri; logReader.addLine('The Dart VM service is listening on http12asdasdsd9999'); await Future<void>.delayed(const Duration(milliseconds: 10)); logReader.addLine('The Dart VM service is listening on http://127.0.0.1:9999'); await Future<void>.delayed(Duration.zero); final Uri uri = (await pendingUri)!; expect(uri, isNotNull); expect(uri.port, 9999); expect('$uri', 'http://127.0.0.1:9999'); }); testWithoutContext('discovers uri if logs already produced output and no listener is attached', () async { logReader.addLine('HELLO WORLD'); logReader.addLine('The Dart VM service is listening on http://127.0.0.1:9999'); await Future<void>.delayed(Duration.zero); final Uri uri = (await discoverer.uri)!; expect(uri, isNotNull); expect(uri.port, 9999); expect('$uri', 'http://127.0.0.1:9999'); }); testWithoutContext('uri throws if logs produce bad line and no listener is attached', () async { logReader.addLine('The Dart VM service is listening on http://127.0.0.1:apple'); await Future<void>.delayed(Duration.zero); expect(discoverer.uri, throwsA(isFormatException)); }); testWithoutContext('discovers uri if logs not yet produced output', () async { final Future<Uri?> uriFuture = discoverer.uri; logReader.addLine('The Dart VM service is 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'); }); testWithoutContext('discovers uri with Ascii Esc code', () async { logReader.addLine('The Dart VM service is listening on http://127.0.0.1:3333\x1b['); final Uri uri = (await discoverer.uri)!; expect(uri.port, 3333); expect('$uri', 'http://127.0.0.1:3333'); }); testWithoutContext('uri throws if logs produce bad line', () async { logReader.addLine('The Dart VM service is listening on http://127.0.0.1:apple'); expect(discoverer.uri, throwsA(isFormatException)); }); testWithoutContext('uri is null when the log reader closes early', () async { final Future<Uri?> uriFuture = discoverer.uri; await logReader.dispose(); expect(await uriFuture, isNull); }); testWithoutContext('uri waits for correct log line', () async { final Future<Uri?> 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); }); testWithoutContext('discovers uri if log line contains Android prefix', () async { logReader.addLine('I/flutter : The Dart VM service is 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'); }); testWithoutContext('discovers uri if log line contains auth key', () async { final Future<Uri?> uriFuture = discoverer.uri; logReader.addLine('I/flutter : The Dart VM service is 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=/'); }); testWithoutContext('discovers uri if log line contains non-localhost', () async { final Future<Uri?> uriFuture = discoverer.uri; logReader.addLine('I/flutter : The Dart VM service is 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=/'); }); testWithoutContext('skips uri if port does not match the requested vmservice - requested last', () async { discoverer = ProtocolDiscovery.observatory( logReader, ipv6: false, devicePort: 12346, throttleDuration: const Duration(milliseconds: 200), logger: BufferLogger.test(), ); final Future<Uri?> uriFuture = discoverer.uri; logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/'); final Uri uri = (await uriFuture)!; expect(uri.port, 12346); expect('$uri', 'http://127.0.0.1:12346/PTwjm8Ii8qg=/'); }); testWithoutContext('skips uri if port does not match the requested vmservice - requested first', () async { discoverer = ProtocolDiscovery.observatory( logReader, ipv6: false, devicePort: 12346, throttleDuration: const Duration(milliseconds: 200), logger: BufferLogger.test(), ); final Future<Uri?> uriFuture = discoverer.uri; logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/'); final Uri uri = (await uriFuture)!; expect(uri.port, 12346); expect('$uri', 'http://127.0.0.1:12346/PTwjm8Ii8qg=/'); }); testWithoutContext('first uri in the stream is the last one from the log', () async { logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : The Dart VM service is 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=/'); }); testWithoutContext('first uri in the stream is the last one from the log that matches the port', () async { discoverer = ProtocolDiscovery.observatory( logReader, ipv6: false, devicePort: 12345, throttleDuration: const Duration(milliseconds: 200), logger: BufferLogger.test(), ); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : The Dart VM service is 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=/'); }); testWithoutContext('protocol discovery does not crash if the log reader is closed while delaying', () async { discoverer = ProtocolDiscovery.observatory( logReader, ipv6: false, devicePort: 12346, throttleDuration: const Duration(milliseconds: 10), logger: BufferLogger.test(), ); final Future<List<Uri>> results = discoverer.uris.toList(); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/'); await logReader.dispose(); // Give time for throttle to finish. await Future<void>.delayed(const Duration(milliseconds: 11)); expect(await results, isEmpty); }); testWithoutContext('uris in the stream are throttled', () async { const Duration kThrottleDuration = Duration(milliseconds: 10); FakeAsync().run((FakeAsync time) { discoverer = ProtocolDiscovery.observatory( logReader, ipv6: false, throttleDuration: kThrottleDuration, logger: BufferLogger.test(), ); final List<Uri> discoveredUris = <Uri>[]; discoverer.uris.listen((Uri uri) { discoveredUris.add(uri); }); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/'); time.elapse(kThrottleDuration); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12344/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : The Dart VM service is 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=/'); }); }); testWithoutContext('uris in the stream are throttled when they match the port', () async { const Duration kThrottleTimeInMilliseconds = Duration(milliseconds: 10); FakeAsync().run((FakeAsync time) { discoverer = ProtocolDiscovery.observatory( logReader, ipv6: false, devicePort: 12345, throttleDuration: kThrottleTimeInMilliseconds, logger: BufferLogger.test(), ); final List<Uri> discoveredUris = <Uri>[]; discoverer.uris.listen((Uri uri) { discoveredUris.add(uri); }); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/'); time.elapse(kThrottleTimeInMilliseconds); logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:12345/PTwjm8Ii8qc=/'); logReader.addLine('I/flutter : The Dart VM service is 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', () { testWithoutContext('default port', () async { final FakeDeviceLogReader logReader = FakeDeviceLogReader(); final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory( logReader, portForwarder: MockPortForwarder(99), ipv6: false, logger: BufferLogger.test(), ); // Get next port future. final Future<Uri?> nextUri = discoverer.uri; logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/'); final Uri uri = (await nextUri)!; expect(uri.port, 99); expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/'); await discoverer.cancel(); await logReader.dispose(); }); testWithoutContext('specified port', () async { final FakeDeviceLogReader logReader = FakeDeviceLogReader(); final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory( logReader, portForwarder: MockPortForwarder(99), hostPort: 1243, ipv6: false, logger: BufferLogger.test(), ); // Get next port future. final Future<Uri?> nextUri = discoverer.uri; logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/'); final Uri uri = (await nextUri)!; expect(uri.port, 1243); expect('$uri', 'http://127.0.0.1:1243/PTwjm8Ii8qg=/'); await discoverer.cancel(); await logReader.dispose(); }); testWithoutContext('specified port zero', () async { final FakeDeviceLogReader logReader = FakeDeviceLogReader(); final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory( logReader, portForwarder: MockPortForwarder(99), hostPort: 0, ipv6: false, logger: BufferLogger.test(), ); // Get next port future. final Future<Uri?> nextUri = discoverer.uri; logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/'); final Uri uri = (await nextUri)!; expect(uri.port, 99); expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/'); await discoverer.cancel(); await logReader.dispose(); }); testWithoutContext('ipv6', () async { final FakeDeviceLogReader logReader = FakeDeviceLogReader(); final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory( logReader, portForwarder: MockPortForwarder(99), hostPort: 54777, ipv6: true, logger: BufferLogger.test(), ); // Get next port future. final Future<Uri?> nextUri = discoverer.uri; logReader.addLine('I/flutter : The Dart VM service is listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/'); final Uri uri = (await nextUri)!; expect(uri.port, 54777); expect('$uri', 'http://[::1]:54777/PTwjm8Ii8qg=/'); await discoverer.cancel(); await logReader.dispose(); }); testWithoutContext('ipv6 with Ascii Escape code', () async { final FakeDeviceLogReader logReader = FakeDeviceLogReader(); final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory( logReader, portForwarder: MockPortForwarder(99), hostPort: 54777, ipv6: true, logger: BufferLogger.test(), ); // Get next port future. final Future<Uri?> nextUri = discoverer.uri; logReader.addLine('I/flutter : The Dart VM service is listening on http://[::1]:54777/PTwjm8Ii8qg=/\x1b['); final Uri uri = (await nextUri)!; expect(uri.port, 54777); expect('$uri', 'http://[::1]:54777/PTwjm8Ii8qg=/'); await discoverer.cancel(); await logReader.dispose(); }); }); }); } class MockPortForwarder extends DevicePortForwarder { MockPortForwarder([this.availablePort]); final int? availablePort; @override Future<int> forward(int devicePort, { int? hostPort }) async { hostPort ??= 0; if (hostPort == 0) { return availablePort!; } return hostPort; } @override List<ForwardedPort> get forwardedPorts => throw UnimplementedError(); @override Future<void> unforward(ForwardedPort forwardedPort) { throw UnimplementedError(); } @override Future<void> dispose() async {} }