V1.9.1 hotfixes (#41229)
Bump version of just package:multicast_dns (#41207)
Implement mdns for flutter run (#40447)
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index c546075..aef335f 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -919,7 +919,7 @@
print('$redLine');
exit(1);
}
- final RegExp pattern = RegExp(r'^\d+\.\d+\.\d+(?:|-pre\.\d+|\+hotfix\.\d+)$');
+ final RegExp pattern = RegExp(r'^\d+\.\d+\.\d+(?:|-pre\.\d+|\+hotfix\.\d+)(-pre\.\d+)?$');
if (!version.contains(pattern)) {
print('$redLine');
print('The version logic generated an invalid version string.');
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index dd68a47..20a23a3 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -4,8 +4,6 @@
import 'dart:async';
-import 'package:multicast_dns/multicast_dns.dart';
-
import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart';
@@ -20,6 +18,7 @@
import '../globals.dart';
import '../ios/devices.dart';
import '../ios/simulators.dart';
+import '../mdns_discovery.dart';
import '../project.dart';
import '../protocol_discovery.dart';
import '../resident_runner.dart';
@@ -204,7 +203,7 @@
final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;
bool attachLogger = false;
- if (devicePort == null && debugUri == null) {
+ if (devicePort == null && debugUri == null) {
if (device is FuchsiaDevice) {
attachLogger = true;
final String module = argResults['module'];
@@ -225,10 +224,11 @@
rethrow;
}
} else if ((device is IOSDevice) || (device is IOSSimulator)) {
- final MDnsObservatoryDiscoveryResult result = await MDnsObservatoryDiscovery().query(applicationId: appId);
- if (result != null) {
- observatoryUri = await _buildObservatoryUri(device, hostname, result.port, result.authCode);
- }
+ observatoryUri = await MDnsObservatoryDiscovery.instance.getObservatoryUri(
+ appId,
+ device,
+ usesIpv6,
+ );
}
// If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery.
if (observatoryUri == null) {
@@ -250,8 +250,13 @@
}
}
} else {
- observatoryUri = await _buildObservatoryUri(device,
- debugUri?.host ?? hostname, devicePort ?? debugUri.port, debugUri?.path);
+ observatoryUri = await buildObservatoryUri(
+ device,
+ debugUri?.host ?? hostname,
+ devicePort ?? debugUri.port,
+ observatoryPort,
+ debugUri?.path,
+ );
}
try {
final bool useHot = getBuildInfo().isDebug;
@@ -333,22 +338,6 @@
}
Future<void> _validateArguments() async { }
-
- Future<Uri> _buildObservatoryUri(Device device,
- String host, int devicePort, [String authCode]) async {
- String path = '/';
- if (authCode != null) {
- path = authCode;
- }
- // Not having a trailing slash can cause problems in some situations.
- // Ensure that there's one present.
- if (!path.endsWith('/')) {
- path += '/';
- }
- final int localPort = observatoryPort
- ?? await device.portForwarder.forward(devicePort);
- return Uri(scheme: 'http', host: host, port: localPort, path: path);
- }
}
class HotRunnerFactory {
@@ -381,132 +370,3 @@
ipv6: ipv6,
);
}
-
-class MDnsObservatoryDiscoveryResult {
- MDnsObservatoryDiscoveryResult(this.port, this.authCode);
- final int port;
- final String authCode;
-}
-
-/// A wrapper around [MDnsClient] to find a Dart observatory instance.
-class MDnsObservatoryDiscovery {
- /// Creates a new [MDnsObservatoryDiscovery] object.
- ///
- /// The [client] parameter will be defaulted to a new [MDnsClient] if null.
- /// The [applicationId] parameter may be null, and can be used to
- /// automatically select which application to use if multiple are advertising
- /// Dart observatory ports.
- MDnsObservatoryDiscovery({MDnsClient mdnsClient})
- : client = mdnsClient ?? MDnsClient();
-
- /// The [MDnsClient] used to do a lookup.
- final MDnsClient client;
-
- static const String dartObservatoryName = '_dartobservatory._tcp.local';
-
- /// Executes an mDNS query for a Dart Observatory.
- ///
- /// The [applicationId] parameter may be used to specify which application
- /// to find. For Android, it refers to the package name; on iOS, it refers to
- /// the bundle ID.
- ///
- /// If it is not null, this method will find the port and authentication code
- /// of the Dart Observatory for that application. If it cannot find a Dart
- /// Observatory matching that application identifier, it will call
- /// [throwToolExit].
- ///
- /// If it is null and there are multiple ports available, the user will be
- /// prompted with a list of available observatory ports and asked to select
- /// one.
- ///
- /// If it is null and there is only one available instance of Observatory,
- /// it will return that instance's information regardless of what application
- /// the Observatory instance is for.
- Future<MDnsObservatoryDiscoveryResult> query({String applicationId}) async {
- printStatus('Checking for advertised Dart observatories...');
- try {
- await client.start();
- final List<PtrResourceRecord> pointerRecords = await client
- .lookup<PtrResourceRecord>(
- ResourceRecordQuery.serverPointer(dartObservatoryName),
- )
- .toList();
- if (pointerRecords.isEmpty) {
- return null;
- }
- // We have no guarantee that we won't get multiple hits from the same
- // service on this.
- final List<String> uniqueDomainNames = pointerRecords
- .map<String>((PtrResourceRecord record) => record.domainName)
- .toSet()
- .toList();
-
- String domainName;
- if (applicationId != null) {
- for (String name in uniqueDomainNames) {
- if (name.toLowerCase().startsWith(applicationId.toLowerCase())) {
- domainName = name;
- break;
- }
- }
- if (domainName == null) {
- throwToolExit('Did not find a observatory port advertised for $applicationId.');
- }
- } else if (uniqueDomainNames.length > 1) {
- final StringBuffer buffer = StringBuffer();
- buffer.writeln('There are multiple observatory ports available.');
- buffer.writeln('Rerun this command with one of the following passed in as the appId:');
- buffer.writeln('');
- for (final String uniqueDomainName in uniqueDomainNames) {
- buffer.writeln(' flutter attach --app-id ${uniqueDomainName.replaceAll('.$dartObservatoryName', '')}');
- }
- throwToolExit(buffer.toString());
- } else {
- domainName = pointerRecords[0].domainName;
- }
- printStatus('Checking for available port on $domainName');
- // Here, if we get more than one, it should just be a duplicate.
- final List<SrvResourceRecord> srv = await client
- .lookup<SrvResourceRecord>(
- ResourceRecordQuery.service(domainName),
- )
- .toList();
- if (srv.isEmpty) {
- return null;
- }
- if (srv.length > 1) {
- printError('Unexpectedly found more than one observatory report for $domainName '
- '- using first one (${srv.first.port}).');
- }
- printStatus('Checking for authentication code for $domainName');
- final List<TxtResourceRecord> txt = await client
- .lookup<TxtResourceRecord>(
- ResourceRecordQuery.text(domainName),
- )
- ?.toList();
- if (txt == null || txt.isEmpty) {
- return MDnsObservatoryDiscoveryResult(srv.first.port, '');
- }
- String authCode = '';
- const String authCodePrefix = 'authCode=';
- String raw = txt.first.text;
- // TXT has a format of [<length byte>, text], so if the length is 2,
- // that means that TXT is empty.
- if (raw.length > 2) {
- // Remove length byte from raw txt.
- raw = raw.substring(1);
- if (raw.startsWith(authCodePrefix)) {
- authCode = raw.substring(authCodePrefix.length);
- // The Observatory currently expects a trailing '/' as part of the
- // URI, otherwise an invalid authentication code response is given.
- if (!authCode.endsWith('/')) {
- authCode += '/';
- }
- }
- }
- return MDnsObservatoryDiscoveryResult(srv.first.port, authCode);
- } finally {
- client.stop();
- }
- }
-}
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 83985e5..1207434 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -42,6 +42,7 @@
import 'macos/macos_workflow.dart';
import 'macos/xcode.dart';
import 'macos/xcode_validator.dart';
+import 'mdns_discovery.dart';
import 'reporting/reporting.dart';
import 'run_hot.dart';
import 'version.dart';
@@ -96,6 +97,7 @@
LinuxWorkflow: () => const LinuxWorkflow(),
Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger(),
MacOSWorkflow: () => const MacOSWorkflow(),
+ MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(),
OperatingSystemUtils: () => OperatingSystemUtils(),
SimControl: () => SimControl(),
Stdio: () => const Stdio(),
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 51d8f85..6653c0c 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -18,6 +18,7 @@
import '../convert.dart';
import '../device.dart';
import '../globals.dart';
+import '../mdns_discovery.dart';
import '../project.dart';
import '../protocol_discovery.dart';
import '../reporting/reporting.dart';
@@ -382,16 +383,36 @@
return LaunchResult.succeeded();
}
+ Uri localUri;
try {
printTrace('Application launched on the device. Waiting for observatory port.');
- final Uri localUri = await observatoryDiscovery.uri;
- return LaunchResult.succeeded(observatoryUri: localUri);
+ localUri = await MDnsObservatoryDiscovery.instance.getObservatoryUri(
+ package.id,
+ this,
+ ipv6,
+ debuggingOptions.observatoryPort,
+ );
+ if (localUri != null) {
+ return LaunchResult.succeeded(observatoryUri: localUri);
+ }
} catch (error) {
printError('Failed to establish a debug connection with $id: $error');
- return LaunchResult.failed();
+ }
+
+ // Fallback to manual protocol discovery
+ printTrace('mDNS lookup failed, attempting fallback to reading device log.');
+ try {
+ printTrace('Waiting for observatory port.');
+ localUri = await observatoryDiscovery.uri;
+ if (localUri != null) {
+ return LaunchResult.succeeded(observatoryUri: localUri);
+ }
+ } catch (error) {
+ printError('Failed to establish a debug connection with $id: $error');
} finally {
await observatoryDiscovery?.cancel();
}
+ return LaunchResult.failed();
} finally {
installStatus.stop();
}
diff --git a/packages/flutter_tools/lib/src/mdns_discovery.dart b/packages/flutter_tools/lib/src/mdns_discovery.dart
new file mode 100644
index 0000000..72aa60a
--- /dev/null
+++ b/packages/flutter_tools/lib/src/mdns_discovery.dart
@@ -0,0 +1,174 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+import 'package:multicast_dns/multicast_dns.dart';
+
+import 'base/common.dart';
+import 'base/context.dart';
+import 'base/io.dart';
+import 'device.dart';
+import 'globals.dart';
+
+/// A wrapper around [MDnsClient] to find a Dart observatory instance.
+class MDnsObservatoryDiscovery {
+ /// Creates a new [MDnsObservatoryDiscovery] object.
+ ///
+ /// The [client] parameter will be defaulted to a new [MDnsClient] if null.
+ /// The [applicationId] parameter may be null, and can be used to
+ /// automatically select which application to use if multiple are advertising
+ /// Dart observatory ports.
+ MDnsObservatoryDiscovery({MDnsClient mdnsClient})
+ : client = mdnsClient ?? MDnsClient();
+
+ /// The [MDnsClient] used to do a lookup.
+ final MDnsClient client;
+
+ @visibleForTesting
+ static const String dartObservatoryName = '_dartobservatory._tcp.local';
+
+ static MDnsObservatoryDiscovery get instance => context.get<MDnsObservatoryDiscovery>();
+
+ /// Executes an mDNS query for a Dart Observatory.
+ ///
+ /// The [applicationId] parameter may be used to specify which application
+ /// to find. For Android, it refers to the package name; on iOS, it refers to
+ /// the bundle ID.
+ ///
+ /// If it is not null, this method will find the port and authentication code
+ /// of the Dart Observatory for that application. If it cannot find a Dart
+ /// Observatory matching that application identifier, it will call
+ /// [throwToolExit].
+ ///
+ /// If it is null and there are multiple ports available, the user will be
+ /// prompted with a list of available observatory ports and asked to select
+ /// one.
+ ///
+ /// If it is null and there is only one available instance of Observatory,
+ /// it will return that instance's information regardless of what application
+ /// the Observatory instance is for.
+ Future<MDnsObservatoryDiscoveryResult> query({String applicationId}) async {
+ printStatus('Checking for advertised Dart observatories...');
+ try {
+ await client.start();
+ final List<PtrResourceRecord> pointerRecords = await client
+ .lookup<PtrResourceRecord>(
+ ResourceRecordQuery.serverPointer(dartObservatoryName),
+ )
+ .toList();
+ if (pointerRecords.isEmpty) {
+ return null;
+ }
+ // We have no guarantee that we won't get multiple hits from the same
+ // service on this.
+ final List<String> uniqueDomainNames = pointerRecords
+ .map<String>((PtrResourceRecord record) => record.domainName)
+ .toSet()
+ .toList();
+
+ String domainName;
+ if (applicationId != null) {
+ for (String name in uniqueDomainNames) {
+ if (name.toLowerCase().startsWith(applicationId.toLowerCase())) {
+ domainName = name;
+ break;
+ }
+ }
+ if (domainName == null) {
+ throwToolExit('Did not find a observatory port advertised for $applicationId.');
+ }
+ } else if (uniqueDomainNames.length > 1) {
+ final StringBuffer buffer = StringBuffer();
+ buffer.writeln('There are multiple observatory ports available.');
+ buffer.writeln('Rerun this command with one of the following passed in as the appId:');
+ buffer.writeln('');
+ for (final String uniqueDomainName in uniqueDomainNames) {
+ buffer.writeln(' flutter attach --app-id ${uniqueDomainName.replaceAll('.$dartObservatoryName', '')}');
+ }
+ throwToolExit(buffer.toString());
+ } else {
+ domainName = pointerRecords[0].domainName;
+ }
+ printStatus('Checking for available port on $domainName');
+ // Here, if we get more than one, it should just be a duplicate.
+ final List<SrvResourceRecord> srv = await client
+ .lookup<SrvResourceRecord>(
+ ResourceRecordQuery.service(domainName),
+ )
+ .toList();
+ if (srv.isEmpty) {
+ return null;
+ }
+ if (srv.length > 1) {
+ printError('Unexpectedly found more than one observatory report for $domainName '
+ '- using first one (${srv.first.port}).');
+ }
+ printStatus('Checking for authentication code for $domainName');
+ final List<TxtResourceRecord> txt = await client
+ .lookup<TxtResourceRecord>(
+ ResourceRecordQuery.text(domainName),
+ )
+ ?.toList();
+ if (txt == null || txt.isEmpty) {
+ return MDnsObservatoryDiscoveryResult(srv.first.port, '');
+ }
+ String authCode = '';
+ const String authCodePrefix = 'authCode=';
+ String raw = txt.first.text;
+ // TXT has a format of [<length byte>, text], so if the length is 2,
+ // that means that TXT is empty.
+ if (raw.length > 2) {
+ // Remove length byte from raw txt.
+ raw = raw.substring(1);
+ if (raw.startsWith(authCodePrefix)) {
+ authCode = raw.substring(authCodePrefix.length);
+ // The Observatory currently expects a trailing '/' as part of the
+ // URI, otherwise an invalid authentication code response is given.
+ if (!authCode.endsWith('/')) {
+ authCode += '/';
+ }
+ }
+ }
+ return MDnsObservatoryDiscoveryResult(srv.first.port, authCode);
+ } finally {
+ client.stop();
+ }
+ }
+
+ Future<Uri> getObservatoryUri(String applicationId, Device device, [bool usesIpv6 = false, int observatoryPort]) async {
+ final MDnsObservatoryDiscoveryResult result = await query(applicationId: applicationId);
+ Uri observatoryUri;
+ if (result != null) {
+ final String host = usesIpv6
+ ? InternetAddress.loopbackIPv6.address
+ : InternetAddress.loopbackIPv4.address;
+ observatoryUri = await buildObservatoryUri(device, host, result.port, observatoryPort, result.authCode);
+ }
+ return observatoryUri;
+ }
+}
+
+class MDnsObservatoryDiscoveryResult {
+ MDnsObservatoryDiscoveryResult(this.port, this.authCode);
+ final int port;
+ final String authCode;
+}
+
+Future<Uri> buildObservatoryUri(Device device,
+ String host, int devicePort, [int observatoryPort, String authCode]) async {
+ String path = '/';
+ if (authCode != null) {
+ path = authCode;
+ }
+ // Not having a trailing slash can cause problems in some situations.
+ // Ensure that there's one present.
+ if (!path.endsWith('/')) {
+ path += '/';
+ }
+ final int localPort = observatoryPort
+ ?? await device.portForwarder.forward(devicePort);
+ return Uri(scheme: 'http', host: host, port: localPort, path: path);
+}
diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml
index 8184e64..58a2692 100644
--- a/packages/flutter_tools/pubspec.yaml
+++ b/packages/flutter_tools/pubspec.yaml
@@ -21,7 +21,7 @@
json_rpc_2: 2.1.0
linter: 0.1.93
meta: 1.1.7
- multicast_dns: 0.2.0
+ multicast_dns: 0.2.1
mustache: 1.1.1
package_config: 1.0.5
platform: 2.2.1
@@ -135,4 +135,4 @@
# Exclude this package from the hosted API docs.
nodoc: true
-# PUBSPEC CHECKSUM: 3394
+# PUBSPEC CHECKSUM: 7d95
diff --git a/packages/flutter_tools/test/general.shard/commands/attach_test.dart b/packages/flutter_tools/test/general.shard/commands/attach_test.dart
index 23a1a47..38cd4b4 100644
--- a/packages/flutter_tools/test/general.shard/commands/attach_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/attach_test.dart
@@ -13,11 +13,12 @@
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/attach.dart';
import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/ios/devices.dart';
+import 'package:flutter_tools/src/mdns_discovery.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_hot.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
-import 'package:multicast_dns/multicast_dns.dart';
import '../../src/common.dart';
import '../../src/context.dart';
@@ -263,7 +264,7 @@
when(portForwarder.unforward(any))
.thenAnswer((_) async => null);
when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter')))
- .thenAnswer((_) async => 0);
+ .thenAnswer((_) async => 0);
when(mockHotRunnerFactory.build(
any,
target: anyNamed('target'),
@@ -470,137 +471,13 @@
FileSystem: () => testFileSystem,
});
});
-
- group('mDNS Discovery', () {
- final int year3000 = DateTime(3000).millisecondsSinceEpoch;
-
- MDnsClient getMockClient(
- List<PtrResourceRecord> ptrRecords,
- Map<String, List<SrvResourceRecord>> srvResponse,
- ) {
- final MDnsClient client = MockMDnsClient();
-
- when(client.lookup<PtrResourceRecord>(
- ResourceRecordQuery.serverPointer(MDnsObservatoryDiscovery.dartObservatoryName),
- )).thenAnswer((_) => Stream<PtrResourceRecord>.fromIterable(ptrRecords));
-
- for (final MapEntry<String, List<SrvResourceRecord>> entry in srvResponse.entries) {
- when(client.lookup<SrvResourceRecord>(
- ResourceRecordQuery.service(entry.key),
- )).thenAnswer((_) => Stream<SrvResourceRecord>.fromIterable(entry.value));
- }
- return client;
- }
-
- testUsingContext('No ports available', () async {
- final MDnsClient client = getMockClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
-
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
- final int port = (await portDiscovery.query())?.port;
- expect(port, isNull);
- });
-
- testUsingContext('One port available, no appId', () async {
- final MDnsClient client = getMockClient(
- <PtrResourceRecord>[
- PtrResourceRecord('foo', year3000, domainName: 'bar'),
- ],
- <String, List<SrvResourceRecord>>{
- 'bar': <SrvResourceRecord>[
- SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
- ],
- },
- );
-
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
- final int port = (await portDiscovery.query())?.port;
- expect(port, 123);
- });
-
- testUsingContext('Multiple ports available, without appId', () async {
- final MDnsClient client = getMockClient(
- <PtrResourceRecord>[
- PtrResourceRecord('foo', year3000, domainName: 'bar'),
- PtrResourceRecord('baz', year3000, domainName: 'fiz'),
- ],
- <String, List<SrvResourceRecord>>{
- 'bar': <SrvResourceRecord>[
- SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
- ],
- 'fiz': <SrvResourceRecord>[
- SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
- ],
- },
- );
-
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
- expect(() => portDiscovery.query(), throwsToolExit());
- });
-
- testUsingContext('Multiple ports available, with appId', () async {
- final MDnsClient client = getMockClient(
- <PtrResourceRecord>[
- PtrResourceRecord('foo', year3000, domainName: 'bar'),
- PtrResourceRecord('baz', year3000, domainName: 'fiz'),
- ],
- <String, List<SrvResourceRecord>>{
- 'bar': <SrvResourceRecord>[
- SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
- ],
- 'fiz': <SrvResourceRecord>[
- SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
- ],
- },
- );
-
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
- final int port = (await portDiscovery.query(applicationId: 'fiz'))?.port;
- expect(port, 321);
- });
-
- testUsingContext('Multiple ports available per process, with appId', () async {
- final MDnsClient client = getMockClient(
- <PtrResourceRecord>[
- PtrResourceRecord('foo', year3000, domainName: 'bar'),
- PtrResourceRecord('baz', year3000, domainName: 'fiz'),
- ],
- <String, List<SrvResourceRecord>>{
- 'bar': <SrvResourceRecord>[
- SrvResourceRecord('bar', year3000, port: 1234, weight: 1, priority: 1, target: 'appId'),
- SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
- ],
- 'fiz': <SrvResourceRecord>[
- SrvResourceRecord('fiz', year3000, port: 4321, weight: 1, priority: 1, target: 'local'),
- SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
- ],
- },
- );
-
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
- final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
- expect(port, 1234);
- });
-
- testUsingContext('Query returns null', () async {
- final MDnsClient client = getMockClient(
- <PtrResourceRecord>[],
- <String, List<SrvResourceRecord>>{},
- );
-
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
- final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
- expect(port, isNull);
- });
- });
}
-class MockMDnsClient extends Mock implements MDnsClient {}
-
-class MockPortForwarder extends Mock implements DevicePortForwarder {}
-
class MockHotRunner extends Mock implements HotRunner {}
-
class MockHotRunnerFactory extends Mock implements HotRunnerFactory {}
+class MockIOSDevice extends Mock implements IOSDevice {}
+class MockMDnsObservatoryDiscovery extends Mock implements MDnsObservatoryDiscovery {}
+class MockPortForwarder extends Mock implements DevicePortForwarder {}
class StreamLogger extends Logger {
@override
diff --git a/packages/flutter_tools/test/general.shard/ios/devices_test.dart b/packages/flutter_tools/test/general.shard/ios/devices_test.dart
index 3e31c32..dba51cf 100644
--- a/packages/flutter_tools/test/general.shard/ios/devices_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/devices_test.dart
@@ -16,6 +16,7 @@
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/macos/xcode.dart';
+import 'package:flutter_tools/src/mdns_discovery.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
@@ -31,6 +32,7 @@
class MockDirectory extends Mock implements Directory {}
class MockFileSystem extends Mock implements FileSystem {}
class MockIMobileDevice extends Mock implements IMobileDevice {}
+class MockMDnsObservatoryDiscovery extends Mock implements MDnsObservatoryDiscovery {}
class MockXcode extends Mock implements Xcode {}
class MockFile extends Mock implements File {}
class MockPortForwarder extends Mock implements DevicePortForwarder {}
@@ -70,6 +72,7 @@
MockFileSystem mockFileSystem;
MockProcessManager mockProcessManager;
MockDeviceLogReader mockLogReader;
+ MockMDnsObservatoryDiscovery mockMDnsObservatoryDiscovery;
MockPortForwarder mockPortForwarder;
const int devicePort = 499;
@@ -91,6 +94,7 @@
mockCache = MockCache();
when(mockCache.dyLdLibEntry).thenReturn(libraryEntry);
mockFileSystem = MockFileSystem();
+ mockMDnsObservatoryDiscovery = MockMDnsObservatoryDiscovery();
mockProcessManager = MockProcessManager();
mockLogReader = MockDeviceLogReader();
mockPortForwarder = MockPortForwarder();
@@ -132,16 +136,18 @@
mockLogReader.dispose();
});
- testUsingContext(' succeeds in debug mode', () async {
+ testUsingContext(' succeeds in debug mode via mDNS', () async {
final IOSDevice device = IOSDevice('123');
device.portForwarder = mockPortForwarder;
device.setLogReader(mockApp, mockLogReader);
-
- // Now that the reader is used, start writing messages to it.
- Timer.run(() {
- mockLogReader.addLine('Foo');
- mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
- });
+ final Uri uri = Uri(
+ scheme: 'http',
+ host: '127.0.0.1',
+ port: 1234,
+ path: 'observatory',
+ );
+ when(mockMDnsObservatoryDiscovery.getObservatoryUri(any, any, any))
+ .thenAnswer((Invocation invocation) => Future<Uri>.value(uri));
final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true,
@@ -155,6 +161,65 @@
Artifacts: () => mockArtifacts,
Cache: () => mockCache,
FileSystem: () => mockFileSystem,
+ MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery,
+ Platform: () => macPlatform,
+ ProcessManager: () => mockProcessManager,
+ });
+
+ testUsingContext(' succeeds in debug mode when mDNS fails by falling back to manual protocol discovery', () async {
+ final IOSDevice device = IOSDevice('123');
+ device.portForwarder = mockPortForwarder;
+ device.setLogReader(mockApp, mockLogReader);
+ // Now that the reader is used, start writing messages to it.
+ Timer.run(() {
+ mockLogReader.addLine('Foo');
+ mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
+ });
+ when(mockMDnsObservatoryDiscovery.getObservatoryUri(any, any, any))
+ .thenAnswer((Invocation invocation) => Future<Uri>.value(null));
+
+ final LaunchResult launchResult = await device.startApp(mockApp,
+ prebuiltApplication: true,
+ debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)),
+ platformArgs: <String, dynamic>{},
+ );
+ expect(launchResult.started, isTrue);
+ expect(launchResult.hasObservatory, isTrue);
+ expect(await device.stopApp(mockApp), isFalse);
+ }, overrides: <Type, Generator>{
+ Artifacts: () => mockArtifacts,
+ Cache: () => mockCache,
+ FileSystem: () => mockFileSystem,
+ MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery,
+ Platform: () => macPlatform,
+ ProcessManager: () => mockProcessManager,
+ });
+
+ testUsingContext(' fails in debug mode when mDNS fails and when Observatory URI is malformed', () async {
+ final IOSDevice device = IOSDevice('123');
+ device.portForwarder = mockPortForwarder;
+ device.setLogReader(mockApp, mockLogReader);
+
+ // Now that the reader is used, start writing messages to it.
+ Timer.run(() {
+ mockLogReader.addLine('Foo');
+ mockLogReader.addLine('Observatory listening on http:/:/127.0.0.1:$devicePort');
+ });
+ when(mockMDnsObservatoryDiscovery.getObservatoryUri(any, any, any))
+ .thenAnswer((Invocation invocation) => Future<Uri>.value(null));
+
+ final LaunchResult launchResult = await device.startApp(mockApp,
+ prebuiltApplication: true,
+ debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)),
+ platformArgs: <String, dynamic>{},
+ );
+ expect(launchResult.started, isFalse);
+ expect(launchResult.hasObservatory, isFalse);
+ }, overrides: <Type, Generator>{
+ Artifacts: () => mockArtifacts,
+ Cache: () => mockCache,
+ FileSystem: () => mockFileSystem,
+ MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery,
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
});
@@ -176,32 +241,6 @@
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
});
-
- testUsingContext(' fails in debug mode when Observatory URI is malformed', () async {
- final IOSDevice device = IOSDevice('123');
- device.portForwarder = mockPortForwarder;
- device.setLogReader(mockApp, mockLogReader);
-
- // Now that the reader is used, start writing messages to it.
- Timer.run(() {
- mockLogReader.addLine('Foo');
- mockLogReader.addLine('Observatory listening on http:/:/127.0.0.1:$devicePort');
- });
-
- final LaunchResult launchResult = await device.startApp(mockApp,
- prebuiltApplication: true,
- debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)),
- platformArgs: <String, dynamic>{},
- );
- expect(launchResult.started, isFalse);
- expect(launchResult.hasObservatory, isFalse);
- }, overrides: <Type, Generator>{
- Artifacts: () => mockArtifacts,
- Cache: () => mockCache,
- FileSystem: () => mockFileSystem,
- Platform: () => macPlatform,
- ProcessManager: () => mockProcessManager,
- });
});
group('Process calls', () {
diff --git a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart
new file mode 100644
index 0000000..a56b12e
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart
@@ -0,0 +1,138 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter_tools/src/mdns_discovery.dart';
+import 'package:mockito/mockito.dart';
+import 'package:multicast_dns/multicast_dns.dart';
+
+import '../src/common.dart';
+import '../src/context.dart';
+
+void main() {
+ group('mDNS Discovery', () {
+ final int year3000 = DateTime(3000).millisecondsSinceEpoch;
+
+ MDnsClient getMockClient(
+ List<PtrResourceRecord> ptrRecords,
+ Map<String, List<SrvResourceRecord>> srvResponse,
+ ) {
+ final MDnsClient client = MockMDnsClient();
+
+ when(client.lookup<PtrResourceRecord>(
+ ResourceRecordQuery.serverPointer(MDnsObservatoryDiscovery.dartObservatoryName),
+ )).thenAnswer((_) => Stream<PtrResourceRecord>.fromIterable(ptrRecords));
+
+ for (final MapEntry<String, List<SrvResourceRecord>> entry in srvResponse.entries) {
+ when(client.lookup<SrvResourceRecord>(
+ ResourceRecordQuery.service(entry.key),
+ )).thenAnswer((_) => Stream<SrvResourceRecord>.fromIterable(entry.value));
+ }
+ return client;
+ }
+
+ testUsingContext('No ports available', () async {
+ final MDnsClient client = getMockClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ final int port = (await portDiscovery.query())?.port;
+ expect(port, isNull);
+ });
+
+ testUsingContext('One port available, no appId', () async {
+ final MDnsClient client = getMockClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', year3000, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ );
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ final int port = (await portDiscovery.query())?.port;
+ expect(port, 123);
+ });
+
+ testUsingContext('Multiple ports available, without appId', () async {
+ final MDnsClient client = getMockClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', year3000, domainName: 'bar'),
+ PtrResourceRecord('baz', year3000, domainName: 'fiz'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ 'fiz': <SrvResourceRecord>[
+ SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
+ ],
+ },
+ );
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ expect(() => portDiscovery.query(), throwsToolExit());
+ });
+
+ testUsingContext('Multiple ports available, with appId', () async {
+ final MDnsClient client = getMockClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', year3000, domainName: 'bar'),
+ PtrResourceRecord('baz', year3000, domainName: 'fiz'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ 'fiz': <SrvResourceRecord>[
+ SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
+ ],
+ },
+ );
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ final int port = (await portDiscovery.query(applicationId: 'fiz'))?.port;
+ expect(port, 321);
+ });
+
+ testUsingContext('Multiple ports available per process, with appId', () async {
+ final MDnsClient client = getMockClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', year3000, domainName: 'bar'),
+ PtrResourceRecord('baz', year3000, domainName: 'fiz'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', year3000, port: 1234, weight: 1, priority: 1, target: 'appId'),
+ SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ 'fiz': <SrvResourceRecord>[
+ SrvResourceRecord('fiz', year3000, port: 4321, weight: 1, priority: 1, target: 'local'),
+ SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
+ ],
+ },
+ );
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
+ expect(port, 1234);
+ });
+
+ testUsingContext('Query returns null', () async {
+ final MDnsClient client = getMockClient(
+ <PtrResourceRecord>[],
+ <String, List<SrvResourceRecord>>{},
+ );
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
+ expect(port, isNull);
+ });
+ });
+}
+
+class MockMDnsClient extends Mock implements MDnsClient {}