mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
V1.9.1 hotfixes (#41229)
Bump version of just package:multicast_dns (#41207) Implement mdns for flutter run (#40447)
This commit is contained in:
parent
1ced75cc2b
commit
a72edc2706
@ -919,7 +919,7 @@ Future<void> _verifyVersion(String filename) async {
|
||||
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.');
|
||||
|
||||
@ -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 '../fuchsia/fuchsia_device.dart';
|
||||
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 @@ class AttachCommand extends FlutterCommand {
|
||||
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 @@ class AttachCommand extends FlutterCommand {
|
||||
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 @@ class AttachCommand extends FlutterCommand {
|
||||
}
|
||||
}
|
||||
} 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 @@ class AttachCommand extends FlutterCommand {
|
||||
}
|
||||
|
||||
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 @@ class HotRunnerFactory {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ import 'macos/cocoapods_validator.dart';
|
||||
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 @@ Future<T> runInContext<T>(
|
||||
LinuxWorkflow: () => const LinuxWorkflow(),
|
||||
Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger(),
|
||||
MacOSWorkflow: () => const MacOSWorkflow(),
|
||||
MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(),
|
||||
OperatingSystemUtils: () => OperatingSystemUtils(),
|
||||
SimControl: () => SimControl(),
|
||||
Stdio: () => const Stdio(),
|
||||
|
||||
@ -18,6 +18,7 @@ import '../build_info.dart';
|
||||
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 @@ class IOSDevice extends Device {
|
||||
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');
|
||||
}
|
||||
|
||||
// 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');
|
||||
return LaunchResult.failed();
|
||||
} finally {
|
||||
await observatoryDiscovery?.cancel();
|
||||
}
|
||||
return LaunchResult.failed();
|
||||
} finally {
|
||||
installStatus.stop();
|
||||
}
|
||||
|
||||
174
packages/flutter_tools/lib/src/mdns_discovery.dart
Normal file
174
packages/flutter_tools/lib/src/mdns_discovery.dart
Normal file
@ -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);
|
||||
}
|
||||
@ -21,7 +21,7 @@ dependencies:
|
||||
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 @@ dartdoc:
|
||||
# Exclude this package from the hosted API docs.
|
||||
nodoc: true
|
||||
|
||||
# PUBSPEC CHECKSUM: 3394
|
||||
# PUBSPEC CHECKSUM: 7d95
|
||||
|
||||
@ -13,11 +13,12 @@ import 'package:flutter_tools/src/base/terminal.dart';
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
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
|
||||
|
||||
@ -16,6 +16,7 @@ import 'package:flutter_tools/src/device.dart';
|
||||
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 MockCache extends Mock implements Cache {}
|
||||
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 @@ void main() {
|
||||
MockFileSystem mockFileSystem;
|
||||
MockProcessManager mockProcessManager;
|
||||
MockDeviceLogReader mockLogReader;
|
||||
MockMDnsObservatoryDiscovery mockMDnsObservatoryDiscovery;
|
||||
MockPortForwarder mockPortForwarder;
|
||||
|
||||
const int devicePort = 499;
|
||||
@ -91,6 +94,7 @@ void main() {
|
||||
mockCache = MockCache();
|
||||
when(mockCache.dyLdLibEntry).thenReturn(libraryEntry);
|
||||
mockFileSystem = MockFileSystem();
|
||||
mockMDnsObservatoryDiscovery = MockMDnsObservatoryDiscovery();
|
||||
mockProcessManager = MockProcessManager();
|
||||
mockLogReader = MockDeviceLogReader();
|
||||
mockPortForwarder = MockPortForwarder();
|
||||
@ -132,16 +136,18 @@ void main() {
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
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', () {
|
||||
|
||||
@ -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 {}
|
||||
Loading…
x
Reference in New Issue
Block a user