[reland] Support wireless debugging (#118895)
* Reland "Support iOS wireless debugging (#118104)"
This reverts commit cbf2e16892eaf0fe81c01c01263daf5b1f7c602f.
* Remove device loading status
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index 82c6088..843306a 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -16,7 +16,7 @@
import '../base/platform.dart';
import '../base/signals.dart';
import '../base/terminal.dart';
-import '../build_info.dart';
+import '../build_info.dart';
import '../commands/daemon.dart';
import '../compile.dart';
import '../daemon.dart';
@@ -24,6 +24,7 @@
import '../device_port_forwarder.dart';
import '../fuchsia/fuchsia_device.dart';
import '../ios/devices.dart';
+import '../ios/iproxy.dart';
import '../ios/simulators.dart';
import '../macos/macos_ipad_device.dart';
import '../mdns_discovery.dart';
@@ -229,7 +230,7 @@
}
if (debugPort != null && debugUri != null) {
throwToolExit(
- 'Either --debugPort or --debugUri can be provided, not both.');
+ 'Either --debug-port or --debug-url can be provided, not both.');
}
if (userIdentifier != null) {
@@ -282,8 +283,9 @@
final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;
+ final bool isNetworkDevice = (device is IOSDevice) && device.interfaceType == IOSDeviceConnectionInterface.network;
- if (debugPort == null && debugUri == null) {
+ if ((debugPort == null && debugUri == null) || isNetworkDevice) {
if (device is FuchsiaDevice) {
final String module = stringArgDeprecated('module')!;
if (module == null) {
@@ -303,16 +305,73 @@
rethrow;
}
} else if ((device is IOSDevice) || (device is IOSSimulator) || (device is MacOSDesignedForIPadDevice)) {
- final Uri? uriFromMdns =
- await MDnsObservatoryDiscovery.instance!.getObservatoryUri(
- appId,
- device,
- usesIpv6: usesIpv6,
- deviceVmservicePort: deviceVmservicePort,
+ // Protocol Discovery relies on logging. On iOS earlier than 13, logging is gathered using syslog.
+ // syslog is not available for iOS 13+. For iOS 13+, Protocol Discovery gathers logs from the VMService.
+ // Since we don't have access to the VMService yet, Protocol Discovery cannot be used for iOS 13+.
+ // Also, network devices must be found using mDNS and cannot use Protocol Discovery.
+ final bool compatibleWithProtocolDiscovery = (device is IOSDevice) &&
+ device.majorSdkVersion < IOSDeviceLogReader.minimumUniversalLoggingSdkVersion &&
+ !isNetworkDevice;
+
+ _logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
+ final Status discoveryStatus = _logger.startSpinner(
+ timeout: const Duration(seconds: 30),
+ slowWarningCallback: () {
+ // If relying on mDNS to find Dart VM Service, remind the user to allow local network permissions.
+ if (!compatibleWithProtocolDiscovery) {
+ return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n\n'
+ 'Click "Allow" to the prompt asking if you would like to find and connect devices on your local network. '
+ 'If you selected "Don\'t Allow", you can turn it on in Settings > Your App Name > Local Network. '
+ "If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again.\n";
+ }
+
+ return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n';
+ },
+ );
+
+ int? devicePort;
+ if (debugPort != null) {
+ devicePort = debugPort;
+ } else if (debugUri != null) {
+ devicePort = debugUri?.port;
+ } else if (deviceVmservicePort != null) {
+ devicePort = deviceVmservicePort;
+ }
+
+ final Future<Uri?> mDNSDiscoveryFuture = MDnsVmServiceDiscovery.instance!.getVMServiceUriForAttach(
+ appId,
+ device,
+ usesIpv6: usesIpv6,
+ isNetworkDevice: isNetworkDevice,
+ deviceVmservicePort: devicePort,
+ );
+
+ Future<Uri?>? protocolDiscoveryFuture;
+ if (compatibleWithProtocolDiscovery) {
+ final ProtocolDiscovery vmServiceDiscovery = ProtocolDiscovery.observatory(
+ device.getLogReader(),
+ portForwarder: device.portForwarder,
+ ipv6: ipv6!,
+ devicePort: devicePort,
+ hostPort: hostVmservicePort,
+ logger: _logger,
);
- observatoryUri = uriFromMdns == null
+ protocolDiscoveryFuture = vmServiceDiscovery.uri;
+ }
+
+ final Uri? foundUrl;
+ if (protocolDiscoveryFuture == null) {
+ foundUrl = await mDNSDiscoveryFuture;
+ } else {
+ foundUrl = await Future.any(
+ <Future<Uri?>>[mDNSDiscoveryFuture, protocolDiscoveryFuture]
+ );
+ }
+ discoveryStatus.stop();
+
+ observatoryUri = foundUrl == null
? null
- : Stream<Uri>.value(uriFromMdns).asBroadcastStream();
+ : Stream<Uri>.value(foundUrl).asBroadcastStream();
}
// If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery.
if (observatoryUri == null) {
@@ -335,7 +394,7 @@
} else {
observatoryUri = Stream<Uri>
.fromFuture(
- buildObservatoryUri(
+ buildVMServiceUri(
device,
debugUri?.host ?? hostname,
debugPort ?? debugUri!.port,
diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart
index 0c46922..95945a0 100644
--- a/packages/flutter_tools/lib/src/commands/drive.dart
+++ b/packages/flutter_tools/lib/src/commands/drive.dart
@@ -4,6 +4,7 @@
import 'dart:async';
+import 'package:args/args.dart';
import 'package:meta/meta.dart';
import 'package:package_config/package_config_types.dart';
@@ -21,6 +22,8 @@
import '../device.dart';
import '../drive/drive_service.dart';
import '../globals.dart' as globals;
+import '../ios/devices.dart';
+import '../ios/iproxy.dart';
import '../resident_runner.dart';
import '../runner/flutter_command.dart' show FlutterCommandCategory, FlutterCommandResult, FlutterOptions;
import '../web/web_device.dart';
@@ -203,6 +206,27 @@
@override
bool get cachePubGet => false;
+ String? get applicationBinaryPath => stringArgDeprecated(FlutterOptions.kUseApplicationBinary);
+
+ Future<Device?> get targetedDevice async {
+ return findTargetDevice(includeUnsupportedDevices: applicationBinaryPath == null);
+ }
+
+ // Network devices need `publish-port` to be enabled because it requires mDNS.
+ // If the flag wasn't provided as an actual argument and it's a network device,
+ // change it to be enabled.
+ @override
+ Future<bool> get disablePortPublication async {
+ final ArgResults? localArgResults = argResults;
+ final Device? device = await targetedDevice;
+ final bool isNetworkDevice = device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network;
+ if (isNetworkDevice && localArgResults != null && !localArgResults.wasParsed('publish-port')) {
+ _logger.printTrace('Network device is being used. Changing `publish-port` to be enabled.');
+ return false;
+ }
+ return !boolArgDeprecated('publish-port');
+ }
+
@override
Future<void> validateCommand() async {
if (userIdentifier != null) {
@@ -223,8 +247,7 @@
if (await _fileSystem.type(testFile) != FileSystemEntityType.file) {
throwToolExit('Test file not found: $testFile');
}
- final String? applicationBinaryPath = stringArgDeprecated(FlutterOptions.kUseApplicationBinary);
- final Device? device = await findTargetDevice(includeUnsupportedDevices: applicationBinaryPath == null);
+ final Device? device = await targetedDevice;
if (device == null) {
throwToolExit(null);
}
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index be9a9a2..37c7dc4 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -254,7 +254,7 @@
purgePersistentCache: purgePersistentCache,
deviceVmServicePort: deviceVmservicePort,
hostVmServicePort: hostVmservicePort,
- disablePortPublication: disablePortPublication,
+ disablePortPublication: await disablePortPublication,
ddsPort: ddsPort,
devToolsServerAddress: devToolsServerAddress,
verboseSystemLogs: boolArgDeprecated('verbose-system-logs'),
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index ba960c1..fffa29d 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -275,7 +275,7 @@
featureFlags: featureFlags,
platform: globals.platform,
),
- MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(
+ MDnsVmServiceDiscovery: () => MDnsVmServiceDiscovery(
logger: globals.logger,
flutterUsage: globals.flutterUsage,
),
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 67e6cab..fbd2686 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -17,6 +17,7 @@
import 'build_info.dart';
import 'devfs.dart';
import 'device_port_forwarder.dart';
+import 'ios/iproxy.dart';
import 'project.dart';
import 'vmservice.dart';
@@ -917,7 +918,13 @@
/// * https://github.com/dart-lang/sdk/blob/main/sdk/lib/html/doc/NATIVE_NULL_ASSERTIONS.md
final bool nativeNullAssertions;
- List<String> getIOSLaunchArguments(EnvironmentType environmentType, String? route, Map<String, Object?> platformArgs) {
+ List<String> getIOSLaunchArguments(
+ EnvironmentType environmentType,
+ String? route,
+ Map<String, Object?> platformArgs, {
+ bool ipv6 = false,
+ IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.none
+ }) {
final String dartVmFlags = computeDartVmFlags(this);
return <String>[
if (enableDartProfiling) '--enable-dart-profiling',
@@ -954,6 +961,9 @@
// Use the suggested host port.
if (environmentType == EnvironmentType.simulator && hostVmServicePort != null)
'--observatory-port=$hostVmServicePort',
+ // Tell the observatory to listen on all interfaces, don't restrict to the loopback.
+ if (interfaceType == IOSDeviceConnectionInterface.network)
+ '--observatory-host=${ipv6 ? '::0' : '0.0.0.0'}',
];
}
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index b1606e1..c58de37 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -9,6 +9,7 @@
import 'package:vm_service/vm_service.dart' as vm_service;
import '../application_package.dart';
+import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
@@ -21,6 +22,7 @@
import '../device_port_forwarder.dart';
import '../globals.dart' as globals;
import '../macos/xcdevice.dart';
+import '../mdns_discovery.dart';
import '../project.dart';
import '../protocol_discovery.dart';
import '../vmservice.dart';
@@ -190,15 +192,6 @@
}
@override
- bool get supportsHotReload => interfaceType == IOSDeviceConnectionInterface.usb;
-
- @override
- bool get supportsHotRestart => interfaceType == IOSDeviceConnectionInterface.usb;
-
- @override
- bool get supportsFlutterExit => interfaceType == IOSDeviceConnectionInterface.usb;
-
- @override
final String name;
@override
@@ -318,7 +311,11 @@
@visibleForTesting Duration? discoveryTimeout,
}) async {
String? packageId;
-
+ if (interfaceType == IOSDeviceConnectionInterface.network &&
+ debuggingOptions.debuggingEnabled &&
+ debuggingOptions.disablePortPublication) {
+ throwToolExit('Cannot start app on wirelessly tethered iOS device. Try running again with the --publish-port flag');
+ }
if (!prebuiltApplication) {
_logger.printTrace('Building ${package.name} for $id');
@@ -353,8 +350,10 @@
EnvironmentType.physical,
route,
platformArgs,
+ ipv6: ipv6,
+ interfaceType: interfaceType,
);
- final Status installStatus = _logger.startProgress(
+ Status startAppStatus = _logger.startProgress(
'Installing and launching...',
);
try {
@@ -379,9 +378,10 @@
deviceLogReader.debuggerStream = iosDeployDebugger;
}
}
+ // Don't port foward if debugging with a network device.
observatoryDiscovery = ProtocolDiscovery.observatory(
deviceLogReader,
- portForwarder: portForwarder,
+ portForwarder: interfaceType == IOSDeviceConnectionInterface.network ? null : portForwarder,
hostPort: debuggingOptions.hostVmServicePort,
devicePort: debuggingOptions.deviceVmServicePort,
ipv6: ipv6,
@@ -412,12 +412,59 @@
return LaunchResult.succeeded();
}
- _logger.printTrace('Application launched on the device. Waiting for observatory url.');
- final Timer timer = Timer(discoveryTimeout ?? const Duration(seconds: 30), () {
- _logger.printError('iOS Observatory not discovered after 30 seconds. This is taking much longer than expected...');
- iosDeployDebugger?.pauseDumpBacktraceResume();
+ _logger.printTrace('Application launched on the device. Waiting for Dart VM Service url.');
+
+ final int defaultTimeout = interfaceType == IOSDeviceConnectionInterface.network ? 45 : 30;
+ final Timer timer = Timer(discoveryTimeout ?? Duration(seconds: defaultTimeout), () {
+ _logger.printError('The Dart VM Service was not discovered after $defaultTimeout seconds. This is taking much longer than expected...');
+
+ // If debugging with a wireless device and the timeout is reached, remind the
+ // user to allow local network permissions.
+ if (interfaceType == IOSDeviceConnectionInterface.network) {
+ _logger.printError(
+ '\nClick "Allow" to the prompt asking if you would like to find and connect devices on your local network. '
+ 'This is required for wireless debugging. If you selected "Don\'t Allow", '
+ 'you can turn it on in Settings > Your App Name > Local Network. '
+ "If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again."
+ );
+ } else {
+ iosDeployDebugger?.pauseDumpBacktraceResume();
+ }
});
- final Uri? localUri = await observatoryDiscovery?.uri;
+
+ Uri? localUri;
+ if (interfaceType == IOSDeviceConnectionInterface.network) {
+ // Wait for Dart VM Service to start up.
+ final Uri? serviceURL = await observatoryDiscovery?.uri;
+ if (serviceURL == null) {
+ await iosDeployDebugger?.stopAndDumpBacktrace();
+ return LaunchResult.failed();
+ }
+
+ // If Dart VM Service URL with the device IP is not found within 5 seconds,
+ // change the status message to prompt users to click Allow. Wait 5 seconds because it
+ // should only show this message if they have not already approved the permissions.
+ // MDnsVmServiceDiscovery usually takes less than 5 seconds to find it.
+ final Timer mDNSLookupTimer = Timer(const Duration(seconds: 5), () {
+ startAppStatus.stop();
+ startAppStatus = _logger.startProgress(
+ 'Waiting for approval of local network permissions...',
+ );
+ });
+
+ // Get Dart VM Service URL with the device IP as the host.
+ localUri = await MDnsVmServiceDiscovery.instance!.getVMServiceUriForLaunch(
+ packageId,
+ this,
+ usesIpv6: ipv6,
+ deviceVmservicePort: serviceURL.port,
+ isNetworkDevice: true,
+ );
+
+ mDNSLookupTimer.cancel();
+ } else {
+ localUri = await observatoryDiscovery?.uri;
+ }
timer.cancel();
if (localUri == null) {
await iosDeployDebugger?.stopAndDumpBacktrace();
@@ -429,7 +476,7 @@
_logger.printError(e.message);
return LaunchResult.failed();
} finally {
- installStatus.stop();
+ startAppStatus.stop();
}
}
@@ -569,7 +616,6 @@
}
}
-@visibleForTesting
class IOSDeviceLogReader extends DeviceLogReader {
IOSDeviceLogReader._(
this._iMobileDevice,
diff --git a/packages/flutter_tools/lib/src/macos/xcdevice.dart b/packages/flutter_tools/lib/src/macos/xcdevice.dart
index c6bde74..523e052 100644
--- a/packages/flutter_tools/lib/src/macos/xcdevice.dart
+++ b/packages/flutter_tools/lib/src/macos/xcdevice.dart
@@ -305,12 +305,6 @@
final IOSDeviceConnectionInterface interface = _interfaceType(device);
- // Only support USB devices, skip "network" interface (Xcode > Window > Devices and Simulators > Connect via network).
- // TODO(jmagman): Remove this check once wirelessly detected devices can be observed and attached, https://github.com/flutter/flutter/issues/15072.
- if (interface != IOSDeviceConnectionInterface.usb) {
- continue;
- }
-
String? sdkVersion = _sdkVersion(device);
if (sdkVersion != null) {
diff --git a/packages/flutter_tools/lib/src/mdns_discovery.dart b/packages/flutter_tools/lib/src/mdns_discovery.dart
index a1d0717..d466a83 100644
--- a/packages/flutter_tools/lib/src/mdns_discovery.dart
+++ b/packages/flutter_tools/lib/src/mdns_discovery.dart
@@ -13,161 +13,427 @@
import 'device.dart';
import 'reporting/reporting.dart';
-/// A wrapper around [MDnsClient] to find a Dart observatory instance.
-class MDnsObservatoryDiscovery {
- /// Creates a new [MDnsObservatoryDiscovery] object.
+/// A wrapper around [MDnsClient] to find a Dart VM Service instance.
+class MDnsVmServiceDiscovery {
+ /// Creates a new [MDnsVmServiceDiscovery] 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({
+ MDnsVmServiceDiscovery({
MDnsClient? mdnsClient,
+ MDnsClient? preliminaryMDnsClient,
required Logger logger,
required Usage flutterUsage,
- }): _client = mdnsClient ?? MDnsClient(),
- _logger = logger,
- _flutterUsage = flutterUsage;
+ }) : _client = mdnsClient ?? MDnsClient(),
+ _preliminaryClient = preliminaryMDnsClient,
+ _logger = logger,
+ _flutterUsage = flutterUsage;
final MDnsClient _client;
+
+ // Used when discovering VM services with `queryForAttach` to do a preliminary
+ // check for already running services so that results are not cached in _client.
+ final MDnsClient? _preliminaryClient;
+
final Logger _logger;
final Usage _flutterUsage;
@visibleForTesting
- static const String dartObservatoryName = '_dartobservatory._tcp.local';
+ static const String dartVmServiceName = '_dartobservatory._tcp.local';
- static MDnsObservatoryDiscovery? get instance => context.get<MDnsObservatoryDiscovery>();
+ static MDnsVmServiceDiscovery? get instance => context.get<MDnsVmServiceDiscovery>();
- /// Executes an mDNS query for a Dart Observatory.
+ /// Executes an mDNS query for Dart VM Services.
+ /// Checks for services that have already been launched.
+ /// If none are found, it will listen for new services to become active
+ /// and return the first it finds that match the parameters.
///
/// 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].
+ /// The [deviceVmservicePort] parameter may be used to specify which port
+ /// to find.
///
- /// 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.
+ /// The [isNetworkDevice] parameter flags whether to get the device IP
+ /// and the [ipv6] parameter flags whether to get an iPv6 address
+ /// (otherwise it will get iPv4).
///
- /// 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.
+ /// The [timeout] parameter determines how long to continue to wait for
+ /// services to become active.
+ ///
+ /// If [applicationId] is not null, this method will find the port and authentication code
+ /// of the Dart VM Service for that application. If it cannot find a service matching
+ /// that application identifier after the [timeout], it will call [throwToolExit].
+ ///
+ /// If [applicationId] is null and there are multiple Dart VM Services available,
+ /// the user will be prompted with a list of available services with the respective
+ /// app-id and device-vmservice-port to use and asked to select one.
+ ///
+ /// If it is null and there is only one available or it's the first found instance
+ /// of Dart VM Service, it will return that instance's information regardless of
+ /// what application the service instance is for.
@visibleForTesting
- Future<MDnsObservatoryDiscoveryResult?> query({String? applicationId, int? deviceVmservicePort}) async {
- _logger.printTrace('Checking for advertised Dart observatories...');
- try {
- await _client.start();
- final List<PtrResourceRecord> pointerRecords = await _client
- .lookup<PtrResourceRecord>(
- ResourceRecordQuery.serverPointer(dartObservatoryName),
- )
- .toList();
- if (pointerRecords.isEmpty) {
- _logger.printTrace('No pointer records found.');
- return null;
+ Future<MDnsVmServiceDiscoveryResult?> queryForAttach({
+ String? applicationId,
+ int? deviceVmservicePort,
+ bool ipv6 = false,
+ bool isNetworkDevice = false,
+ Duration timeout = const Duration(minutes: 10),
+ }) async {
+ // Poll for 5 seconds to see if there are already services running.
+ // Use a new instance of MDnsClient so results don't get cached in _client.
+ // If no results are found, poll for a longer duration to wait for connections.
+ // If more than 1 result is found, throw an error since it can't be determined which to pick.
+ // If only one is found, return it.
+ final List<MDnsVmServiceDiscoveryResult> results = await _pollingVmService(
+ _preliminaryClient ?? MDnsClient(),
+ applicationId: applicationId,
+ deviceVmservicePort: deviceVmservicePort,
+ ipv6: ipv6,
+ isNetworkDevice: isNetworkDevice,
+ timeout: const Duration(seconds: 5),
+ );
+ if (results.isEmpty) {
+ return firstMatchingVmService(
+ _client,
+ applicationId: applicationId,
+ deviceVmservicePort: deviceVmservicePort,
+ ipv6: ipv6,
+ isNetworkDevice: isNetworkDevice,
+ timeout: timeout,
+ );
+ } else if (results.length > 1) {
+ final StringBuffer buffer = StringBuffer();
+ buffer.writeln('There are multiple Dart VM Services available.');
+ buffer.writeln('Rerun this command with one of the following passed in as the app-id and device-vmservice-port:');
+ buffer.writeln();
+ for (final MDnsVmServiceDiscoveryResult result in results) {
+ buffer.writeln(
+ ' flutter attach --app-id "${result.domainName.replaceAll('.$dartVmServiceName', '')}" --device-vmservice-port ${result.port}');
}
- // We have no guarantee that we won't get multiple hits from the same
- // service on this.
- final Set<String> uniqueDomainNames = pointerRecords
- .map<String>((PtrResourceRecord record) => record.domainName)
- .toSet();
+ throwToolExit(buffer.toString());
+ }
+ return results.first;
+ }
- String? domainName;
- if (applicationId != null) {
- for (final String name in uniqueDomainNames) {
- if (name.toLowerCase().startsWith(applicationId.toLowerCase())) {
- domainName = name;
+ /// Executes an mDNS query for Dart VM Services.
+ /// Listens for new services to become active and returns the first it finds that
+ /// match the parameters.
+ ///
+ /// The [applicationId] parameter must be set to specify which application
+ /// to find. For Android, it refers to the package name; on iOS, it refers to
+ /// the bundle ID.
+ ///
+ /// The [deviceVmservicePort] parameter must be set to specify which port
+ /// to find.
+ ///
+ /// [applicationId] and [deviceVmservicePort] are required for launch so that
+ /// if multiple flutter apps are running on different devices, it will
+ /// only match with the device running the desired app.
+ ///
+ /// The [isNetworkDevice] parameter flags whether to get the device IP
+ /// and the [ipv6] parameter flags whether to get an iPv6 address
+ /// (otherwise it will get iPv4).
+ ///
+ /// The [timeout] parameter determines how long to continue to wait for
+ /// services to become active.
+ ///
+ /// If a Dart VM Service matching the [applicationId] and [deviceVmservicePort]
+ /// cannot be found after the [timeout], it will call [throwToolExit].
+ @visibleForTesting
+ Future<MDnsVmServiceDiscoveryResult?> queryForLaunch({
+ required String applicationId,
+ required int deviceVmservicePort,
+ bool ipv6 = false,
+ bool isNetworkDevice = false,
+ Duration timeout = const Duration(minutes: 10),
+ }) async {
+ // Query for a specific application and device port.
+ return firstMatchingVmService(
+ _client,
+ applicationId: applicationId,
+ deviceVmservicePort: deviceVmservicePort,
+ ipv6: ipv6,
+ isNetworkDevice: isNetworkDevice,
+ timeout: timeout,
+ );
+ }
+
+ /// Polls for Dart VM Services and returns the first it finds that match
+ /// the [applicationId]/[deviceVmservicePort] (if applicable).
+ /// Returns null if no results are found.
+ @visibleForTesting
+ Future<MDnsVmServiceDiscoveryResult?> firstMatchingVmService(
+ MDnsClient client, {
+ String? applicationId,
+ int? deviceVmservicePort,
+ bool ipv6 = false,
+ bool isNetworkDevice = false,
+ Duration timeout = const Duration(minutes: 10),
+ }) async {
+ final List<MDnsVmServiceDiscoveryResult> results = await _pollingVmService(
+ client,
+ applicationId: applicationId,
+ deviceVmservicePort: deviceVmservicePort,
+ ipv6: ipv6,
+ isNetworkDevice: isNetworkDevice,
+ timeout: timeout,
+ quitOnFind: true,
+ );
+ if (results.isEmpty) {
+ return null;
+ }
+ return results.first;
+ }
+
+ Future<List<MDnsVmServiceDiscoveryResult>> _pollingVmService(
+ MDnsClient client, {
+ String? applicationId,
+ int? deviceVmservicePort,
+ bool ipv6 = false,
+ bool isNetworkDevice = false,
+ required Duration timeout,
+ bool quitOnFind = false,
+ }) async {
+ _logger.printTrace('Checking for advertised Dart VM Services...');
+ try {
+ await client.start();
+
+ final List<MDnsVmServiceDiscoveryResult> results =
+ <MDnsVmServiceDiscoveryResult>[];
+ final Set<String> uniqueDomainNames = <String>{};
+
+ // Listen for mDNS connections until timeout.
+ final Stream<PtrResourceRecord> ptrResourceStream = client.lookup<PtrResourceRecord>(
+ ResourceRecordQuery.serverPointer(dartVmServiceName),
+ timeout: timeout
+ );
+ await for (final PtrResourceRecord ptr in ptrResourceStream) {
+ uniqueDomainNames.add(ptr.domainName);
+
+ String? domainName;
+ if (applicationId != null) {
+ // If applicationId is set, only use records that match it
+ if (ptr.domainName.toLowerCase().startsWith(applicationId.toLowerCase())) {
+ domainName = ptr.domainName;
+ } else {
+ continue;
+ }
+ } else {
+ domainName = ptr.domainName;
+ }
+
+ _logger.printTrace('Checking for available port on $domainName');
+ final List<SrvResourceRecord> srvRecords = await client
+ .lookup<SrvResourceRecord>(
+ ResourceRecordQuery.service(domainName),
+ )
+ .toList();
+ if (srvRecords.isEmpty) {
+ continue;
+ }
+
+ // If more than one SrvResourceRecord found, it should just be a duplicate.
+ final SrvResourceRecord srvRecord = srvRecords.first;
+ if (srvRecords.length > 1) {
+ _logger.printWarning(
+ 'Unexpectedly found more than one Dart VM Service report for $domainName '
+ '- using first one (${srvRecord.port}).');
+ }
+
+ // If deviceVmservicePort is set, only use records that match it
+ if (deviceVmservicePort != null && srvRecord.port != deviceVmservicePort) {
+ continue;
+ }
+
+ // Get the IP address of the service if using a network device.
+ InternetAddress? ipAddress;
+ if (isNetworkDevice) {
+ List<IPAddressResourceRecord> ipAddresses = await client
+ .lookup<IPAddressResourceRecord>(
+ ipv6
+ ? ResourceRecordQuery.addressIPv6(srvRecord.target)
+ : ResourceRecordQuery.addressIPv4(srvRecord.target),
+ )
+ .toList();
+ if (ipAddresses.isEmpty) {
+ throwToolExit('Did not find IP for service ${srvRecord.target}.');
+ }
+
+ // Filter out link-local addresses.
+ if (ipAddresses.length > 1) {
+ ipAddresses = ipAddresses.where((IPAddressResourceRecord element) => !element.address.isLinkLocal).toList();
+ }
+
+ ipAddress = ipAddresses.first.address;
+ if (ipAddresses.length > 1) {
+ _logger.printWarning(
+ 'Unexpectedly found more than one IP for Dart VM Service ${srvRecord.target} '
+ '- using first one ($ipAddress).');
+ }
+ }
+
+ _logger.printTrace('Checking for authentication code for $domainName');
+ final List<TxtResourceRecord> txt = await client
+ .lookup<TxtResourceRecord>(
+ ResourceRecordQuery.text(domainName),
+ )
+ .toList();
+ if (txt == null || txt.isEmpty) {
+ results.add(MDnsVmServiceDiscoveryResult(domainName, srvRecord.port, ''));
+ if (quitOnFind) {
+ return results;
+ }
+ continue;
+ }
+ const String authCodePrefix = 'authCode=';
+ String? raw;
+ for (final String record in txt.first.text.split('\n')) {
+ if (record.startsWith(authCodePrefix)) {
+ raw = record;
break;
}
}
- if (domainName == null) {
- throwToolExit('Did not find a observatory port advertised for $applicationId.');
+ if (raw == null) {
+ results.add(MDnsVmServiceDiscoveryResult(domainName, srvRecord.port, ''));
+ if (quitOnFind) {
+ return results;
+ }
+ continue;
}
- } 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', '')}');
+ String authCode = raw.substring(authCodePrefix.length);
+ // The Dart VM Service currently expects a trailing '/' as part of the
+ // URI, otherwise an invalid authentication code response is given.
+ if (!authCode.endsWith('/')) {
+ authCode += '/';
}
- throwToolExit(buffer.toString());
- } else {
- domainName = pointerRecords[0].domainName;
- }
- _logger.printTrace('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) {
- _logger.printWarning('Unexpectedly found more than one observatory report for $domainName '
- '- using first one (${srv.first.port}).');
- }
- _logger.printTrace('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, '');
- }
- const String authCodePrefix = 'authCode=';
- String? raw;
- for (final String record in txt.first.text.split('\n')) {
- if (record.startsWith(authCodePrefix)) {
- raw = record;
- break;
+
+ results.add(MDnsVmServiceDiscoveryResult(
+ domainName,
+ srvRecord.port,
+ authCode,
+ ipAddress: ipAddress
+ ));
+ if (quitOnFind) {
+ return results;
}
}
- if (raw == null) {
- return MDnsObservatoryDiscoveryResult(srv.first.port, '');
+
+ // If applicationId is set and quitOnFind is true and no results matching
+ // the applicationId were found but other results were found, throw an error.
+ if (applicationId != null &&
+ quitOnFind &&
+ results.isEmpty &&
+ uniqueDomainNames.isNotEmpty) {
+ String message = 'Did not find a Dart VM Service advertised for $applicationId';
+ if (deviceVmservicePort != null) {
+ message += ' on port $deviceVmservicePort';
+ }
+ throwToolExit('$message.');
}
- String 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);
+
+ return results;
} finally {
- _client.stop();
+ client.stop();
}
}
- Future<Uri?> getObservatoryUri(String? applicationId, Device device, {
+ /// Gets Dart VM Service Uri for `flutter attach`.
+ /// Executes an mDNS query and waits until a Dart VM Service is found.
+ ///
+ /// Differs from `getVMServiceUriForLaunch` because it can search for any available Dart VM Service.
+ /// Since [applicationId] and [deviceVmservicePort] are optional, it can either look for any service
+ /// or a specific service matching [applicationId]/[deviceVmservicePort].
+ /// It may find more than one service, which will throw an error listing the found services.
+ Future<Uri?> getVMServiceUriForAttach(
+ String? applicationId,
+ Device device, {
bool usesIpv6 = false,
int? hostVmservicePort,
int? deviceVmservicePort,
+ bool isNetworkDevice = false,
+ Duration timeout = const Duration(minutes: 10),
}) async {
- final MDnsObservatoryDiscoveryResult? result = await query(
+ final MDnsVmServiceDiscoveryResult? result = await queryForAttach(
applicationId: applicationId,
deviceVmservicePort: deviceVmservicePort,
+ ipv6: usesIpv6,
+ isNetworkDevice: isNetworkDevice,
+ timeout: timeout,
);
+ return _handleResult(
+ result,
+ device,
+ applicationId: applicationId,
+ deviceVmservicePort: deviceVmservicePort,
+ hostVmservicePort: hostVmservicePort,
+ usesIpv6: usesIpv6,
+ isNetworkDevice: isNetworkDevice
+ );
+ }
+
+ /// Gets Dart VM Service Uri for `flutter run`.
+ /// Executes an mDNS query and waits until the Dart VM Service service is found.
+ ///
+ /// Differs from `getVMServiceUriForAttach` because it only searches for a specific service.
+ /// This is enforced by [applicationId] and [deviceVmservicePort] being required.
+ Future<Uri?> getVMServiceUriForLaunch(
+ String applicationId,
+ Device device, {
+ bool usesIpv6 = false,
+ int? hostVmservicePort,
+ required int deviceVmservicePort,
+ bool isNetworkDevice = false,
+ Duration timeout = const Duration(minutes: 10),
+ }) async {
+ final MDnsVmServiceDiscoveryResult? result = await queryForLaunch(
+ applicationId: applicationId,
+ deviceVmservicePort: deviceVmservicePort,
+ ipv6: usesIpv6,
+ isNetworkDevice: isNetworkDevice,
+ timeout: timeout,
+ );
+ return _handleResult(
+ result,
+ device,
+ applicationId: applicationId,
+ deviceVmservicePort: deviceVmservicePort,
+ hostVmservicePort: hostVmservicePort,
+ usesIpv6: usesIpv6,
+ isNetworkDevice: isNetworkDevice
+ );
+ }
+
+ Future<Uri?> _handleResult(
+ MDnsVmServiceDiscoveryResult? result,
+ Device device, {
+ String? applicationId,
+ int? deviceVmservicePort,
+ int? hostVmservicePort,
+ bool usesIpv6 = false,
+ bool isNetworkDevice = false,
+ }) async {
if (result == null) {
await _checkForIPv4LinkLocal(device);
return null;
}
+ final String host;
- final String host = usesIpv6
+ final InternetAddress? ipAddress = result.ipAddress;
+ if (isNetworkDevice && ipAddress != null) {
+ host = ipAddress.address;
+ } else {
+ host = usesIpv6
? InternetAddress.loopbackIPv6.address
: InternetAddress.loopbackIPv4.address;
- return buildObservatoryUri(
+ }
+ return buildVMServiceUri(
device,
host,
result.port,
hostVmservicePort,
result.authCode,
+ isNetworkDevice,
);
}
@@ -236,18 +502,26 @@
}
}
-class MDnsObservatoryDiscoveryResult {
- MDnsObservatoryDiscoveryResult(this.port, this.authCode);
+class MDnsVmServiceDiscoveryResult {
+ MDnsVmServiceDiscoveryResult(
+ this.domainName,
+ this.port,
+ this.authCode, {
+ this.ipAddress
+ });
+ final String domainName;
final int port;
final String authCode;
+ final InternetAddress? ipAddress;
}
-Future<Uri> buildObservatoryUri(
+Future<Uri> buildVMServiceUri(
Device device,
String host,
int devicePort, [
int? hostVmservicePort,
String? authCode,
+ bool isNetworkDevice = false,
]) async {
String path = '/';
if (authCode != null) {
@@ -259,8 +533,16 @@
path += '/';
}
hostVmservicePort ??= 0;
- final int? actualHostPort = hostVmservicePort == 0 ?
+
+ final int? actualHostPort;
+ if (isNetworkDevice) {
+ // When debugging with a network device, port forwarding is not required
+ // so just use the device's port.
+ actualHostPort = devicePort;
+ } else {
+ actualHostPort = hostVmservicePort == 0 ?
await device.portForwarder?.forward(devicePort) :
hostVmservicePort;
+ }
return Uri(scheme: 'http', host: host, port: actualHostPort, path: path);
}
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index d0a963c..fd9c3ed 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -569,7 +569,7 @@
);
}
- bool get disablePortPublication => !boolArgDeprecated('publish-port');
+ Future<bool> get disablePortPublication async => !boolArgDeprecated('publish-port');
void usesIpv6Flag({required bool verboseHelp}) {
argParser.addFlag(ipv6Flag,
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
index a725c0b..42d91c6 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
@@ -23,6 +23,7 @@
import 'package:flutter_tools/src/device_port_forwarder.dart';
import 'package:flutter_tools/src/ios/application_package.dart';
import 'package:flutter_tools/src/ios/devices.dart';
+import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/macos/macos_ipad_device.dart';
import 'package:flutter_tools/src/mdns_discovery.dart';
import 'package:flutter_tools/src/project.dart';
@@ -83,6 +84,7 @@
group('with one device and no specified target file', () {
const int devicePort = 499;
const int hostPort = 42;
+ final int future = DateTime.now().add(const Duration(days: 1)).millisecondsSinceEpoch;
late FakeDeviceLogReader fakeLogReader;
late RecordingPortForwarder portForwarder;
@@ -102,17 +104,17 @@
fakeLogReader.dispose();
});
- testUsingContext('succeeds with iOS device', () async {
+ testUsingContext('succeeds with iOS device with protocol discovery', () async {
final FakeIOSDevice device = FakeIOSDevice(
logReader: fakeLogReader,
portForwarder: portForwarder,
+ majorSdkVersion: 12,
onGetLogReader: () {
fakeLogReader.addLine('Foo');
fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort');
return fakeLogReader;
},
);
-
testDeviceManager.devices = <Device>[device];
final Completer<void> completer = Completer<void>();
final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
@@ -121,7 +123,20 @@
completer.complete();
}
});
- final Future<void> task = createTestCommandRunner(AttachCommand(
+ final FakeHotRunner hotRunner = FakeHotRunner();
+ hotRunner.onAttach = (
+ Completer<DebugConnectionInfo>? connectionInfoCompleter,
+ Completer<void>? appStartedCompleter,
+ bool allowExistingDdsInstance,
+ bool enableDevTools,
+ ) async => 0;
+ hotRunner.exited = false;
+ hotRunner.isWaitingForObservatory = false;
+ final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory()
+ ..hotRunner = hotRunner;
+
+ await createTestCommandRunner(AttachCommand(
+ hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
@@ -137,15 +152,309 @@
expect(portForwarder.hostPort, hostPort);
await fakeLogReader.dispose();
- await expectLoggerInterruptEndsTask(task, logger);
await loggerSubscription.cancel();
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
Logger: () => logger,
DeviceManager: () => testDeviceManager,
- MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(
+ MDnsVmServiceDiscovery: () => MDnsVmServiceDiscovery(
mdnsClient: FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}),
+ preliminaryMDnsClient: FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}),
+ logger: logger,
+ flutterUsage: TestUsage(),
+ ),
+ });
+
+ testUsingContext('succeeds with iOS device with mDNS', () async {
+ final FakeIOSDevice device = FakeIOSDevice(
+ logReader: fakeLogReader,
+ portForwarder: portForwarder,
+ majorSdkVersion: 16,
+ onGetLogReader: () {
+ fakeLogReader.addLine('Foo');
+ fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort');
+ return fakeLogReader;
+ },
+ );
+ testDeviceManager.devices = <Device>[device];
+ final FakeHotRunner hotRunner = FakeHotRunner();
+ hotRunner.onAttach = (
+ Completer<DebugConnectionInfo>? connectionInfoCompleter,
+ Completer<void>? appStartedCompleter,
+ bool allowExistingDdsInstance,
+ bool enableDevTools,
+ ) async => 0;
+ hotRunner.exited = false;
+ hotRunner.isWaitingForObservatory = false;
+ final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory()
+ ..hotRunner = hotRunner;
+
+ await createTestCommandRunner(AttachCommand(
+ hotRunnerFactory: hotRunnerFactory,
+ artifacts: artifacts,
+ stdio: stdio,
+ logger: logger,
+ terminal: terminal,
+ signals: signals,
+ platform: platform,
+ processInfo: processInfo,
+ fileSystem: testFileSystem,
+ )).run(<String>['attach']);
+ await fakeLogReader.dispose();
+
+ expect(portForwarder.devicePort, devicePort);
+ expect(portForwarder.hostPort, hostPort);
+ expect(hotRunnerFactory.devices, hasLength(1));
+ final FlutterDevice flutterDevice = hotRunnerFactory.devices.first;
+ final Uri? observatoryUri = await flutterDevice.observatoryUris?.first;
+ expect(observatoryUri.toString(), 'http://127.0.0.1:$hostPort/xyz/');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ ProcessManager: () => FakeProcessManager.any(),
+ Logger: () => logger,
+ DeviceManager: () => testDeviceManager,
+ MDnsVmServiceDiscovery: () => MDnsVmServiceDiscovery(
+ mdnsClient: FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}),
+ preliminaryMDnsClient: FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: devicePort, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ txtResponse: <String, List<TxtResourceRecord>>{
+ 'bar': <TxtResourceRecord>[
+ TxtResourceRecord('bar', future, text: 'authCode=xyz\n'),
+ ],
+ },
+ ),
+ logger: logger,
+ flutterUsage: TestUsage(),
+ ),
+ });
+
+ testUsingContext('succeeds with iOS device with mDNS network device', () async {
+ final FakeIOSDevice device = FakeIOSDevice(
+ logReader: fakeLogReader,
+ portForwarder: portForwarder,
+ majorSdkVersion: 16,
+ interfaceType: IOSDeviceConnectionInterface.network,
+ );
+ testDeviceManager.devices = <Device>[device];
+ final FakeHotRunner hotRunner = FakeHotRunner();
+ hotRunner.onAttach = (
+ Completer<DebugConnectionInfo>? connectionInfoCompleter,
+ Completer<void>? appStartedCompleter,
+ bool allowExistingDdsInstance,
+ bool enableDevTools,
+ ) async => 0;
+ hotRunner.exited = false;
+ hotRunner.isWaitingForObservatory = false;
+ final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory()
+ ..hotRunner = hotRunner;
+
+ await createTestCommandRunner(AttachCommand(
+ hotRunnerFactory: hotRunnerFactory,
+ artifacts: artifacts,
+ stdio: stdio,
+ logger: logger,
+ terminal: terminal,
+ signals: signals,
+ platform: platform,
+ processInfo: processInfo,
+ fileSystem: testFileSystem,
+ )).run(<String>['attach']);
+ await fakeLogReader.dispose();
+
+ expect(portForwarder.devicePort, null);
+ expect(portForwarder.hostPort, hostPort);
+ expect(hotRunnerFactory.devices, hasLength(1));
+
+ final FlutterDevice flutterDevice = hotRunnerFactory.devices.first;
+ final Uri? observatoryUri = await flutterDevice.observatoryUris?.first;
+ expect(observatoryUri.toString(), 'http://111.111.111.111:123/xyz/');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ ProcessManager: () => FakeProcessManager.any(),
+ Logger: () => logger,
+ DeviceManager: () => testDeviceManager,
+ MDnsVmServiceDiscovery: () => MDnsVmServiceDiscovery(
+ mdnsClient: FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}),
+ preliminaryMDnsClient: FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'srv-foo'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'srv-foo': <SrvResourceRecord>[
+ SrvResourceRecord('srv-foo', future, port: 123, weight: 1, priority: 1, target: 'target-foo'),
+ ],
+ },
+ ipResponse: <String, List<IPAddressResourceRecord>>{
+ 'target-foo': <IPAddressResourceRecord>[
+ IPAddressResourceRecord('target-foo', 0, address: InternetAddress.tryParse('111.111.111.111')!),
+ ],
+ },
+ txtResponse: <String, List<TxtResourceRecord>>{
+ 'srv-foo': <TxtResourceRecord>[
+ TxtResourceRecord('srv-foo', future, text: 'authCode=xyz\n'),
+ ],
+ },
+ ),
+ logger: logger,
+ flutterUsage: TestUsage(),
+ ),
+ });
+
+ testUsingContext('succeeds with iOS device with mDNS network device with debug-port', () async {
+ final FakeIOSDevice device = FakeIOSDevice(
+ logReader: fakeLogReader,
+ portForwarder: portForwarder,
+ majorSdkVersion: 16,
+ interfaceType: IOSDeviceConnectionInterface.network,
+ );
+ testDeviceManager.devices = <Device>[device];
+ final FakeHotRunner hotRunner = FakeHotRunner();
+ hotRunner.onAttach = (
+ Completer<DebugConnectionInfo>? connectionInfoCompleter,
+ Completer<void>? appStartedCompleter,
+ bool allowExistingDdsInstance,
+ bool enableDevTools,
+ ) async => 0;
+ hotRunner.exited = false;
+ hotRunner.isWaitingForObservatory = false;
+ final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory()
+ ..hotRunner = hotRunner;
+
+ await createTestCommandRunner(AttachCommand(
+ hotRunnerFactory: hotRunnerFactory,
+ artifacts: artifacts,
+ stdio: stdio,
+ logger: logger,
+ terminal: terminal,
+ signals: signals,
+ platform: platform,
+ processInfo: processInfo,
+ fileSystem: testFileSystem,
+ )).run(<String>['attach', '--debug-port', '123']);
+ await fakeLogReader.dispose();
+
+ expect(portForwarder.devicePort, null);
+ expect(portForwarder.hostPort, hostPort);
+ expect(hotRunnerFactory.devices, hasLength(1));
+
+ final FlutterDevice flutterDevice = hotRunnerFactory.devices.first;
+ final Uri? observatoryUri = await flutterDevice.observatoryUris?.first;
+ expect(observatoryUri.toString(), 'http://111.111.111.111:123/xyz/');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ ProcessManager: () => FakeProcessManager.any(),
+ Logger: () => logger,
+ DeviceManager: () => testDeviceManager,
+ MDnsVmServiceDiscovery: () => MDnsVmServiceDiscovery(
+ mdnsClient: FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}),
+ preliminaryMDnsClient: FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('bar', future, domainName: 'srv-bar'),
+ PtrResourceRecord('foo', future, domainName: 'srv-foo'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'srv-bar': <SrvResourceRecord>[
+ SrvResourceRecord('srv-bar', future, port: 321, weight: 1, priority: 1, target: 'target-bar'),
+ ],
+ 'srv-foo': <SrvResourceRecord>[
+ SrvResourceRecord('srv-foo', future, port: 123, weight: 1, priority: 1, target: 'target-foo'),
+ ],
+ },
+ ipResponse: <String, List<IPAddressResourceRecord>>{
+ 'target-foo': <IPAddressResourceRecord>[
+ IPAddressResourceRecord('target-foo', 0, address: InternetAddress.tryParse('111.111.111.111')!),
+ ],
+ },
+ txtResponse: <String, List<TxtResourceRecord>>{
+ 'srv-foo': <TxtResourceRecord>[
+ TxtResourceRecord('srv-foo', future, text: 'authCode=xyz\n'),
+ ],
+ },
+ ),
+ logger: logger,
+ flutterUsage: TestUsage(),
+ ),
+ });
+
+ testUsingContext('succeeds with iOS device with mDNS network device with debug-url', () async {
+ final FakeIOSDevice device = FakeIOSDevice(
+ logReader: fakeLogReader,
+ portForwarder: portForwarder,
+ majorSdkVersion: 16,
+ interfaceType: IOSDeviceConnectionInterface.network,
+ );
+ testDeviceManager.devices = <Device>[device];
+ final FakeHotRunner hotRunner = FakeHotRunner();
+ hotRunner.onAttach = (
+ Completer<DebugConnectionInfo>? connectionInfoCompleter,
+ Completer<void>? appStartedCompleter,
+ bool allowExistingDdsInstance,
+ bool enableDevTools,
+ ) async => 0;
+ hotRunner.exited = false;
+ hotRunner.isWaitingForObservatory = false;
+ final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory()
+ ..hotRunner = hotRunner;
+
+ await createTestCommandRunner(AttachCommand(
+ hotRunnerFactory: hotRunnerFactory,
+ artifacts: artifacts,
+ stdio: stdio,
+ logger: logger,
+ terminal: terminal,
+ signals: signals,
+ platform: platform,
+ processInfo: processInfo,
+ fileSystem: testFileSystem,
+ )).run(<String>['attach', '--debug-url', 'https://0.0.0.0:123']);
+ await fakeLogReader.dispose();
+
+ expect(portForwarder.devicePort, null);
+ expect(portForwarder.hostPort, hostPort);
+ expect(hotRunnerFactory.devices, hasLength(1));
+
+ final FlutterDevice flutterDevice = hotRunnerFactory.devices.first;
+ final Uri? observatoryUri = await flutterDevice.observatoryUris?.first;
+ expect(observatoryUri.toString(), 'http://111.111.111.111:123/xyz/');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ ProcessManager: () => FakeProcessManager.any(),
+ Logger: () => logger,
+ DeviceManager: () => testDeviceManager,
+ MDnsVmServiceDiscovery: () => MDnsVmServiceDiscovery(
+ mdnsClient: FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}),
+ preliminaryMDnsClient: FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('bar', future, domainName: 'srv-bar'),
+ PtrResourceRecord('foo', future, domainName: 'srv-foo'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'srv-bar': <SrvResourceRecord>[
+ SrvResourceRecord('srv-bar', future, port: 321, weight: 1, priority: 1, target: 'target-bar'),
+ ],
+ 'srv-foo': <SrvResourceRecord>[
+ SrvResourceRecord('srv-foo', future, port: 123, weight: 1, priority: 1, target: 'target-foo'),
+ ],
+ },
+ ipResponse: <String, List<IPAddressResourceRecord>>{
+ 'target-foo': <IPAddressResourceRecord>[
+ IPAddressResourceRecord('target-foo', 0, address: InternetAddress.tryParse('111.111.111.111')!),
+ ],
+ },
+ txtResponse: <String, List<TxtResourceRecord>>{
+ 'srv-foo': <TxtResourceRecord>[
+ TxtResourceRecord('srv-foo', future, text: 'authCode=xyz\n'),
+ ],
+ },
+ ),
logger: logger,
flutterUsage: TestUsage(),
),
@@ -979,9 +1288,16 @@
DevicePortForwarder? portForwarder,
DeviceLogReader? logReader,
this.onGetLogReader,
+ this.interfaceType = IOSDeviceConnectionInterface.none,
+ this.majorSdkVersion = 0,
}) : _portForwarder = portForwarder, _logReader = logReader;
final DevicePortForwarder? _portForwarder;
+ @override
+ int majorSdkVersion;
+
+ @override
+ final IOSDeviceConnectionInterface interfaceType;
@override
DevicePortForwarder get portForwarder => _portForwarder!;
@@ -1029,12 +1345,14 @@
class FakeMDnsClient extends Fake implements MDnsClient {
FakeMDnsClient(this.ptrRecords, this.srvResponse, {
this.txtResponse = const <String, List<TxtResourceRecord>>{},
+ this.ipResponse = const <String, List<IPAddressResourceRecord>>{},
this.osErrorOnStart = false,
});
final List<PtrResourceRecord> ptrRecords;
final Map<String, List<SrvResourceRecord>> srvResponse;
final Map<String, List<TxtResourceRecord>> txtResponse;
+ final Map<String, List<IPAddressResourceRecord>> ipResponse;
final bool osErrorOnStart;
@override
@@ -1054,7 +1372,7 @@
ResourceRecordQuery query, {
Duration timeout = const Duration(seconds: 5),
}) {
- if (T == PtrResourceRecord && query.fullyQualifiedName == MDnsObservatoryDiscovery.dartObservatoryName) {
+ if (T == PtrResourceRecord && query.fullyQualifiedName == MDnsVmServiceDiscovery.dartVmServiceName) {
return Stream<PtrResourceRecord>.fromIterable(ptrRecords) as Stream<T>;
}
if (T == SrvResourceRecord) {
@@ -1065,6 +1383,10 @@
final String key = query.fullyQualifiedName;
return Stream<TxtResourceRecord>.fromIterable(txtResponse[key] ?? <TxtResourceRecord>[]) as Stream<T>;
}
+ if (T == IPAddressResourceRecord) {
+ final String key = query.fullyQualifiedName;
+ return Stream<IPAddressResourceRecord>.fromIterable(ipResponse[key] ?? <IPAddressResourceRecord>[]) as Stream<T>;
+ }
throw UnsupportedError('Unsupported query type $T');
}
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
index 19f2eb2..e4d9e63 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
@@ -21,6 +21,8 @@
import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/drive/drive_service.dart';
+import 'package:flutter_tools/src/ios/devices.dart';
+import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:package_config/package_config.dart';
import 'package:test/fake.dart';
@@ -406,6 +408,94 @@
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
+
+ testUsingContext('Port publication not disabled for network device', () async {
+ final DriveCommand command = DriveCommand(
+ fileSystem: fileSystem,
+ logger: logger,
+ platform: platform,
+ signals: signals,
+ );
+
+ fileSystem.file('lib/main.dart').createSync(recursive: true);
+ fileSystem.file('test_driver/main_test.dart').createSync(recursive: true);
+ fileSystem.file('pubspec.yaml').createSync();
+
+ final Device networkDevice = FakeIosDevice()
+ ..interfaceType = IOSDeviceConnectionInterface.network;
+ fakeDeviceManager.devices = <Device>[networkDevice];
+
+ await expectLater(() => createTestCommandRunner(command).run(<String>[
+ 'drive',
+ ]), throwsToolExit());
+
+ final DebuggingOptions options = await command.createDebuggingOptions(false);
+ expect(options.disablePortPublication, false);
+ }, overrides: <Type, Generator>{
+ Cache: () => Cache.test(processManager: FakeProcessManager.any()),
+ FileSystem: () => MemoryFileSystem.test(),
+ ProcessManager: () => FakeProcessManager.any(),
+ DeviceManager: () => fakeDeviceManager,
+ });
+
+ testUsingContext('Port publication is disabled for wired device', () async {
+ final DriveCommand command = DriveCommand(
+ fileSystem: fileSystem,
+ logger: logger,
+ platform: platform,
+ signals: signals,
+ );
+
+ fileSystem.file('lib/main.dart').createSync(recursive: true);
+ fileSystem.file('test_driver/main_test.dart').createSync(recursive: true);
+ fileSystem.file('pubspec.yaml').createSync();
+
+ await expectLater(() => createTestCommandRunner(command).run(<String>[
+ 'drive',
+ ]), throwsToolExit());
+
+ final Device usbDevice = FakeIosDevice()
+ ..interfaceType = IOSDeviceConnectionInterface.usb;
+ fakeDeviceManager.devices = <Device>[usbDevice];
+
+ final DebuggingOptions options = await command.createDebuggingOptions(false);
+ expect(options.disablePortPublication, true);
+ }, overrides: <Type, Generator>{
+ Cache: () => Cache.test(processManager: FakeProcessManager.any()),
+ FileSystem: () => MemoryFileSystem.test(),
+ ProcessManager: () => FakeProcessManager.any(),
+ DeviceManager: () => fakeDeviceManager,
+ });
+
+ testUsingContext('Port publication does not default to enabled for network device if flag manually added', () async {
+ final DriveCommand command = DriveCommand(
+ fileSystem: fileSystem,
+ logger: logger,
+ platform: platform,
+ signals: signals,
+ );
+
+ fileSystem.file('lib/main.dart').createSync(recursive: true);
+ fileSystem.file('test_driver/main_test.dart').createSync(recursive: true);
+ fileSystem.file('pubspec.yaml').createSync();
+
+ final Device networkDevice = FakeIosDevice()
+ ..interfaceType = IOSDeviceConnectionInterface.network;
+ fakeDeviceManager.devices = <Device>[networkDevice];
+
+ await expectLater(() => createTestCommandRunner(command).run(<String>[
+ 'drive',
+ '--no-publish-port'
+ ]), throwsToolExit());
+
+ final DebuggingOptions options = await command.createDebuggingOptions(false);
+ expect(options.disablePortPublication, true);
+ }, overrides: <Type, Generator>{
+ Cache: () => Cache.test(processManager: FakeProcessManager.any()),
+ FileSystem: () => MemoryFileSystem.test(),
+ ProcessManager: () => FakeProcessManager.any(),
+ DeviceManager: () => fakeDeviceManager,
+ });
}
// Unfortunately Device, despite not being immutable, has an `operator ==`.
@@ -577,3 +667,14 @@
@override
Stream<io.ProcessSignal> watch() => controller.stream;
}
+
+// Unfortunately Device, despite not being immutable, has an `operator ==`.
+// Until we fix that, we have to also ignore related lints here.
+// ignore: avoid_implementing_value_types
+class FakeIosDevice extends Fake implements IOSDevice {
+ @override
+ IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.usb;
+
+ @override
+ Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
+}
diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart
index e957f20..d97a6a2 100644
--- a/packages/flutter_tools/test/general.shard/device_test.dart
+++ b/packages/flutter_tools/test/general.shard/device_test.dart
@@ -11,6 +11,7 @@
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';
@@ -554,6 +555,53 @@
);
});
+ testWithoutContext('Get launch arguments for physical device with iPv4 network connection', () {
+ final DebuggingOptions original = DebuggingOptions.enabled(
+ BuildInfo.debug,
+ );
+
+ final List<String> launchArguments = original.getIOSLaunchArguments(
+ EnvironmentType.physical,
+ null,
+ <String, Object?>{},
+ interfaceType: IOSDeviceConnectionInterface.network,
+ );
+
+ expect(
+ launchArguments.join(' '),
+ <String>[
+ '--enable-dart-profiling',
+ '--enable-checked-mode',
+ '--verify-entry-points',
+ '--observatory-host=0.0.0.0',
+ ].join(' '),
+ );
+ });
+
+ testWithoutContext('Get launch arguments for physical device with iPv6 network connection', () {
+ final DebuggingOptions original = DebuggingOptions.enabled(
+ BuildInfo.debug,
+ );
+
+ final List<String> launchArguments = original.getIOSLaunchArguments(
+ EnvironmentType.physical,
+ null,
+ <String, Object?>{},
+ ipv6: true,
+ interfaceType: IOSDeviceConnectionInterface.network,
+ );
+
+ expect(
+ launchArguments.join(' '),
+ <String>[
+ '--enable-dart-profiling',
+ '--enable-checked-mode',
+ '--verify-entry-points',
+ '--observatory-host=::0',
+ ].join(' '),
+ );
+ });
+
testWithoutContext('Get launch arguments for physical device with debugging disabled with available launch arguments', () {
final DebuggingOptions original = DebuggingOptions.disabled(
BuildInfo.debug,
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
index c212a08..e26ef2b 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
@@ -19,9 +19,11 @@
import 'package:flutter_tools/src/ios/ios_deploy.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/ios/mac.dart';
+import 'package:flutter_tools/src/mdns_discovery.dart';
import 'package:test/fake.dart';
import '../../src/common.dart';
+import '../../src/context.dart';
import '../../src/fake_devices.dart';
import '../../src/fake_process_manager.dart';
import '../../src/fakes.dart';
@@ -66,9 +68,10 @@
FakeCommand attachDebuggerCommand({
IOSink? stdin,
Completer<void>? completer,
+ bool isNetworkDevice = false,
}) {
return FakeCommand(
- command: const <String>[
+ command: <String>[
'script',
'-t',
'0',
@@ -79,9 +82,12 @@
'--bundle',
'/',
'--debug',
- '--no-wifi',
+ if (!isNetworkDevice) '--no-wifi',
'--args',
- '--enable-dart-profiling --enable-checked-mode --verify-entry-points',
+ if (isNetworkDevice)
+ '--enable-dart-profiling --enable-checked-mode --verify-entry-points --observatory-host=0.0.0.0'
+ else
+ '--enable-dart-profiling --enable-checked-mode --verify-entry-points',
],
completer: completer,
environment: const <String, String>{
@@ -188,7 +194,7 @@
expect(await device.stopApp(iosApp), false);
});
- testWithoutContext('IOSDevice.startApp prints warning message if discovery takes longer than configured timeout', () async {
+ testWithoutContext('IOSDevice.startApp prints warning message if discovery takes longer than configured timeout for wired device', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final BufferLogger logger = BufferLogger.test();
final CompleterIOSink stdin = CompleterIOSink();
@@ -226,12 +232,59 @@
expect(launchResult.started, true);
expect(launchResult.hasObservatory, true);
expect(await device.stopApp(iosApp), false);
- expect(logger.errorText, contains('iOS Observatory not discovered after 30 seconds. This is taking much longer than expected...'));
+ expect(logger.errorText, contains('The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...'));
expect(utf8.decoder.convert(stdin.writes.first), contains('process interrupt'));
completer.complete();
expect(processManager, hasNoRemainingExpectations);
});
+ testUsingContext('IOSDevice.startApp prints warning message if discovery takes longer than configured timeout for wireless device', () async {
+ final FileSystem fileSystem = MemoryFileSystem.test();
+ final BufferLogger logger = BufferLogger.test();
+ final CompleterIOSink stdin = CompleterIOSink();
+ final Completer<void> completer = Completer<void>();
+ final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
+ attachDebuggerCommand(stdin: stdin, completer: completer, isNetworkDevice: true),
+ ]);
+ final IOSDevice device = setUpIOSDevice(
+ processManager: processManager,
+ fileSystem: fileSystem,
+ logger: logger,
+ interfaceType: IOSDeviceConnectionInterface.network,
+ );
+ final IOSApp iosApp = PrebuiltIOSApp(
+ projectBundleId: 'app',
+ bundleName: 'Runner',
+ uncompressedBundle: fileSystem.currentDirectory,
+ applicationPackage: fileSystem.currentDirectory,
+ );
+ final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
+
+ device.portForwarder = const NoOpDevicePortForwarder();
+ device.setLogReader(iosApp, deviceLogReader);
+
+ // Start writing messages to the log reader.
+ deviceLogReader.addLine('Foo');
+ deviceLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:456');
+
+ final LaunchResult launchResult = await device.startApp(iosApp,
+ prebuiltApplication: true,
+ debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
+ platformArgs: <String, dynamic>{},
+ discoveryTimeout: Duration.zero,
+ );
+
+ expect(launchResult.started, true);
+ expect(launchResult.hasObservatory, true);
+ expect(await device.stopApp(iosApp), false);
+ expect(logger.errorText, contains('The Dart VM Service was not discovered after 45 seconds. This is taking much longer than expected...'));
+ expect(logger.errorText, contains('Click "Allow" to the prompt asking if you would like to find and connect devices on your local network.'));
+ completer.complete();
+ expect(processManager, hasNoRemainingExpectations);
+ }, overrides: <Type, Generator>{
+ MDnsVmServiceDiscovery: () => FakeMDnsVmServiceDiscovery(),
+ });
+
testWithoutContext('IOSDevice.startApp succeeds in release mode', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
@@ -505,6 +558,7 @@
Logger? logger,
ProcessManager? processManager,
IOSDeploy? iosDeploy,
+ IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.usb,
}) {
final Artifacts artifacts = Artifacts.test();
final FakePlatform macPlatform = FakePlatform(
@@ -542,7 +596,7 @@
cache: cache,
),
cpuArchitecture: DarwinArch.arm64,
- interfaceType: IOSDeviceConnectionInterface.usb,
+ interfaceType: interfaceType,
);
}
@@ -554,3 +608,18 @@
disposed = true;
}
}
+
+class FakeMDnsVmServiceDiscovery extends Fake implements MDnsVmServiceDiscovery {
+ @override
+ Future<Uri?> getVMServiceUriForLaunch(
+ String applicationId,
+ Device device, {
+ bool usesIpv6 = false,
+ int? hostVmservicePort,
+ required int deviceVmservicePort,
+ bool isNetworkDevice = false,
+ Duration timeout = Duration.zero,
+ }) async {
+ return Uri.tryParse('http://0.0.0.0:1234');
+ }
+}
diff --git a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart
index 3013f5f..ee97168 100644
--- a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart
+++ b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart
@@ -479,7 +479,7 @@
stdout: devicesOutput,
));
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
- expect(devices, hasLength(3));
+ expect(devices, hasLength(4));
expect(devices[0].id, '00008027-00192736010F802E');
expect(devices[0].name, 'An iPhone (Space Gray)');
expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54');
@@ -488,10 +488,14 @@
expect(devices[1].name, 'iPad 1');
expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54');
expect(devices[1].cpuArchitecture, DarwinArch.armv7);
- expect(devices[2].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4');
- expect(devices[2].name, 'iPad 2');
+ expect(devices[2].id, '234234234234234234345445687594e089dede3c44');
+ expect(devices[2].name, 'A networked iPad');
expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54');
expect(devices[2].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
+ expect(devices[3].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4');
+ expect(devices[3].name, 'iPad 2');
+ expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54');
+ expect(devices[3].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
diff --git a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart
index 42fae0e..8bba83f 100644
--- a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart
+++ b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart
@@ -17,7 +17,7 @@
void main() {
group('mDNS Discovery', () {
- final int year3000 = DateTime(3000).millisecondsSinceEpoch;
+ final int future = DateTime.now().add(const Duration(days: 1)).millisecondsSinceEpoch;
setUp(() {
setNetworkInterfaceLister(
@@ -33,209 +33,652 @@
resetNetworkInterfaceLister();
});
+ group('for attach', () {
+ late MDnsClient emptyClient;
- testWithoutContext('No ports available', () async {
- final MDnsClient client = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
+ setUp(() {
+ emptyClient = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
+ });
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
- mdnsClient: client,
- logger: BufferLogger.test(),
- flutterUsage: TestUsage(),
- );
- final int? port = (await portDiscovery.query())?.port;
- expect(port, isNull);
+ testWithoutContext('Find result in preliminary client', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ );
+
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: emptyClient,
+ preliminaryMDnsClient: client,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+
+ final MDnsVmServiceDiscoveryResult? result = await portDiscovery.queryForAttach();
+ expect(result, isNotNull);
+ });
+
+ testWithoutContext('Do not find result in preliminary client, but find in main client', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ );
+
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+
+ final MDnsVmServiceDiscoveryResult? result = await portDiscovery.queryForAttach();
+ expect(result, isNotNull);
+ });
+
+ testWithoutContext('Find multiple in preliminary client', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ PtrResourceRecord('baz', future, domainName: 'fiz'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ 'fiz': <SrvResourceRecord>[
+ SrvResourceRecord('fiz', future, port: 321, weight: 1, priority: 1, target: 'local'),
+ ],
+ },
+ );
+
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: emptyClient,
+ preliminaryMDnsClient: client,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+
+ expect(portDiscovery.queryForAttach, throwsToolExit());
+ });
+
+ testWithoutContext('No ports available', () async {
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: emptyClient,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+
+ final int? port = (await portDiscovery.queryForAttach())?.port;
+ expect(port, isNull);
+ });
+
+ testWithoutContext('Prints helpful message when there is no ipv4 link local address.', () async {
+ final BufferLogger logger = BufferLogger.test();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: emptyClient,
+ preliminaryMDnsClient: emptyClient,
+ logger: logger,
+ flutterUsage: TestUsage(),
+ );
+ final Uri? uri = await portDiscovery.getVMServiceUriForAttach(
+ '',
+ FakeIOSDevice(),
+ );
+ expect(uri, isNull);
+ expect(logger.errorText, contains('Personal Hotspot'));
+ });
+
+ testWithoutContext('One port available, no appId', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ );
+
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ final int? port = (await portDiscovery.queryForAttach())?.port;
+ expect(port, 123);
+ });
+
+ testWithoutContext('One port available, no appId, with authCode', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ txtResponse: <String, List<TxtResourceRecord>>{
+ 'bar': <TxtResourceRecord>[
+ TxtResourceRecord('bar', future, text: 'authCode=xyz\n'),
+ ],
+ },
+ );
+
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ final MDnsVmServiceDiscoveryResult? result = await portDiscovery.queryForAttach();
+ expect(result?.port, 123);
+ expect(result?.authCode, 'xyz/');
+ });
+
+ testWithoutContext('Multiple ports available, with appId', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ PtrResourceRecord('baz', future, domainName: 'fiz'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ 'fiz': <SrvResourceRecord>[
+ SrvResourceRecord('fiz', future, port: 321, weight: 1, priority: 1, target: 'local'),
+ ],
+ },
+ );
+
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ final int? port = (await portDiscovery.queryForAttach(applicationId: 'fiz'))?.port;
+ expect(port, 321);
+ });
+
+ testWithoutContext('Multiple ports available per process, with appId', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ PtrResourceRecord('baz', future, domainName: 'fiz'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 1234, weight: 1, priority: 1, target: 'appId'),
+ SrvResourceRecord('bar', future, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ 'fiz': <SrvResourceRecord>[
+ SrvResourceRecord('fiz', future, port: 4321, weight: 1, priority: 1, target: 'local'),
+ SrvResourceRecord('fiz', future, port: 321, weight: 1, priority: 1, target: 'local'),
+ ],
+ },
+ );
+
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ final int? port = (await portDiscovery.queryForAttach(applicationId: 'bar'))?.port;
+ expect(port, 1234);
+ });
+
+ testWithoutContext('Throws Exception when client throws OSError on start', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[], <String, List<SrvResourceRecord>>{},
+ osErrorOnStart: true,
+ );
+
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ expect(
+ () async => portDiscovery.queryForAttach(),
+ throwsException,
+ );
+ });
+
+ testWithoutContext('Correctly builds VM Service URI with hostVmservicePort == 0', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ );
+
+ final FakeIOSDevice device = FakeIOSDevice();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ final Uri? uri = await portDiscovery.getVMServiceUriForAttach('bar', device, hostVmservicePort: 0);
+ expect(uri.toString(), 'http://127.0.0.1:123/');
+ });
+
+ testWithoutContext('Get network device IP (iPv4)', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 1234, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ ipResponse: <String, List<IPAddressResourceRecord>>{
+ 'appId': <IPAddressResourceRecord>[
+ IPAddressResourceRecord('Device IP', 0, address: InternetAddress.tryParse('111.111.111.111')!),
+ ],
+ },
+ txtResponse: <String, List<TxtResourceRecord>>{
+ 'bar': <TxtResourceRecord>[
+ TxtResourceRecord('bar', future, text: 'authCode=xyz\n'),
+ ],
+ },
+ );
+
+ final FakeIOSDevice device = FakeIOSDevice();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ final Uri? uri = await portDiscovery.getVMServiceUriForAttach(
+ 'bar',
+ device,
+ isNetworkDevice: true,
+ );
+ expect(uri.toString(), 'http://111.111.111.111:1234/xyz/');
+ });
+
+ testWithoutContext('Get network device IP (iPv6)', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 1234, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ ipResponse: <String, List<IPAddressResourceRecord>>{
+ 'appId': <IPAddressResourceRecord>[
+ IPAddressResourceRecord('Device IP', 0, address: InternetAddress.tryParse('1111:1111:1111:1111:1111:1111:1111:1111')!),
+ ],
+ },
+ txtResponse: <String, List<TxtResourceRecord>>{
+ 'bar': <TxtResourceRecord>[
+ TxtResourceRecord('bar', future, text: 'authCode=xyz\n'),
+ ],
+ },
+ );
+
+ final FakeIOSDevice device = FakeIOSDevice();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ final Uri? uri = await portDiscovery.getVMServiceUriForAttach(
+ 'bar',
+ device,
+ isNetworkDevice: true,
+ );
+ expect(uri.toString(), 'http://[1111:1111:1111:1111:1111:1111:1111:1111]:1234/xyz/');
+ });
+
+ testWithoutContext('Throw error if unable to find VM service with app id and device port', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'srv-foo'),
+ PtrResourceRecord('bar', future, domainName: 'srv-bar'),
+ PtrResourceRecord('baz', future, domainName: 'srv-boo'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'srv-foo': <SrvResourceRecord>[
+ SrvResourceRecord('srv-foo', future, port: 123, weight: 1, priority: 1, target: 'target-foo'),
+ ],
+ 'srv-bar': <SrvResourceRecord>[
+ SrvResourceRecord('srv-bar', future, port: 123, weight: 1, priority: 1, target: 'target-bar'),
+ ],
+ 'srv-baz': <SrvResourceRecord>[
+ SrvResourceRecord('srv-baz', future, port: 123, weight: 1, priority: 1, target: 'target-baz'),
+ ],
+ },
+ );
+ final FakeIOSDevice device = FakeIOSDevice();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ expect(
+ portDiscovery.getVMServiceUriForAttach(
+ 'srv-bar',
+ device,
+ deviceVmservicePort: 321,
+ ),
+ throwsToolExit(
+ message: 'Did not find a Dart VM Service advertised for srv-bar on port 321.'
+ ),
+ );
+ });
+
+ testWithoutContext('Throw error if unable to find VM Service with app id', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'srv-foo'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'srv-foo': <SrvResourceRecord>[
+ SrvResourceRecord('srv-foo', future, port: 123, weight: 1, priority: 1, target: 'target-foo'),
+ ],
+ },
+ );
+ final FakeIOSDevice device = FakeIOSDevice();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ preliminaryMDnsClient: emptyClient,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ expect(
+ portDiscovery.getVMServiceUriForAttach(
+ 'srv-asdf',
+ device,
+ ),
+ throwsToolExit(
+ message: 'Did not find a Dart VM Service advertised for srv-asdf.'
+ ),
+ );
+ });
});
- testWithoutContext('Prints helpful message when there is no ipv4 link local address.', () async {
- final MDnsClient client = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
- final BufferLogger logger = BufferLogger.test();
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
- mdnsClient: client,
- logger: logger,
- flutterUsage: TestUsage(),
- );
- final Uri? uri = await portDiscovery.getObservatoryUri(
- '',
- FakeIOSDevice(),
- );
- expect(uri, isNull);
- expect(logger.errorText, contains('Personal Hotspot'));
+ group('for launch', () {
+ testWithoutContext('No ports available', () async {
+ final MDnsClient client = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
+
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+
+ final MDnsVmServiceDiscoveryResult? result = await portDiscovery.queryForLaunch(
+ applicationId: 'app-id',
+ deviceVmservicePort: 123,
+ );
+
+ expect(result, null);
+ });
+
+ testWithoutContext('Prints helpful message when there is no ipv4 link local address.', () async {
+ final MDnsClient client = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
+ final BufferLogger logger = BufferLogger.test();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ logger: logger,
+ flutterUsage: TestUsage(),
+ );
+
+ final Uri? uri = await portDiscovery.getVMServiceUriForLaunch(
+ '',
+ FakeIOSDevice(),
+ deviceVmservicePort: 0,
+ );
+ expect(uri, isNull);
+ expect(logger.errorText, contains('Personal Hotspot'));
+ });
+
+ testWithoutContext('Throws Exception when client throws OSError on start', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[], <String, List<SrvResourceRecord>>{},
+ osErrorOnStart: true,
+ );
+
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ expect(
+ () async => portDiscovery.queryForLaunch(applicationId: 'app-id', deviceVmservicePort: 123),
+ throwsException,
+ );
+ });
+
+ testWithoutContext('Correctly builds VM Service URI with hostVmservicePort == 0', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ );
+
+ final FakeIOSDevice device = FakeIOSDevice();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ final Uri? uri = await portDiscovery.getVMServiceUriForLaunch(
+ 'bar',
+ device,
+ hostVmservicePort: 0,
+ deviceVmservicePort: 123,
+ );
+ expect(uri.toString(), 'http://127.0.0.1:123/');
+ });
+
+ testWithoutContext('Get network device IP (iPv4)', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 1234, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ ipResponse: <String, List<IPAddressResourceRecord>>{
+ 'appId': <IPAddressResourceRecord>[
+ IPAddressResourceRecord('Device IP', 0, address: InternetAddress.tryParse('111.111.111.111')!),
+ ],
+ },
+ txtResponse: <String, List<TxtResourceRecord>>{
+ 'bar': <TxtResourceRecord>[
+ TxtResourceRecord('bar', future, text: 'authCode=xyz\n'),
+ ],
+ },
+ );
+
+ final FakeIOSDevice device = FakeIOSDevice();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ final Uri? uri = await portDiscovery.getVMServiceUriForLaunch(
+ 'bar',
+ device,
+ isNetworkDevice: true,
+ deviceVmservicePort: 1234,
+ );
+ expect(uri.toString(), 'http://111.111.111.111:1234/xyz/');
+ });
+
+ testWithoutContext('Get network device IP (iPv6)', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', future, port: 1234, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ ipResponse: <String, List<IPAddressResourceRecord>>{
+ 'appId': <IPAddressResourceRecord>[
+ IPAddressResourceRecord('Device IP', 0, address: InternetAddress.tryParse('1111:1111:1111:1111:1111:1111:1111:1111')!),
+ ],
+ },
+ txtResponse: <String, List<TxtResourceRecord>>{
+ 'bar': <TxtResourceRecord>[
+ TxtResourceRecord('bar', future, text: 'authCode=xyz\n'),
+ ],
+ },
+ );
+
+ final FakeIOSDevice device = FakeIOSDevice();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ final Uri? uri = await portDiscovery.getVMServiceUriForLaunch(
+ 'bar',
+ device,
+ isNetworkDevice: true,
+ deviceVmservicePort: 1234,
+ );
+ expect(uri.toString(), 'http://[1111:1111:1111:1111:1111:1111:1111:1111]:1234/xyz/');
+ });
+
+ testWithoutContext('Throw error if unable to find VM Service with app id and device port', () async {
+ final MDnsClient client = FakeMDnsClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', future, domainName: 'srv-foo'),
+ PtrResourceRecord('bar', future, domainName: 'srv-bar'),
+ PtrResourceRecord('baz', future, domainName: 'srv-boo'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'srv-foo': <SrvResourceRecord>[
+ SrvResourceRecord('srv-foo', future, port: 123, weight: 1, priority: 1, target: 'target-foo'),
+ ],
+ 'srv-bar': <SrvResourceRecord>[
+ SrvResourceRecord('srv-bar', future, port: 123, weight: 1, priority: 1, target: 'target-bar'),
+ ],
+ 'srv-baz': <SrvResourceRecord>[
+ SrvResourceRecord('srv-baz', future, port: 123, weight: 1, priority: 1, target: 'target-baz'),
+ ],
+ },
+ );
+ final FakeIOSDevice device = FakeIOSDevice();
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+ mdnsClient: client,
+ logger: BufferLogger.test(),
+ flutterUsage: TestUsage(),
+ );
+ expect(
+ portDiscovery.getVMServiceUriForLaunch(
+ 'srv-bar',
+ device,
+ deviceVmservicePort: 321,
+ ),
+ throwsToolExit(
+ message:'Did not find a Dart VM Service advertised for srv-bar on port 321.'),
+ );
+ });
});
- testWithoutContext('One port available, no appId', () async {
+ testWithoutContext('Find firstMatchingVmService with many available and no application id', () async {
final MDnsClient client = FakeMDnsClient(
<PtrResourceRecord>[
- PtrResourceRecord('foo', year3000, domainName: 'bar'),
+ PtrResourceRecord('foo', future, domainName: 'srv-foo'),
+ PtrResourceRecord('bar', future, domainName: 'srv-bar'),
+ PtrResourceRecord('baz', future, domainName: 'srv-boo'),
],
<String, List<SrvResourceRecord>>{
- 'bar': <SrvResourceRecord>[
- SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+ 'srv-foo': <SrvResourceRecord>[
+ SrvResourceRecord('srv-foo', future, port: 123, weight: 1, priority: 1, target: 'target-foo'),
+ ],
+ 'srv-bar': <SrvResourceRecord>[
+ SrvResourceRecord('srv-bar', future, port: 123, weight: 1, priority: 1, target: 'target-bar'),
+ ],
+ 'srv-baz': <SrvResourceRecord>[
+ SrvResourceRecord('srv-baz', future, port: 123, weight: 1, priority: 1, target: 'target-baz'),
],
},
);
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: TestUsage(),
);
- final int? port = (await portDiscovery.query())?.port;
- expect(port, 123);
+ final MDnsVmServiceDiscoveryResult? result = await portDiscovery.firstMatchingVmService(client);
+ expect(result?.domainName, 'srv-foo');
});
- testWithoutContext('One port available, no appId, with authCode', () async {
+ testWithoutContext('Find firstMatchingVmService app id', () async {
final MDnsClient client = FakeMDnsClient(
<PtrResourceRecord>[
- PtrResourceRecord('foo', year3000, domainName: 'bar'),
+ PtrResourceRecord('foo', future, domainName: 'srv-foo'),
+ PtrResourceRecord('bar', future, domainName: 'srv-bar'),
+ PtrResourceRecord('baz', future, domainName: 'srv-boo'),
],
<String, List<SrvResourceRecord>>{
- 'bar': <SrvResourceRecord>[
- SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+ 'srv-foo': <SrvResourceRecord>[
+ SrvResourceRecord('srv-foo', future, port: 111, weight: 1, priority: 1, target: 'target-foo'),
],
- },
- txtResponse: <String, List<TxtResourceRecord>>{
- 'bar': <TxtResourceRecord>[
- TxtResourceRecord('bar', year3000, text: 'authCode=xyz\n'),
+ 'srv-bar': <SrvResourceRecord>[
+ SrvResourceRecord('srv-bar', future, port: 222, weight: 1, priority: 1, target: 'target-bar'),
+ SrvResourceRecord('srv-bar', future, port: 333, weight: 1, priority: 1, target: 'target-bar-2'),
+ ],
+ 'srv-baz': <SrvResourceRecord>[
+ SrvResourceRecord('srv-baz', future, port: 444, weight: 1, priority: 1, target: 'target-baz'),
],
},
);
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
+ final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
mdnsClient: client,
logger: BufferLogger.test(),
flutterUsage: TestUsage(),
);
- final MDnsObservatoryDiscoveryResult? result = await portDiscovery.query();
- expect(result?.port, 123);
- expect(result?.authCode, 'xyz/');
- });
-
- testWithoutContext('Multiple ports available, without appId', () async {
- final MDnsClient client = FakeMDnsClient(
- <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 MDnsVmServiceDiscoveryResult? result = await portDiscovery.firstMatchingVmService(
+ client,
+ applicationId: 'srv-bar'
);
-
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
- mdnsClient: client,
- logger: BufferLogger.test(),
- flutterUsage: TestUsage(),
- );
- expect(portDiscovery.query, throwsToolExit());
- });
-
- testWithoutContext('Multiple ports available, with appId', () async {
- final MDnsClient client = FakeMDnsClient(
- <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,
- logger: BufferLogger.test(),
- flutterUsage: TestUsage(),
- );
- final int? port = (await portDiscovery.query(applicationId: 'fiz'))?.port;
- expect(port, 321);
- });
-
- testWithoutContext('Multiple ports available per process, with appId', () async {
- final MDnsClient client = FakeMDnsClient(
- <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,
- logger: BufferLogger.test(),
- flutterUsage: TestUsage(),
- );
- final int? port = (await portDiscovery.query(applicationId: 'bar'))?.port;
- expect(port, 1234);
- });
-
- testWithoutContext('Query returns null', () async {
- final MDnsClient client = FakeMDnsClient(
- <PtrResourceRecord>[],
- <String, List<SrvResourceRecord>>{},
- );
-
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
- mdnsClient: client,
- logger: BufferLogger.test(),
- flutterUsage: TestUsage(),
- );
- final int? port = (await portDiscovery.query(applicationId: 'bar'))?.port;
- expect(port, isNull);
- });
-
- testWithoutContext('Throws Exception when client throws OSError on start', () async {
- final MDnsClient client = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}, osErrorOnStart: true);
-
-
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
- mdnsClient: client,
- logger: BufferLogger.test(),
- flutterUsage: TestUsage(),
- );
- expect(
- () async => portDiscovery.query(),
- throwsException,
- );
- });
-
- testWithoutContext('Correctly builds Observatory URI with hostVmservicePort == 0', () async {
- final MDnsClient client = FakeMDnsClient(
- <PtrResourceRecord>[
- PtrResourceRecord('foo', year3000, domainName: 'bar'),
- ],
- <String, List<SrvResourceRecord>>{
- 'bar': <SrvResourceRecord>[
- SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
- ],
- },
- );
-
- final FakeIOSDevice device = FakeIOSDevice();
- final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
- mdnsClient: client,
- logger: BufferLogger.test(),
- flutterUsage: TestUsage(),
- );
- final Uri? uri = await portDiscovery.getObservatoryUri('bar', device, hostVmservicePort: 0);
- expect(uri.toString(), 'http://127.0.0.1:123/');
+ expect(result?.domainName, 'srv-bar');
+ expect(result?.port, 222);
});
});
}
@@ -243,12 +686,14 @@
class FakeMDnsClient extends Fake implements MDnsClient {
FakeMDnsClient(this.ptrRecords, this.srvResponse, {
this.txtResponse = const <String, List<TxtResourceRecord>>{},
+ this.ipResponse = const <String, List<IPAddressResourceRecord>>{},
this.osErrorOnStart = false,
});
final List<PtrResourceRecord> ptrRecords;
final Map<String, List<SrvResourceRecord>> srvResponse;
final Map<String, List<TxtResourceRecord>> txtResponse;
+ final Map<String, List<IPAddressResourceRecord>> ipResponse;
final bool osErrorOnStart;
@override
@@ -268,7 +713,7 @@
ResourceRecordQuery query, {
Duration timeout = const Duration(seconds: 5),
}) {
- if (T == PtrResourceRecord && query.fullyQualifiedName == MDnsObservatoryDiscovery.dartObservatoryName) {
+ if (T == PtrResourceRecord && query.fullyQualifiedName == MDnsVmServiceDiscovery.dartVmServiceName) {
return Stream<PtrResourceRecord>.fromIterable(ptrRecords) as Stream<T>;
}
if (T == SrvResourceRecord) {
@@ -279,6 +724,10 @@
final String key = query.fullyQualifiedName;
return Stream<TxtResourceRecord>.fromIterable(txtResponse[key] ?? <TxtResourceRecord>[]) as Stream<T>;
}
+ if (T == IPAddressResourceRecord) {
+ final String key = query.fullyQualifiedName;
+ return Stream<IPAddressResourceRecord>.fromIterable(ipResponse[key] ?? <IPAddressResourceRecord>[]) as Stream<T>;
+ }
throw UnsupportedError('Unsupported query type $T');
}