From 27248d4b64229767c1cc351976150d528feffce9 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth Date: Wed, 15 Mar 2023 11:35:05 -0500 Subject: [PATCH] Separate attached and wireless devices (#122615) Separate attached and wireless devices --- .../lib/src/android/android_device.dart | 8 + .../lib/src/base/user_messages.dart | 2 +- .../lib/src/commands/attach.dart | 3 +- .../lib/src/commands/devices.dart | 82 +- .../flutter_tools/lib/src/commands/drive.dart | 3 +- .../flutter_tools/lib/src/commands/run.dart | 5 +- packages/flutter_tools/lib/src/device.dart | 5 +- .../flutter_tools/lib/src/ios/devices.dart | 25 +- .../flutter_tools/lib/src/ios/ios_deploy.dart | 14 +- .../flutter_tools/lib/src/ios/iproxy.dart | 6 - packages/flutter_tools/lib/src/ios/mac.dart | 6 +- .../flutter_tools/lib/src/macos/xcdevice.dart | 24 +- .../lib/src/runner/target_devices.dart | 328 ++-- .../commands.shard/hermetic/attach_test.dart | 19 +- .../commands.shard/hermetic/devices_test.dart | 208 ++- .../commands.shard/hermetic/drive_test.dart | 31 +- .../commands.shard/hermetic/install_test.dart | 15 +- .../commands.shard/hermetic/run_test.dart | 27 +- .../commands.shard/hermetic/test_test.dart | 9 +- .../permeable/devices_test.dart | 7 + .../android_device_discovery_test.dart | 31 +- .../test/general.shard/device_test.dart | 5 +- .../test/general.shard/ios/devices_test.dart | 26 +- .../general.shard/ios/ios_deploy_test.dart | 4 +- .../ios/ios_device_install_test.dart | 9 +- .../ios/ios_device_project_test.dart | 3 +- .../ios_device_start_nonprebuilt_test.dart | 2 +- .../ios/ios_device_start_prebuilt_test.dart | 6 +- .../test/general.shard/ios/mac_test.dart | 8 +- .../test/general.shard/macos/xcode_test.dart | 10 + .../runner/flutter_command_test.dart | 6 +- .../runner/target_devices_test.dart | 1314 ++++++++++++----- packages/flutter_tools/test/src/context.dart | 22 +- .../flutter_tools/test/src/fake_devices.dart | 25 + 34 files changed, 1637 insertions(+), 661 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index eadc603fb9..3385513365 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -89,6 +89,14 @@ class AndroidDevice extends Device { final String modelID; final String? deviceCodeName; + @override + // Wirelessly paired Android devices should have `adb-tls-connect` in the id. + // Source: https://android.googlesource.com/platform/packages/modules/adb/+/f4ba8d73079b99532069dbe888a58167b8723d6c/adb_mdns.h#30 + DeviceConnectionInterface get connectionInterface => + id.contains('adb-tls-connect') + ? DeviceConnectionInterface.wireless + : DeviceConnectionInterface.attached; + late final Future> _properties = () async { Map properties = {}; diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart index 11ec334021..31487b9fdc 100644 --- a/packages/flutter_tools/lib/src/base/user_messages.dart +++ b/packages/flutter_tools/lib/src/base/user_messages.dart @@ -265,7 +265,7 @@ class UserMessages { 'for information about installing additional components.'; String flutterNoMatchingDevice(String deviceId) => 'No supported devices found with name or id ' "matching '$deviceId'."; - String get flutterNoDevicesFound => 'No devices found'; + String get flutterNoDevicesFound => 'No devices found.'; String get flutterNoSupportedDevices => 'No supported devices connected.'; String flutterMissPlatformProjects(List unsupportedDevicesType) => 'If you would like your app to run on ${unsupportedDevicesType.join(' or ')}, consider running `flutter create .` to generate projects for these platforms.'; diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart index b3dd7bfd40..21cae52f3c 100644 --- a/packages/flutter_tools/lib/src/commands/attach.dart +++ b/packages/flutter_tools/lib/src/commands/attach.dart @@ -24,7 +24,6 @@ import '../device.dart'; 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'; @@ -287,7 +286,7 @@ known, it can be explicitly provided to attach via the command-line, e.g. 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; + final bool isNetworkDevice = (device is IOSDevice) && device.isWirelesslyConnected; if ((debugPort == null && debugUri == null) || isNetworkDevice) { if (device is FuchsiaDevice) { diff --git a/packages/flutter_tools/lib/src/commands/devices.dart b/packages/flutter_tools/lib/src/commands/devices.dart index 8e392f1cb8..8e8d6112c4 100644 --- a/packages/flutter_tools/lib/src/commands/devices.dart +++ b/packages/flutter_tools/lib/src/commands/devices.dart @@ -79,31 +79,71 @@ class DevicesCommandOutput { final Duration? deviceDiscoveryTimeout; + Future> _getAttachedDevices(DeviceManager deviceManager) async { + return deviceManager.getAllDevices( + filter: DeviceDiscoveryFilter( + deviceConnectionInterface: DeviceConnectionInterface.attached, + ), + ); + } + + Future> _getWirelessDevices(DeviceManager deviceManager) async { + return deviceManager.getAllDevices( + filter: DeviceDiscoveryFilter( + deviceConnectionInterface: DeviceConnectionInterface.wireless, + ), + ); + } + Future findAndOutputAllTargetDevices({required bool machine}) async { - final List devices = await globals.deviceManager?.refreshAllDevices(timeout: deviceDiscoveryTimeout) ?? []; + List attachedDevices = []; + List wirelessDevices = []; + final DeviceManager? deviceManager = globals.deviceManager; + if (deviceManager != null) { + // Refresh the cache and then get the attached and wireless devices from + // the cache. + await deviceManager.refreshAllDevices(timeout: deviceDiscoveryTimeout); + attachedDevices = await _getAttachedDevices(deviceManager); + wirelessDevices = await _getWirelessDevices(deviceManager); + } + final List allDevices = attachedDevices + wirelessDevices; if (machine) { - await printDevicesAsJson(devices); - } else { - if (devices.isEmpty) { - final StringBuffer status = StringBuffer('No devices detected.'); - status.writeln(); - status.writeln(); - status.writeln('Run "flutter emulators" to list and start any available device emulators.'); - status.writeln(); - status.write('If you expected your device to be detected, please run "flutter doctor" to diagnose potential issues. '); - if (deviceDiscoveryTimeout == null) { - status.write('You may also try increasing the time to wait for connected devices with the --${FlutterOptions.kDeviceTimeout} flag. '); - } - status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.'); - - globals.printStatus(status.toString()); - } else { - globals.printStatus('${devices.length} connected ${pluralize('device', devices.length)}:\n'); - await Device.printDevices(devices, globals.logger); - } - await _printDiagnostics(); + await printDevicesAsJson(allDevices); + return; } + + if (allDevices.isEmpty) { + _printNoDevicesDetected(); + } else { + if (attachedDevices.isNotEmpty) { + globals.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n'); + await Device.printDevices(attachedDevices, globals.logger); + } + if (wirelessDevices.isNotEmpty) { + if (attachedDevices.isNotEmpty) { + globals.printStatus(''); + } + globals.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n'); + await Device.printDevices(wirelessDevices, globals.logger); + } + } + await _printDiagnostics(); + } + + void _printNoDevicesDetected() { + final StringBuffer status = StringBuffer('No devices detected.'); + status.writeln(); + status.writeln(); + status.writeln('Run "flutter emulators" to list and start any available device emulators.'); + status.writeln(); + status.write('If you expected your device to be detected, please run "flutter doctor" to diagnose potential issues. '); + if (deviceDiscoveryTimeout == null) { + status.write('You may also try increasing the time to wait for connected devices with the --${FlutterOptions.kDeviceTimeout} flag. '); + } + status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.'); + + globals.printStatus(status.toString()); } Future _printDiagnostics() async { diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart index bc98d8b457..c67eddbdaa 100644 --- a/packages/flutter_tools/lib/src/commands/drive.dart +++ b/packages/flutter_tools/lib/src/commands/drive.dart @@ -23,7 +23,6 @@ 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'; @@ -220,7 +219,7 @@ class DriveCommand extends RunCommandBase { Future get disablePortPublication async { final ArgResults? localArgResults = argResults; final Device? device = await targetedDevice; - final bool isNetworkDevice = device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network; + final bool isNetworkDevice = device is IOSDevice && device.isWirelesslyConnected; if (isNetworkDevice && localArgResults != null && !localArgResults.wasParsed('publish-port')) { _logger.printTrace('Network device is being used. Changing `publish-port` to be enabled.'); return false; diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index e57e57395f..b002f4d25d 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -20,7 +20,6 @@ import '../device.dart'; import '../features.dart'; import '../globals.dart' as globals; import '../ios/devices.dart'; -import '../ios/iproxy.dart'; import '../project.dart'; import '../reporting/reporting.dart'; import '../resident_runner.dart'; @@ -426,7 +425,7 @@ class RunCommand extends RunCommandBase { final TargetPlatform platform = await device.targetPlatform; anyAndroidDevices = platform == TargetPlatform.android; anyIOSDevices = platform == TargetPlatform.ios; - if (device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network) { + if (device is IOSDevice && device.isWirelesslyConnected) { anyIOSNetworkDevices = true; } deviceType = getNameForTargetPlatform(platform); @@ -440,7 +439,7 @@ class RunCommand extends RunCommandBase { final TargetPlatform platform = await device.targetPlatform; anyAndroidDevices = anyAndroidDevices || (platform == TargetPlatform.android); anyIOSDevices = anyIOSDevices || (platform == TargetPlatform.ios); - if (device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network) { + if (device is IOSDevice && device.isWirelesslyConnected) { anyIOSNetworkDevices = true; } if (anyAndroidDevices && anyIOSDevices) { diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 52c75af8ff..3984ed4550 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -17,7 +17,6 @@ import 'base/utils.dart'; import 'build_info.dart'; import 'devfs.dart'; import 'device_port_forwarder.dart'; -import 'ios/iproxy.dart'; import 'project.dart'; import 'vmservice.dart'; @@ -1098,7 +1097,7 @@ class DebuggingOptions { String? route, Map platformArgs, { bool ipv6 = false, - IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.none + DeviceConnectionInterface interfaceType = DeviceConnectionInterface.attached, }) { final String dartVmFlags = computeDartVmFlags(this); return [ @@ -1137,7 +1136,7 @@ class DebuggingOptions { if (environmentType == EnvironmentType.simulator && hostVmServicePort != null) '--vm-service-port=$hostVmServicePort', // Tell the VM service to listen on all interfaces, don't restrict to the loopback. - if (interfaceType == IOSDeviceConnectionInterface.network) + if (interfaceType == DeviceConnectionInterface.wireless) '--vm-service-host=${ipv6 ? '::0' : '0.0.0.0'}', if (enableEmbedderApi) '--enable-embedder-api', ]; diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 00db24a0c1..d06659ccd8 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -151,7 +151,7 @@ class IOSDevice extends Device { required FileSystem fileSystem, required this.name, required this.cpuArchitecture, - required this.interfaceType, + required this.connectionInterface, String? sdkVersion, required Platform platform, required IOSDeploy iosDeploy, @@ -199,7 +199,8 @@ class IOSDevice extends Device { final DarwinArch cpuArchitecture; - final IOSDeviceConnectionInterface interfaceType; + @override + final DeviceConnectionInterface connectionInterface; final Map _logReaders = {}; @@ -256,7 +257,7 @@ class IOSDevice extends Device { bundlePath: bundle.path, appDeltaDirectory: app.appDeltaDirectory, launchArguments: [], - interfaceType: interfaceType, + interfaceType: connectionInterface, ); } on ProcessException catch (e) { _logger.printError(e.message); @@ -311,7 +312,7 @@ class IOSDevice extends Device { @visibleForTesting Duration? discoveryTimeout, }) async { String? packageId; - if (interfaceType == IOSDeviceConnectionInterface.network && + if (isWirelesslyConnected && debuggingOptions.debuggingEnabled && debuggingOptions.disablePortPublication) { throwToolExit('Cannot start app on wirelessly tethered iOS device. Try running again with the --publish-port flag'); @@ -351,7 +352,7 @@ class IOSDevice extends Device { route, platformArgs, ipv6: ipv6, - interfaceType: interfaceType, + interfaceType: connectionInterface, ); Status startAppStatus = _logger.startProgress( 'Installing and launching...', @@ -371,7 +372,7 @@ class IOSDevice extends Device { bundlePath: bundle.path, appDeltaDirectory: package.appDeltaDirectory, launchArguments: launchArguments, - interfaceType: interfaceType, + interfaceType: connectionInterface, uninstallFirst: debuggingOptions.uninstallFirst, ); if (deviceLogReader is IOSDeviceLogReader) { @@ -381,7 +382,7 @@ class IOSDevice extends Device { // Don't port foward if debugging with a network device. vmServiceDiscovery = ProtocolDiscovery.vmService( deviceLogReader, - portForwarder: interfaceType == IOSDeviceConnectionInterface.network ? null : portForwarder, + portForwarder: isWirelesslyConnected ? null : portForwarder, hostPort: debuggingOptions.hostVmServicePort, devicePort: debuggingOptions.deviceVmServicePort, ipv6: ipv6, @@ -394,7 +395,7 @@ class IOSDevice extends Device { bundlePath: bundle.path, appDeltaDirectory: package.appDeltaDirectory, launchArguments: launchArguments, - interfaceType: interfaceType, + interfaceType: connectionInterface, uninstallFirst: debuggingOptions.uninstallFirst, ); } else { @@ -414,13 +415,13 @@ class IOSDevice extends Device { _logger.printTrace('Application launched on the device. Waiting for Dart VM Service url.'); - final int defaultTimeout = interfaceType == IOSDeviceConnectionInterface.network ? 45 : 30; + final int defaultTimeout = isWirelesslyConnected ? 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) { + if (isWirelesslyConnected) { _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", ' @@ -433,7 +434,7 @@ class IOSDevice extends Device { }); Uri? localUri; - if (interfaceType == IOSDeviceConnectionInterface.network) { + if (isWirelesslyConnected) { // Wait for Dart VM Service to start up. final Uri? serviceURL = await vmServiceDiscovery?.uri; if (serviceURL == null) { @@ -538,7 +539,7 @@ class IOSDevice extends Device { @override Future takeScreenshot(File outputFile) async { - await _iMobileDevice.takeScreenshot(outputFile, id, interfaceType); + await _iMobileDevice.takeScreenshot(outputFile, id, connectionInterface); } @override diff --git a/packages/flutter_tools/lib/src/ios/ios_deploy.dart b/packages/flutter_tools/lib/src/ios/ios_deploy.dart index 228823d898..c12c0c41a6 100644 --- a/packages/flutter_tools/lib/src/ios/ios_deploy.dart +++ b/packages/flutter_tools/lib/src/ios/ios_deploy.dart @@ -15,8 +15,8 @@ import '../base/platform.dart'; import '../base/process.dart'; import '../cache.dart'; import '../convert.dart'; +import '../device.dart'; import 'code_signing.dart'; -import 'iproxy.dart'; // Error message patterns from ios-deploy output const String noProvisioningProfileErrorOne = 'Error 0xe8008015'; @@ -88,7 +88,7 @@ class IOSDeploy { required String deviceId, required String bundlePath, required ListlaunchArguments, - required IOSDeviceConnectionInterface interfaceType, + required DeviceConnectionInterface interfaceType, Directory? appDeltaDirectory, }) async { appDeltaDirectory?.createSync(recursive: true); @@ -102,7 +102,7 @@ class IOSDeploy { '--app_deltas', appDeltaDirectory.path, ], - if (interfaceType != IOSDeviceConnectionInterface.network) + if (interfaceType != DeviceConnectionInterface.wireless) '--no-wifi', if (launchArguments.isNotEmpty) ...[ '--args', @@ -126,7 +126,7 @@ class IOSDeploy { required String deviceId, required String bundlePath, required List launchArguments, - required IOSDeviceConnectionInterface interfaceType, + required DeviceConnectionInterface interfaceType, Directory? appDeltaDirectory, required bool uninstallFirst, }) { @@ -149,7 +149,7 @@ class IOSDeploy { if (uninstallFirst) '--uninstall', '--debug', - if (interfaceType != IOSDeviceConnectionInterface.network) + if (interfaceType != DeviceConnectionInterface.wireless) '--no-wifi', if (launchArguments.isNotEmpty) ...[ '--args', @@ -171,7 +171,7 @@ class IOSDeploy { required String deviceId, required String bundlePath, required List launchArguments, - required IOSDeviceConnectionInterface interfaceType, + required DeviceConnectionInterface interfaceType, required bool uninstallFirst, Directory? appDeltaDirectory, }) async { @@ -186,7 +186,7 @@ class IOSDeploy { '--app_deltas', appDeltaDirectory.path, ], - if (interfaceType != IOSDeviceConnectionInterface.network) + if (interfaceType != DeviceConnectionInterface.wireless) '--no-wifi', if (uninstallFirst) '--uninstall', diff --git a/packages/flutter_tools/lib/src/ios/iproxy.dart b/packages/flutter_tools/lib/src/ios/iproxy.dart index c74b6d6e98..2628879592 100644 --- a/packages/flutter_tools/lib/src/ios/iproxy.dart +++ b/packages/flutter_tools/lib/src/ios/iproxy.dart @@ -8,12 +8,6 @@ import '../base/io.dart'; import '../base/logger.dart'; import '../base/process.dart'; -enum IOSDeviceConnectionInterface { - none, - usb, - network, -} - /// Wraps iproxy command line tool port forwarding. /// /// See https://github.com/libimobiledevice/libusbmuxd. diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index d6e2636dfd..20cf7a9819 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -16,6 +16,7 @@ import '../base/project_migrator.dart'; import '../base/utils.dart'; import '../build_info.dart'; import '../cache.dart'; +import '../device.dart'; import '../flutter_manifest.dart'; import '../globals.dart' as globals; import '../macos/cocoapod_utils.dart'; @@ -27,7 +28,6 @@ import '../project.dart'; import '../reporting/reporting.dart'; import 'application_package.dart'; import 'code_signing.dart'; -import 'iproxy.dart'; import 'migrations/host_app_info_plist_migration.dart'; import 'migrations/ios_deployment_target_migration.dart'; import 'migrations/project_base_configuration_migration.dart'; @@ -87,7 +87,7 @@ class IMobileDevice { Future takeScreenshot( File outputFile, String deviceID, - IOSDeviceConnectionInterface interfaceType, + DeviceConnectionInterface interfaceType, ) { return _processUtils.run( [ @@ -95,7 +95,7 @@ class IMobileDevice { outputFile.path, '--udid', deviceID, - if (interfaceType == IOSDeviceConnectionInterface.network) + if (interfaceType == DeviceConnectionInterface.wireless) '--network', ], throwOnError: true, diff --git a/packages/flutter_tools/lib/src/macos/xcdevice.dart b/packages/flutter_tools/lib/src/macos/xcdevice.dart index 523e052184..cfe91f1ce0 100644 --- a/packages/flutter_tools/lib/src/macos/xcdevice.dart +++ b/packages/flutter_tools/lib/src/macos/xcdevice.dart @@ -14,6 +14,7 @@ import '../base/process.dart'; import '../build_info.dart'; import '../cache.dart'; import '../convert.dart'; +import '../device.dart'; import '../globals.dart' as globals; import '../ios/devices.dart'; import '../ios/ios_deploy.dart'; @@ -303,8 +304,6 @@ class XCDevice { } } - final IOSDeviceConnectionInterface interface = _interfaceType(device); - String? sdkVersion = _sdkVersion(device); if (sdkVersion != null) { @@ -318,7 +317,7 @@ class XCDevice { identifier, name: name, cpuArchitecture: _cpuArchitecture(device), - interfaceType: interface, + connectionInterface: _interfaceType(device), sdkVersion: sdkVersion, iProxy: _iProxy, fileSystem: globals.fs, @@ -356,19 +355,16 @@ class XCDevice { return code is int ? code : null; } - static IOSDeviceConnectionInterface _interfaceType(Map deviceProperties) { - // Interface can be "usb", "network", or "none" for simulators - // and unknown future interfaces. + static DeviceConnectionInterface _interfaceType(Map deviceProperties) { + // Interface can be "usb" or "network". It can also be missing + // (e.g. simulators do not have an interface property). + // If the interface is "network", use `DeviceConnectionInterface.wireless`, + // otherwise use `DeviceConnectionInterface.attached. final Object? interface = deviceProperties['interface']; - if (interface is String) { - if (interface.toLowerCase() == 'network') { - return IOSDeviceConnectionInterface.network; - } else { - return IOSDeviceConnectionInterface.usb; - } + if (interface is String && interface.toLowerCase() == 'network') { + return DeviceConnectionInterface.wireless; } - - return IOSDeviceConnectionInterface.none; + return DeviceConnectionInterface.attached; } static String? _sdkVersion(Map deviceProperties) { diff --git a/packages/flutter_tools/lib/src/runner/target_devices.dart b/packages/flutter_tools/lib/src/runner/target_devices.dart index 6f1ae09f0c..a5cdca6e91 100644 --- a/packages/flutter_tools/lib/src/runner/target_devices.dart +++ b/packages/flutter_tools/lib/src/runner/target_devices.dart @@ -2,14 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:meta/meta.dart'; - import '../base/common.dart'; import '../base/logger.dart'; import '../base/user_messages.dart'; import '../device.dart'; import '../globals.dart' as globals; +const String _wirelesslyConnectedDevicesMessage = 'Wirelessly connected devices:'; + +/// This class handles functionality of finding and selecting target devices. +/// +/// Target devices are devices that are supported and selectable to run +/// a flutter application on. class TargetDevices { TargetDevices({ required DeviceManager deviceManager, @@ -20,10 +24,64 @@ class TargetDevices { final DeviceManager _deviceManager; final Logger _logger; - /// Find and return all target [Device]s based upon currently connected - /// devices and criteria entered by the user on the command line. - /// If no device can be found that meets specified criteria, - /// then print an error message and return null. + Future> _getAttachedDevices({ + DeviceDiscoverySupportFilter? supportFilter, + }) async { + return _deviceManager.getDevices( + filter: DeviceDiscoveryFilter( + deviceConnectionInterface: DeviceConnectionInterface.attached, + supportFilter: supportFilter, + ), + ); + } + + Future> _getWirelessDevices({ + DeviceDiscoverySupportFilter? supportFilter, + }) async { + return _deviceManager.getDevices( + filter: DeviceDiscoveryFilter( + deviceConnectionInterface: DeviceConnectionInterface.wireless, + supportFilter: supportFilter, + ), + ); + } + + Future> _getDeviceById({ + bool includeDevicesUnsupportedByProject = false, + }) async { + return _deviceManager.getDevices( + filter: DeviceDiscoveryFilter( + supportFilter: _deviceManager.deviceSupportFilter( + includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject, + ), + ), + ); + } + + DeviceDiscoverySupportFilter _defaultSupportFilter( + bool includeDevicesUnsupportedByProject, + ) { + return _deviceManager.deviceSupportFilter( + includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject, + ); + } + + /// Find and return all target [Device]s based upon criteria entered by the + /// user on the command line. + /// + /// When the user has specified `all` devices, return all devices meeting criteria. + /// + /// When the user has specified a device id/name, attempt to find an exact or + /// partial match. If an exact match or a single partial match is found, + /// return it immediately. + /// + /// When multiple devices are found and there is a terminal attached to + /// stdin, allow the user to select which device to use. When a terminal + /// with stdin is not available, print a list of available devices and + /// return null. + /// + /// When no devices meet user specifications, print a list of unsupported + /// devices and return null. Future?> findAllTargetDevices({ Duration? deviceDiscoveryTimeout, bool includeDevicesUnsupportedByProject = false, @@ -32,67 +90,175 @@ class TargetDevices { _logger.printError(userMessages.flutterNoDevelopmentDevice); return null; } - List devices = await getDevices( - includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject, - timeout: deviceDiscoveryTimeout, - ); - if (devices.isEmpty) { - if (_deviceManager.hasSpecifiedDeviceId) { - _logger.printStatus(userMessages.flutterNoMatchingDevice(_deviceManager.specifiedDeviceId!)); - final List allDevices = await _deviceManager.getAllDevices(); - if (allDevices.isNotEmpty) { - _logger.printStatus(''); - _logger.printStatus('The following devices were found:'); - await Device.printDevices(allDevices, _logger); - } - return null; - } else if (_deviceManager.hasSpecifiedAllDevices) { - _logger.printStatus(userMessages.flutterNoDevicesFound); - await _printUnsupportedDevice(_deviceManager); - return null; - } else { - _logger.printStatus(userMessages.flutterNoSupportedDevices); - await _printUnsupportedDevice(_deviceManager); - return null; - } - } else if (devices.length > 1) { - if (_deviceManager.hasSpecifiedDeviceId) { - _logger.printStatus(userMessages.flutterFoundSpecifiedDevices(devices.length, _deviceManager.specifiedDeviceId!)); - return null; - } else if (!_deviceManager.hasSpecifiedAllDevices) { - if (globals.terminal.stdinHasTerminal) { - // If DeviceManager was not able to prioritize a device. For example, if the user - // has two active Android devices running, then we request the user to - // choose one. If the user has two nonEphemeral devices running, we also - // request input to choose one. - _logger.printStatus(userMessages.flutterMultipleDevicesFound); - await Device.printDevices(devices, _logger); - final Device chosenDevice = await _chooseOneOfAvailableDevices(devices); + if (deviceDiscoveryTimeout != null) { + // Reset the cache with the specified timeout. + await _deviceManager.refreshAllDevices(timeout: deviceDiscoveryTimeout); + } - // Update the [DeviceManager.specifiedDeviceId] so that we will not be prompted again. - _deviceManager.specifiedDeviceId = chosenDevice.id; - - devices = [chosenDevice]; - } else { - // Show an error message asking the user to specify `-d all` if they - // want to run on multiple devices. - final List allDevices = await _deviceManager.getAllDevices(); - _logger.printStatus(userMessages.flutterSpecifyDeviceWithAllOption); - _logger.printStatus(''); - await Device.printDevices(allDevices, _logger); - return null; - } + if (_deviceManager.hasSpecifiedDeviceId) { + // Must check for device match separately from `_getAttachedDevices` and + // `_getWirelessDevices` because if an exact match is found in one + // and a partial match is found in another, there is no way to distinguish + // between them. + final List devices = await _getDeviceById( + includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject, + ); + if (devices.length == 1) { + return devices; } } - return devices; + final List attachedDevices = await _getAttachedDevices( + supportFilter: _defaultSupportFilter(includeDevicesUnsupportedByProject), + ); + final List wirelessDevices = await _getWirelessDevices( + supportFilter: _defaultSupportFilter(includeDevicesUnsupportedByProject), + ); + final List allDevices = attachedDevices + wirelessDevices; + + if (allDevices.isEmpty) { + return _handleNoDevices(); + } else if (_deviceManager.hasSpecifiedAllDevices) { + return allDevices; + } else if (allDevices.length > 1) { + return _handleMultipleDevices(attachedDevices, wirelessDevices); + } + return allDevices; } - Future _printUnsupportedDevice(DeviceManager deviceManager) async { - final List unsupportedDevices = await deviceManager.getDevices(); + /// When no supported devices are found, display a message and list of + /// unsupported devices found. + Future?> _handleNoDevices() async { + // Get connected devices from cache, including unsupported ones. + final List unsupportedDevices = await _deviceManager.getAllDevices(); + + if (_deviceManager.hasSpecifiedDeviceId) { + _logger.printStatus( + userMessages.flutterNoMatchingDevice(_deviceManager.specifiedDeviceId!), + ); + if (unsupportedDevices.isNotEmpty) { + _logger.printStatus(''); + _logger.printStatus('The following devices were found:'); + await Device.printDevices(unsupportedDevices, _logger); + } + return null; + } + + _logger.printStatus(_deviceManager.hasSpecifiedAllDevices + ? userMessages.flutterNoDevicesFound + : userMessages.flutterNoSupportedDevices); + await _printUnsupportedDevice(unsupportedDevices); + return null; + } + + /// Determine which device to use when multiple found. + /// + /// If user has not specified a device id/name, attempt to prioritize + /// ephemeral devices. If a single ephemeral device is found, return it + /// immediately. + /// + /// Otherwise, prompt the user to select a device if there is a terminal + /// with stdin. If there is not a terminal, display the list of devices with + /// instructions to use a device selection flag. + Future?> _handleMultipleDevices( + List attachedDevices, + List wirelessDevices, + ) async { + final List allDevices = attachedDevices + wirelessDevices; + + final Device? ephemeralDevice = _deviceManager.getSingleEphemeralDevice(allDevices); + if (ephemeralDevice != null) { + return [ephemeralDevice]; + } + + if (globals.terminal.stdinHasTerminal) { + return _selectFromMultipleDevices(attachedDevices, wirelessDevices); + } else { + return _printMultipleDevices(attachedDevices, wirelessDevices); + } + } + + /// Display a list of found devices. When the user has not specified the + /// device id/name, display devices unsupported by the project as well and + /// give instructions to use a device selection flag. + Future?> _printMultipleDevices( + List attachedDevices, + List wirelessDevices, + ) async { + List supportedAttachedDevices = attachedDevices; + List supportedWirelessDevices = wirelessDevices; + if (_deviceManager.hasSpecifiedDeviceId) { + final int allDeviceLength = supportedAttachedDevices.length + supportedWirelessDevices.length; + _logger.printStatus(userMessages.flutterFoundSpecifiedDevices( + allDeviceLength, + _deviceManager.specifiedDeviceId!, + )); + } else { + // Get connected devices from cache, including ones unsupported for the + // project but still supported by Flutter. + supportedAttachedDevices = await _getAttachedDevices( + supportFilter: DeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutter(), + ); + supportedWirelessDevices = await _getWirelessDevices( + supportFilter: DeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutter(), + ); + + _logger.printStatus(userMessages.flutterSpecifyDeviceWithAllOption); + _logger.printStatus(''); + } + + await Device.printDevices(supportedAttachedDevices, _logger); + + if (supportedWirelessDevices.isNotEmpty) { + if (_deviceManager.hasSpecifiedDeviceId || supportedAttachedDevices.isNotEmpty) { + _logger.printStatus(''); + } + _logger.printStatus(_wirelesslyConnectedDevicesMessage); + await Device.printDevices(supportedWirelessDevices, _logger); + } + + return null; + } + + /// Display a list of selectable devices, prompt the user to choose one, and + /// wait for the user to select a valid option. + Future?> _selectFromMultipleDevices( + List attachedDevices, + List wirelessDevices, + ) async { + final List allDevices = attachedDevices + wirelessDevices; + + if (_deviceManager.hasSpecifiedDeviceId) { + _logger.printStatus(userMessages.flutterFoundSpecifiedDevices( + allDevices.length, + _deviceManager.specifiedDeviceId!, + )); + } else { + _logger.printStatus(userMessages.flutterMultipleDevicesFound); + } + + await Device.printDevices(attachedDevices, _logger); + + if (wirelessDevices.isNotEmpty) { + _logger.printStatus(''); + _logger.printStatus(_wirelesslyConnectedDevicesMessage); + await Device.printDevices(wirelessDevices, _logger); + _logger.printStatus(''); + } + + final Device chosenDevice = await _chooseOneOfAvailableDevices(allDevices); + + // Update the [DeviceManager.specifiedDeviceId] so that the user will not be prompted again. + _deviceManager.specifiedDeviceId = chosenDevice.id; + + return [chosenDevice]; + } + + Future _printUnsupportedDevice(List unsupportedDevices) async { if (unsupportedDevices.isNotEmpty) { final StringBuffer result = StringBuffer(); + result.writeln(); result.writeln(userMessages.flutterFoundButUnsupportedDevices); result.writeAll( (await Device.descriptions(unsupportedDevices)) @@ -104,7 +270,7 @@ class TargetDevices { result.writeln(userMessages.flutterMissPlatformProjects( Device.devicesPlatformTypes(unsupportedDevices), )); - _logger.printStatus(result.toString()); + _logger.printStatus(result.toString(), newline: false); } } @@ -135,48 +301,4 @@ class TargetDevices { ); return result; } - - /// Find and return all target [Device]s based upon currently connected - /// devices, the current project, and criteria entered by the user on - /// the command line. - /// - /// Returns a list of devices specified by the user. - /// - /// * If the user specified '-d all', then return all connected devices which - /// support the current project, except for fuchsia and web. - /// - /// * If the user specified a device id, then do nothing as the list is already - /// filtered by [_deviceManager.getDevices]. - /// - /// * If the user did not specify a device id and there is more than one - /// device connected, then filter out unsupported devices and prioritize - /// ephemeral devices. - @visibleForTesting - Future> getDevices({ - bool includeDevicesUnsupportedByProject = false, - Duration? timeout, - }) async { - if (timeout != null) { - // Reset the cache with the specified timeout. - await _deviceManager.refreshAllDevices(timeout: timeout); - } - - final List devices = await _deviceManager.getDevices( - filter: DeviceDiscoveryFilter( - supportFilter: _deviceManager.deviceSupportFilter( - includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject, - ), - ), - ); - - // If there is more than one device, attempt to prioritize ephemeral devices. - if (devices.length > 1) { - final Device? ephemeralDevice = _deviceManager.getSingleEphemeralDevice(devices); - if (ephemeralDevice != null) { - return [ephemeralDevice]; - } - } - - return devices; - } } 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 1c43780dba..d1ec789ab7 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -23,7 +23,6 @@ import 'package:flutter_tools/src/device.dart'; 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'; @@ -241,7 +240,7 @@ void main() { logReader: fakeLogReader, portForwarder: portForwarder, majorSdkVersion: 16, - interfaceType: IOSDeviceConnectionInterface.network, + connectionInterface: DeviceConnectionInterface.wireless, ); testDeviceManager.devices = [device]; final FakeHotRunner hotRunner = FakeHotRunner(); @@ -313,7 +312,7 @@ void main() { logReader: fakeLogReader, portForwarder: portForwarder, majorSdkVersion: 16, - interfaceType: IOSDeviceConnectionInterface.network, + connectionInterface: DeviceConnectionInterface.wireless, ); testDeviceManager.devices = [device]; final FakeHotRunner hotRunner = FakeHotRunner(); @@ -389,7 +388,7 @@ void main() { logReader: fakeLogReader, portForwarder: portForwarder, majorSdkVersion: 16, - interfaceType: IOSDeviceConnectionInterface.network, + connectionInterface: DeviceConnectionInterface.wireless, ); testDeviceManager.devices = [device]; final FakeHotRunner hotRunner = FakeHotRunner(); @@ -1237,6 +1236,10 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { @override Future get targetPlatform async => TargetPlatform.android_arm; + @override + DeviceConnectionInterface get connectionInterface => + DeviceConnectionInterface.attached; + @override bool isSupported() => true; @@ -1291,7 +1294,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { DevicePortForwarder? portForwarder, DeviceLogReader? logReader, this.onGetLogReader, - this.interfaceType = IOSDeviceConnectionInterface.none, + this.connectionInterface = DeviceConnectionInterface.attached, this.majorSdkVersion = 0, }) : _portForwarder = portForwarder, _logReader = logReader; @@ -1300,7 +1303,11 @@ class FakeIOSDevice extends Fake implements IOSDevice { int majorSdkVersion; @override - final IOSDeviceConnectionInterface interfaceType; + final DeviceConnectionInterface connectionInterface; + + @override + bool get isWirelesslyConnected => + connectionInterface == DeviceConnectionInterface.wireless; @override DevicePortForwarder get portForwarder => _portForwarder!; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart index 85f40c9dd0..04f1c068dc 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart @@ -6,12 +6,12 @@ import 'dart:convert'; import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/devices.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/globals.dart' as globals; -import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fake_devices.dart'; import '../../src/test_flutter_command_runner.dart'; @@ -23,9 +23,11 @@ void main() { }); late Cache cache; + late Platform platform; setUp(() { cache = Cache.test(processManager: FakeProcessManager.any()); + platform = FakePlatform(); }); testUsingContext('returns 0 when called', () async { @@ -39,7 +41,16 @@ void main() { testUsingContext('no error when no connected devices', () async { final DevicesCommand command = DevicesCommand(); await createTestCommandRunner(command).run(['devices']); - expect(testLogger.statusText, containsIgnoringWhitespace('No devices detected')); + expect( + testLogger.statusText, + equals(''' +No devices detected. + +Run "flutter emulators" to list and start any available device emulators. + +If you expected your device to be detected, please run "flutter doctor" to diagnose potential issues. You may also try increasing the time to wait for connected devices with the --device-timeout flag. Visit https://flutter.dev/setup/ for troubleshooting tips. +'''), + ); }, overrides: { AndroidSdk: () => null, DeviceManager: () => NoDevicesManager(), @@ -48,94 +59,143 @@ void main() { Artifacts: () => Artifacts.test(), }); - testUsingContext("get devices' platform types", () async { - final List platformTypes = Device.devicesPlatformTypes( - await globals.deviceManager!.getAllDevices(), - ); - expect(platformTypes, ['android', 'web']); - }, overrides: { - DeviceManager: () => _FakeDeviceManager(), - ProcessManager: () => FakeProcessManager.any(), - Cache: () => cache, - Artifacts: () => Artifacts.test(), + group('when includes both attached and wireless devices', () { + List? deviceList; + setUp(() { + deviceList = [ + fakeDevices[0], + fakeDevices[1], + fakeDevices[2], + ]; + }); + + testUsingContext("get devices' platform types", () async { + final List platformTypes = Device.devicesPlatformTypes( + await globals.deviceManager!.getAllDevices(), + ); + expect(platformTypes, ['android', 'web']); + }, overrides: { + DeviceManager: () => _FakeDeviceManager(devices: deviceList), + ProcessManager: () => FakeProcessManager.any(), + Cache: () => cache, + Artifacts: () => Artifacts.test(), + Platform: () => platform, + }); + + testUsingContext('Outputs parsable JSON with --machine flag', () async { + final DevicesCommand command = DevicesCommand(); + await createTestCommandRunner(command).run(['devices', '--machine']); + expect( + json.decode(testLogger.statusText), + >[ + fakeDevices[0].json, + fakeDevices[1].json, + fakeDevices[2].json, + ], + ); + }, overrides: { + DeviceManager: () => _FakeDeviceManager(devices: deviceList), + ProcessManager: () => FakeProcessManager.any(), + Cache: () => cache, + Artifacts: () => Artifacts.test(), + Platform: () => platform, + }); + + testUsingContext('available devices and diagnostics', () async { + final DevicesCommand command = DevicesCommand(); + await createTestCommandRunner(command).run(['devices']); + expect(testLogger.statusText, ''' +2 connected devices: + +ephemeral (mobile) • ephemeral • android-arm • Test SDK (1.2.3) (emulator) +webby (mobile) • webby • web-javascript • Web SDK (1.2.4) (emulator) + +1 wirelessly connected device: + +wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2.3) (emulator) + +• Cannot connect to device ABC +'''); + }, overrides: { + DeviceManager: () => _FakeDeviceManager(devices: deviceList), + ProcessManager: () => FakeProcessManager.any(), + Platform: () => platform, + }); }); - testUsingContext('Outputs parsable JSON with --machine flag', () async { - final DevicesCommand command = DevicesCommand(); - await createTestCommandRunner(command).run(['devices', '--machine']); - expect( - json.decode(testLogger.statusText), - >[ - { - 'name': 'ephemeral', - 'id': 'ephemeral', - 'isSupported': true, - 'targetPlatform': 'android-arm', - 'emulator': true, - 'sdk': 'Test SDK (1.2.3)', - 'capabilities': { - 'hotReload': true, - 'hotRestart': true, - 'screenshot': false, - 'fastStart': false, - 'flutterExit': true, - 'hardwareRendering': true, - 'startPaused': true, - }, - }, - { - 'name': 'webby', - 'id': 'webby', - 'isSupported': true, - 'targetPlatform': 'web-javascript', - 'emulator': true, - 'sdk': 'Web SDK (1.2.4)', - 'capabilities': { - 'hotReload': true, - 'hotRestart': true, - 'screenshot': false, - 'fastStart': false, - 'flutterExit': true, - 'hardwareRendering': true, - 'startPaused': true, - }, - }, - ], - ); - }, overrides: { - DeviceManager: () => _FakeDeviceManager(), - ProcessManager: () => FakeProcessManager.any(), - Cache: () => cache, - Artifacts: () => Artifacts.test(), - }); + group('when includes only attached devices', () { + List? deviceList; + setUp(() { + deviceList = [ + fakeDevices[0], + fakeDevices[1], + ]; + }); - testUsingContext('available devices and diagnostics', () async { - final DevicesCommand command = DevicesCommand(); - await createTestCommandRunner(command).run(['devices']); - expect( - testLogger.statusText, - ''' + testUsingContext('available devices and diagnostics', () async { + final DevicesCommand command = DevicesCommand(); + await createTestCommandRunner(command).run(['devices']); + expect(testLogger.statusText, ''' 2 connected devices: ephemeral (mobile) • ephemeral • android-arm • Test SDK (1.2.3) (emulator) webby (mobile) • webby • web-javascript • Web SDK (1.2.4) (emulator) • Cannot connect to device ABC -''' - ); - }, overrides: { - DeviceManager: () => _FakeDeviceManager(), - ProcessManager: () => FakeProcessManager.any(), +'''); + }, overrides: { + DeviceManager: () => _FakeDeviceManager(devices: deviceList), + ProcessManager: () => FakeProcessManager.any(), + Platform: () => platform, + }); + }); + + group('when includes only wireless devices', () { + List? deviceList; + setUp(() { + deviceList = [ + fakeDevices[2], + ]; + }); + + testUsingContext('available devices and diagnostics', () async { + final DevicesCommand command = DevicesCommand(); + await createTestCommandRunner(command).run(['devices']); + expect(testLogger.statusText, ''' +1 wirelessly connected device: + +wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2.3) (emulator) + +• Cannot connect to device ABC +'''); + }, overrides: { + DeviceManager: () => _FakeDeviceManager(devices: deviceList), + ProcessManager: () => FakeProcessManager.any(), + Platform: () => platform, + }); }); }); } class _FakeDeviceManager extends DeviceManager { - _FakeDeviceManager() : super(logger: testLogger); + _FakeDeviceManager({ + List? devices, + }) : fakeDevices = devices ?? [], + super(logger: testLogger); + + List fakeDevices = []; @override - Future> getAllDevices({DeviceDiscoveryFilter? filter}) => - Future>.value(fakeDevices.map((FakeDeviceJsonData d) => d.dev).toList()); + Future> getAllDevices({DeviceDiscoveryFilter? filter}) async { + final List devices = []; + for (final FakeDeviceJsonData deviceJson in fakeDevices) { + if (filter?.deviceConnectionInterface == null || + deviceJson.dev.connectionInterface == filter?.deviceConnectionInterface) { + devices.add(deviceJson.dev); + } + } + return devices; + } @override Future> refreshAllDevices({ 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 f3cebbe008..ae655dd25e 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart @@ -22,7 +22,6 @@ 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'; @@ -68,7 +67,7 @@ void main() { final Device screenshotDevice = ThrowingScreenshotDevice() ..supportsScreenshot = false; - fakeDeviceManager.devices = [screenshotDevice]; + fakeDeviceManager.attachedDevices = [screenshotDevice]; await expectLater(() => createTestCommandRunner(command).run( [ @@ -104,7 +103,7 @@ void main() { fileSystem.directory('drive_screenshots').createSync(); final Device screenshotDevice = ThrowingScreenshotDevice(); - fakeDeviceManager.devices = [screenshotDevice]; + fakeDeviceManager.attachedDevices = [screenshotDevice]; await expectLater(() => createTestCommandRunner(command).run( [ @@ -142,7 +141,7 @@ void main() { fileSystem.directory('drive_screenshots').createSync(); final Device screenshotDevice = ScreenshotDevice(); - fakeDeviceManager.devices = [screenshotDevice]; + fakeDeviceManager.attachedDevices = [screenshotDevice]; await expectLater(() => createTestCommandRunner(command).run( [ @@ -184,7 +183,7 @@ void main() { fileSystem.file('drive_screenshots').createSync(); final Device screenshotDevice = ThrowingScreenshotDevice(); - fakeDeviceManager.devices = [screenshotDevice]; + fakeDeviceManager.attachedDevices = [screenshotDevice]; await expectLater(() => createTestCommandRunner(command).run( [ @@ -222,7 +221,7 @@ void main() { fileSystem.directory('drive_screenshots').createSync(); final ScreenshotDevice screenshotDevice = ScreenshotDevice(); - fakeDeviceManager.devices = [screenshotDevice]; + fakeDeviceManager.attachedDevices = [screenshotDevice]; expect(screenshotDevice.screenshots, isEmpty); bool caughtToolExit = false; @@ -293,7 +292,7 @@ void main() { fileSystem.directory('drive_screenshots').createSync(); final ScreenshotDevice screenshotDevice = ScreenshotDevice(); - fakeDeviceManager.devices = [screenshotDevice]; + fakeDeviceManager.attachedDevices = [screenshotDevice]; expect(screenshotDevice.screenshots, isEmpty); @@ -423,8 +422,8 @@ void main() { fileSystem.file('pubspec.yaml').createSync(); final Device networkDevice = FakeIosDevice() - ..interfaceType = IOSDeviceConnectionInterface.network; - fakeDeviceManager.devices = [networkDevice]; + ..connectionInterface = DeviceConnectionInterface.wireless; + fakeDeviceManager.wirelessDevices = [networkDevice]; await expectLater(() => createTestCommandRunner(command).run([ 'drive', @@ -456,8 +455,8 @@ void main() { ]), throwsToolExit()); final Device usbDevice = FakeIosDevice() - ..interfaceType = IOSDeviceConnectionInterface.usb; - fakeDeviceManager.devices = [usbDevice]; + ..connectionInterface = DeviceConnectionInterface.attached; + fakeDeviceManager.attachedDevices = [usbDevice]; final DebuggingOptions options = await command.createDebuggingOptions(false); expect(options.disablePortPublication, true); @@ -481,8 +480,8 @@ void main() { fileSystem.file('pubspec.yaml').createSync(); final Device networkDevice = FakeIosDevice() - ..interfaceType = IOSDeviceConnectionInterface.network; - fakeDeviceManager.devices = [networkDevice]; + ..connectionInterface = DeviceConnectionInterface.wireless; + fakeDeviceManager.wirelessDevices = [networkDevice]; await expectLater(() => createTestCommandRunner(command).run([ 'drive', @@ -661,7 +660,11 @@ class FakeProcessSignal extends Fake implements io.ProcessSignal { // ignore: avoid_implementing_value_types class FakeIosDevice extends Fake implements IOSDevice { @override - IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.usb; + DeviceConnectionInterface connectionInterface = DeviceConnectionInterface.attached; + + @override + bool get isWirelesslyConnected => + connectionInterface == DeviceConnectionInterface.wireless; @override Future get targetPlatform async => TargetPlatform.ios; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart index 808cf3f85f..b2c549d9a4 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart @@ -36,7 +36,7 @@ void main() { command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk()); final FakeAndroidDevice device = FakeAndroidDevice(); - testDeviceManager.addDevice(device); + testDeviceManager.addAttachedDevice(device); await createTestCommandRunner(command).run(['install']); }, overrides: { @@ -50,7 +50,7 @@ void main() { command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk()); final FakeIOSDevice device = FakeIOSDevice(); - testDeviceManager.addDevice(device); + testDeviceManager.addAttachedDevice(device); expect(() async => createTestCommandRunner(command).run(['install', '--device-user', '10']), throwsToolExit(message: '--device-user is only supported for Android')); @@ -65,7 +65,7 @@ void main() { command.applicationPackages = FakeApplicationPackageFactory(FakeIOSApp()); final FakeIOSDevice device = FakeIOSDevice(); - testDeviceManager.addDevice(device); + testDeviceManager.addAttachedDevice(device); await createTestCommandRunner(command).run(['install']); }, overrides: { @@ -79,7 +79,7 @@ void main() { command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk()); final FakeAndroidDevice device = FakeAndroidDevice(); - testDeviceManager.addDevice(device); + testDeviceManager.addAttachedDevice(device); expect(() async => createTestCommandRunner(command).run(['install', '--use-application-binary', 'bogus']), throwsToolExit(message: 'Prebuilt binary bogus does not exist')); @@ -94,7 +94,7 @@ void main() { command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk()); final FakeAndroidDevice device = FakeAndroidDevice(); - testDeviceManager.addDevice(device); + testDeviceManager.addAttachedDevice(device); fileSystem.file('binary').createSync(recursive: true); await createTestCommandRunner(command).run(['install', '--use-application-binary', 'binary']); @@ -111,7 +111,7 @@ void main() { command.applicationPackages = fakeAppFactory; final FakeIOSDevice device = FakeIOSDevice(); - testDeviceManager.addDevice(device); + testDeviceManager.addAttachedDevice(device); await createTestCommandRunner(command).run(['install', '--flavor', flavor]); expect(fakeAppFactory.buildInfo, isNotNull); @@ -183,4 +183,7 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { @override String get name => 'Android'; + + @override + bool get ephemeral => true; } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index c177293af0..d70b825a2f 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -26,7 +26,6 @@ import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/globals.dart' as globals; 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:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/resident_runner.dart'; @@ -711,7 +710,7 @@ void main() { testUsingContext('with only iOS usb device', () async { final List devices = [ - FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.usb, sdkNameAndVersion: 'iOS 16.2'), + FakeIOSDevice(sdkNameAndVersion: 'iOS 16.2'), ]; final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices); final CommandRunner runner = createTestCommandRunner(command); @@ -752,7 +751,10 @@ void main() { testUsingContext('with only iOS network device', () async { final List devices = [ - FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.network, sdkNameAndVersion: 'iOS 16.2'), + FakeIOSDevice( + connectionInterface: DeviceConnectionInterface.wireless, + sdkNameAndVersion: 'iOS 16.2', + ), ]; final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices); final CommandRunner runner = createTestCommandRunner(command); @@ -793,8 +795,11 @@ void main() { testUsingContext('with both iOS usb and network devices', () async { final List devices = [ - FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.network, sdkNameAndVersion: 'iOS 16.2'), - FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.usb, sdkNameAndVersion: 'iOS 16.2'), + FakeIOSDevice( + connectionInterface: DeviceConnectionInterface.wireless, + sdkNameAndVersion: 'iOS 16.2', + ), + FakeIOSDevice(sdkNameAndVersion: 'iOS 16.2'), ]; final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices); final CommandRunner runner = createTestCommandRunner(command); @@ -1126,6 +1131,10 @@ class FakeDevice extends Fake implements Device { @override bool get isConnected => true; + @override + DeviceConnectionInterface get connectionInterface => + DeviceConnectionInterface.attached; + bool supported = true; @override @@ -1209,7 +1218,7 @@ class FakeDevice extends Fake implements Device { // ignore: avoid_implementing_value_types class FakeIOSDevice extends Fake implements IOSDevice { FakeIOSDevice({ - this.interfaceType = IOSDeviceConnectionInterface.none, + this.connectionInterface = DeviceConnectionInterface.attached, bool isLocalEmulator = false, String sdkNameAndVersion = '', }): _isLocalEmulator = isLocalEmulator, @@ -1225,7 +1234,11 @@ class FakeIOSDevice extends Fake implements IOSDevice { Future get sdkNameAndVersion => Future.value(_sdkNameAndVersion); @override - final IOSDeviceConnectionInterface interfaceType; + final DeviceConnectionInterface connectionInterface; + + @override + bool get isWirelesslyConnected => + connectionInterface == DeviceConnectionInterface.wireless; @override Future get targetPlatform async => TargetPlatform.ios; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart index 7b65e6232a..ab57a98311 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart @@ -922,8 +922,15 @@ class _FakeDeviceManager extends DeviceManager { @override Future> getAllDevices({ DeviceDiscoveryFilter? filter, - }) async => _connectedDevices; + }) async => filteredDevices(filter); @override List get deviceDiscoverers => []; + + List filteredDevices(DeviceDiscoveryFilter? filter) { + if (filter?.deviceConnectionInterface == DeviceConnectionInterface.wireless) { + return []; + } + return _connectedDevices; + } } diff --git a/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart b/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart index 4eaf0b4ad5..5b87d65798 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart @@ -87,6 +87,13 @@ class FakeDeviceManager extends Fake implements DeviceManager { @override String? specifiedDeviceId; + @override + Future> getAllDevices({ + DeviceDiscoveryFilter? filter, + }) async { + return devices; + } + @override Future> refreshAllDevices({ Duration? timeout, diff --git a/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart index a06ef74eb6..2f2135df0e 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart @@ -122,7 +122,7 @@ void main() { expect(androidDevices.supportsPlatform, false); }); - testWithoutContext('AndroidDevices can parse output for physical devices', () async { + testWithoutContext('AndroidDevices can parse output for physical attached devices', () async { final AndroidDevices androidDevices = AndroidDevices( userMessages: UserMessages(), androidWorkflow: androidWorkflow, @@ -147,6 +147,35 @@ List of devices attached expect(devices, hasLength(1)); expect(devices.first.name, 'Nexus 7'); expect(devices.first.category, Category.mobile); + expect(devices.first.connectionInterface, DeviceConnectionInterface.attached); + }); + + testWithoutContext('AndroidDevices can parse output for physical wireless devices', () async { + final AndroidDevices androidDevices = AndroidDevices( + userMessages: UserMessages(), + androidWorkflow: androidWorkflow, + androidSdk: FakeAndroidSdk(), + logger: BufferLogger.test(), + processManager: FakeProcessManager.list([ + const FakeCommand( + command: ['adb', 'devices', '-l'], + stdout: ''' +List of devices attached +05a02bac._adb-tls-connect._tcp. device product:razor model:Nexus_7 device:flo + + ''', + ), + ]), + platform: FakePlatform(), + fileSystem: MemoryFileSystem.test(), + ); + + final List devices = await androidDevices.pollingGetDevices(); + + expect(devices, hasLength(1)); + expect(devices.first.name, 'Nexus 7'); + expect(devices.first.category, Category.mobile); + expect(devices.first.connectionInterface, DeviceConnectionInterface.wireless); }); testWithoutContext('AndroidDevices can parse output for emulators and short listings', () async { diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart index 0d0b08bc2b..423e9873a2 100644 --- a/packages/flutter_tools/test/general.shard/device_test.dart +++ b/packages/flutter_tools/test/general.shard/device_test.dart @@ -11,7 +11,6 @@ import 'package:flutter_tools/src/base/utils.dart'; 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'; @@ -832,7 +831,7 @@ void main() { EnvironmentType.physical, null, {}, - interfaceType: IOSDeviceConnectionInterface.network, + interfaceType: DeviceConnectionInterface.wireless, ); expect( @@ -856,7 +855,7 @@ void main() { null, {}, ipv6: true, - interfaceType: IOSDeviceConnectionInterface.network, + interfaceType: DeviceConnectionInterface.wireless, ); expect( diff --git a/packages/flutter_tools/test/general.shard/ios/devices_test.dart b/packages/flutter_tools/test/general.shard/ios/devices_test.dart index eef99a0da5..f2edda82ba 100644 --- a/packages/flutter_tools/test/general.shard/ios/devices_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/devices_test.dart @@ -74,7 +74,7 @@ void main() { name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ); expect(device.isSupported(), isTrue); }); @@ -90,7 +90,7 @@ void main() { iMobileDevice: iMobileDevice, name: 'iPhone 1', cpuArchitecture: DarwinArch.armv7, - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ); expect(device.isSupported(), isFalse); }); @@ -107,7 +107,7 @@ void main() { name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '1.0.0', - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ).majorSdkVersion, 1); expect(IOSDevice( 'device-123', @@ -120,7 +120,7 @@ void main() { name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '13.1.1', - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ).majorSdkVersion, 13); expect(IOSDevice( 'device-123', @@ -133,7 +133,7 @@ void main() { name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '10', - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ).majorSdkVersion, 10); expect(IOSDevice( 'device-123', @@ -146,7 +146,7 @@ void main() { name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '0', - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ).majorSdkVersion, 0); expect(IOSDevice( 'device-123', @@ -159,7 +159,7 @@ void main() { name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: 'bogus', - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ).majorSdkVersion, 0); }); @@ -175,7 +175,7 @@ void main() { name: 'iPhone 1', sdkVersion: '13.3 17C54', cpuArchitecture: DarwinArch.arm64, - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ); expect(await device.sdkNameAndVersion,'iOS 13.3 17C54'); @@ -193,7 +193,7 @@ void main() { name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ); expect(device.supportsRuntimeMode(BuildMode.debug), true); @@ -217,7 +217,7 @@ void main() { name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ); }, throwsAssertionError, @@ -307,7 +307,7 @@ void main() { name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ); logReader1 = createLogReader(device, appPackage1, process1); logReader2 = createLogReader(device, appPackage2, process2); @@ -368,7 +368,7 @@ void main() { logger: logger, platform: macPlatform, fileSystem: MemoryFileSystem.test(), - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ); device2 = IOSDevice( @@ -382,7 +382,7 @@ void main() { logger: logger, platform: macPlatform, fileSystem: MemoryFileSystem.test(), - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ); }); diff --git a/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart index bd9dd9c761..ce32c7ef36 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart @@ -12,8 +12,8 @@ import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; -import 'package:flutter_tools/src/ios/iproxy.dart'; import '../../src/common.dart'; import '../../src/fake_process_manager.dart'; @@ -73,7 +73,7 @@ void main () { bundlePath: '/', appDeltaDirectory: appDeltaDirectory, launchArguments: ['--enable-dart-profiling'], - interfaceType: IOSDeviceConnectionInterface.network, + interfaceType: DeviceConnectionInterface.wireless, uninstallFirst: true, ); diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart index c5fce96c22..50f0282387 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart @@ -10,6 +10,7 @@ import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/application_package.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; @@ -62,7 +63,7 @@ void main() { final IOSDevice device = setUpIOSDevice( processManager: processManager, fileSystem: fileSystem, - interfaceType: IOSDeviceConnectionInterface.usb, + interfaceType: DeviceConnectionInterface.attached, artifacts: artifacts, ); final bool wasInstalled = await device.installApp(iosApp); @@ -95,7 +96,7 @@ void main() { final IOSDevice device = setUpIOSDevice( processManager: processManager, fileSystem: fileSystem, - interfaceType: IOSDeviceConnectionInterface.network, + interfaceType: DeviceConnectionInterface.wireless, artifacts: artifacts, ); final bool wasInstalled = await device.installApp(iosApp); @@ -319,7 +320,7 @@ IOSDevice setUpIOSDevice({ required ProcessManager processManager, FileSystem? fileSystem, Logger? logger, - IOSDeviceConnectionInterface? interfaceType, + DeviceConnectionInterface? interfaceType, Artifacts? artifacts, }) { logger ??= BufferLogger.test(); @@ -357,6 +358,6 @@ IOSDevice setUpIOSDevice({ cache: cache, ), iProxy: IProxy.test(logger: logger, processManager: processManager), - interfaceType: interfaceType ?? IOSDeviceConnectionInterface.usb, + connectionInterface: interfaceType ?? DeviceConnectionInterface.attached, ); } diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart index 15da88a6f4..66efd65fd7 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart @@ -9,6 +9,7 @@ import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; @@ -98,6 +99,6 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) { sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, iProxy: IProxy.test(logger: logger, processManager: processManager), - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ); } diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart index 853707c97c..f6f72858fc 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart @@ -337,7 +337,7 @@ IOSDevice setUpIOSDevice({ cache: cache, ), cpuArchitecture: DarwinArch.arm64, - interfaceType: IOSDeviceConnectionInterface.usb, + connectionInterface: DeviceConnectionInterface.attached, ); } 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 7f069bcccb..7926d62b6e 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 @@ -250,7 +250,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, logger: logger, - interfaceType: IOSDeviceConnectionInterface.network, + interfaceType: DeviceConnectionInterface.wireless, ); final IOSApp iosApp = PrebuiltIOSApp( projectBundleId: 'app', @@ -560,7 +560,7 @@ IOSDevice setUpIOSDevice({ Logger? logger, ProcessManager? processManager, IOSDeploy? iosDeploy, - IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.usb, + DeviceConnectionInterface interfaceType = DeviceConnectionInterface.attached, }) { final Artifacts artifacts = Artifacts.test(); final FakePlatform macPlatform = FakePlatform( @@ -598,7 +598,7 @@ IOSDevice setUpIOSDevice({ cache: cache, ), cpuArchitecture: DarwinArch.arm64, - interfaceType: interfaceType, + connectionInterface: interfaceType, ); } diff --git a/packages/flutter_tools/test/general.shard/ios/mac_test.dart b/packages/flutter_tools/test/general.shard/ios/mac_test.dart index acf2138437..e7fef592be 100644 --- a/packages/flutter_tools/test/general.shard/ios/mac_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/mac_test.dart @@ -10,8 +10,8 @@ import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/code_signing.dart'; -import 'package:flutter_tools/src/ios/iproxy.dart'; import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/xcresult.dart'; import 'package:flutter_tools/src/project.dart'; @@ -77,7 +77,7 @@ void main() { expect(() async => iMobileDevice.takeScreenshot( outputFile, '1234', - IOSDeviceConnectionInterface.usb, + DeviceConnectionInterface.attached, ), throwsA(anything)); expect(fakeProcessManager, hasNoRemainingExpectations); }); @@ -100,7 +100,7 @@ void main() { await iMobileDevice.takeScreenshot( outputFile, '1234', - IOSDeviceConnectionInterface.usb, + DeviceConnectionInterface.attached, ); expect(fakeProcessManager, hasNoRemainingExpectations); }); @@ -123,7 +123,7 @@ void main() { await iMobileDevice.takeScreenshot( outputFile, '1234', - IOSDeviceConnectionInterface.network, + DeviceConnectionInterface.wireless, ); expect(fakeProcessManager, hasNoRemainingExpectations); }); 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 ee97168d4b..3699d0de48 100644 --- a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart @@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; @@ -480,22 +481,31 @@ void main() { )); final List devices = await xcdevice.getAvailableIOSDevices(); 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'); expect(devices[0].cpuArchitecture, DarwinArch.arm64); + expect(devices[0].connectionInterface, DeviceConnectionInterface.attached); + expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44'); expect(devices[1].name, 'iPad 1'); expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54'); expect(devices[1].cpuArchitecture, DarwinArch.armv7); + expect(devices[1].connectionInterface, DeviceConnectionInterface.attached); + 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[2].connectionInterface, DeviceConnectionInterface.wireless); + 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(devices[3].connectionInterface, DeviceConnectionInterface.attached); + expect(fakeProcessManager, hasNoRemainingExpectations); }, overrides: { Platform: () => macPlatform, diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart index 06f833669f..a9a6594a08 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart @@ -705,15 +705,15 @@ void main() { }); testUsingContext('finds single device', () async { - testDeviceManager.addDevice(device1); + testDeviceManager.addAttachedDevice(device1); final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); final Device? device = await flutterCommand.findTargetDevice(); expect(device, device1); }); testUsingContext('finds multiple devices', () async { - testDeviceManager.addDevice(device1); - testDeviceManager.addDevice(device2); + testDeviceManager.addAttachedDevice(device1); + testDeviceManager.addAttachedDevice(device2); testDeviceManager.specifiedDeviceId = 'all'; final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); final Device? device = await flutterCommand.findTargetDevice(); diff --git a/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart b/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart index 12f90ff0f9..c442884a97 100644 --- a/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart @@ -4,416 +4,1078 @@ import 'dart:async'; -import 'package:flutter_tools/src/android/android_workflow.dart'; import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; -import 'package:flutter_tools/src/base/user_messages.dart'; +import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/doctor.dart'; -import 'package:flutter_tools/src/doctor_validator.dart'; +import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/runner/target_devices.dart'; import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; -import '../../src/fake_devices.dart'; void main() { - group('When cannot launch anything', () { - late BufferLogger logger; - late FakeDoctor doctor; - final FakeDevice device1 = FakeDevice('device1', 'device1'); + group('findAllTargetDevices', () { + late Platform platform; + + final FakeDevice attachedAndroidDevice1 = FakeDevice(deviceName: 'target-device-1'); + final FakeDevice attachedAndroidDevice2 = FakeDevice(deviceName: 'target-device-2'); + final FakeDevice attachedUnsupportedAndroidDevice = FakeDevice(deviceName: 'target-device-3', deviceSupported: false); + final FakeDevice attachedUnsupportedForProjectAndroidDevice = FakeDevice(deviceName: 'target-device-4', deviceSupportForProject: false); + + final FakeDevice wirelessAndroidDevice1 = FakeDevice.wireless(deviceName: 'target-device-5'); + final FakeDevice wirelessAndroidDevice2 = FakeDevice.wireless(deviceName: 'target-device-6'); + final FakeDevice wirelessUnsupportedAndroidDevice = FakeDevice.wireless(deviceName: 'target-device-7', deviceSupported: false); + final FakeDevice wirelessUnsupportedForProjectAndroidDevice = FakeDevice.wireless(deviceName: 'target-device-8', deviceSupportForProject: false); + + final FakeDevice nonEphemeralDevice = FakeDevice(deviceName: 'target-device-9', ephemeral: false); + final FakeDevice fuchsiaDevice = FakeDevice.fuchsia(deviceName: 'target-device-10'); + + final FakeDevice exactMatchAndroidDevice = FakeDevice(deviceName: 'target-device'); + final FakeDevice exactMatchWirelessAndroidDevice = FakeDevice.wireless(deviceName: 'target-device'); + final FakeDevice exactMatchattachedUnsupportedAndroidDevice = FakeDevice(deviceName: 'target-device', deviceSupported: false); + final FakeDevice exactMatchUnsupportedByProjectDevice = FakeDevice(deviceName: 'target-device', deviceSupportForProject: false); setUp(() { - logger = BufferLogger.test(); - doctor = FakeDoctor(logger, canLaunchAnything: false); + platform = FakePlatform(); }); - testUsingContext('does not search for devices', () async { - final MockDeviceDiscovery deviceDiscovery = MockDeviceDiscovery() - ..deviceValues = [device1]; + group('when cannot launch anything', () { + late BufferLogger logger; + late FakeDoctor doctor; - final DeviceManager deviceManager = TestDeviceManager( - [], - deviceDiscoveryOverrides: [ - deviceDiscovery, - ], - logger: logger, - ); + setUp(() { + logger = BufferLogger.test(); + doctor = FakeDoctor(canLaunchAnything: false); + }); - final TargetDevices targetDevices = TargetDevices( - deviceManager: deviceManager, - logger: logger, - ); - final List? devices = await targetDevices.findAllTargetDevices(); + testUsingContext('does not search for devices', () async { + final TestDeviceManager deviceManager = TestDeviceManager( + logger: logger, + platform: platform, + ); + deviceManager.androidDiscoverer.deviceList = [attachedAndroidDevice1]; - expect(logger.errorText, contains(UserMessages().flutterNoDevelopmentDevice)); - expect(devices, isNull); - expect(deviceDiscovery.devicesCalled, 0); - expect(deviceDiscovery.discoverDevicesCalled, 0); - }, overrides: { - Doctor: () => doctor, - }); - }); + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); - group('Ensure refresh when deviceDiscoveryTimeout is provided', () { - late BufferLogger logger; - final FakeDevice device1 = FakeDevice('device1', 'device1'); - - setUp(() { - logger = BufferLogger.test(); + expect(logger.errorText, equals(''' +Unable to locate a development device; please run 'flutter doctor' for information about installing additional components. +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 0); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + }, overrides: { + Doctor: () => doctor, + }); }); - testUsingContext('does not refresh device cache without a timeout', () async { - final MockDeviceDiscovery deviceDiscovery = MockDeviceDiscovery() - ..deviceValues = [device1]; - - final DeviceManager deviceManager = TestDeviceManager( - [], - deviceDiscoveryOverrides: [ - deviceDiscovery, - ], + testUsingContext('ensure refresh when deviceDiscoveryTimeout is provided', () async { + final BufferLogger logger = BufferLogger.test(); + final TestDeviceManager deviceManager = TestDeviceManager( logger: logger, + platform: platform, ); - deviceManager.specifiedDeviceId = device1.id; + deviceManager.androidDiscoverer.deviceList = [attachedAndroidDevice1]; + deviceManager.androidDiscoverer.refreshDeviceList = [attachedAndroidDevice1, wirelessAndroidDevice1]; + deviceManager.hasSpecifiedAllDevices = true; - final TargetDevices targetDevices = TargetDevices( - deviceManager: deviceManager, - logger: logger, - ); - final List? devices = await targetDevices.findAllTargetDevices(); - - expect(devices?.single, device1); - expect(deviceDiscovery.devicesCalled, 1); - expect(deviceDiscovery.discoverDevicesCalled, 0); - }); - - testUsingContext('refreshes device cache with a timeout', () async { - final MockDeviceDiscovery deviceDiscovery = MockDeviceDiscovery() - ..deviceValues = [device1]; - - final DeviceManager deviceManager = TestDeviceManager( - [], - deviceDiscoveryOverrides: [ - deviceDiscovery, - ], - logger: BufferLogger.test(), - ); - deviceManager.specifiedDeviceId = device1.id; - - const Duration timeout = Duration(seconds: 2); final TargetDevices targetDevices = TargetDevices( deviceManager: deviceManager, logger: logger, ); final List? devices = await targetDevices.findAllTargetDevices( - deviceDiscoveryTimeout: timeout, + deviceDiscoveryTimeout: const Duration(seconds: 2), ); - expect(devices?.single, device1); - expect(deviceDiscovery.devicesCalled, 1); - expect(deviceDiscovery.discoverDevicesCalled, 1); - }); - }); - - group('findAllTargetDevices', () { - late BufferLogger logger; - final FakeDevice device1 = FakeDevice('device1', 'device1'); - final FakeDevice device2 = FakeDevice('device2', 'device2'); - - setUp(() { - logger = BufferLogger.test(); + expect(logger.statusText, equals('')); + expect(devices, [attachedAndroidDevice1, wirelessAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 2); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 1); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); }); - group('when specified device id', () { - testUsingContext('returns device when device is found', () async { - testDeviceManager.specifiedDeviceId = 'device1'; - testDeviceManager.addDevice(device1); + group('finds no devices', () { + late BufferLogger logger; + late TestDeviceManager deviceManager; - final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, + setUp(() { + logger = BufferLogger.test(); + deviceManager = TestDeviceManager( logger: logger, + platform: platform, ); - final List? devices = await targetDevices.findAllTargetDevices(); - - expect(devices, [device1]); }); - testUsingContext('show error when no device found', () async { - testDeviceManager.specifiedDeviceId = 'device-id'; + group('when device not specified', () { + testUsingContext('when no devices', () async { + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); - final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, - logger: logger, - ); - final List? devices = await targetDevices.findAllTargetDevices(); + expect(logger.statusText, equals(''' +No supported devices connected. +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); - expect(devices, null); - expect(logger.statusText, contains(UserMessages().flutterNoMatchingDevice('device-id'))); + testUsingContext('when device is unsupported by flutter or project', () async { + deviceManager.androidDiscoverer.deviceList = [ + attachedUnsupportedAndroidDevice, + attachedUnsupportedForProjectAndroidDevice, + ]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +No supported devices connected. + +The following devices were found, but are not supported by this project: +target-device-3 (mobile) • xxx • android • Android 10 (unsupported) +target-device-4 (mobile) • xxx • android • Android 10 +If you would like your app to run on android, consider running `flutter create .` to generate projects for these platforms. +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); }); - testUsingContext('show error when multiple devices found', () async { - testDeviceManager.specifiedDeviceId = 'device'; - testDeviceManager.addDevice(device1); - testDeviceManager.addDevice(device2); - - final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, - logger: logger, - ); - final List? devices = await targetDevices.findAllTargetDevices(); - - expect(devices, null); - expect(logger.statusText, contains(UserMessages().flutterFoundSpecifiedDevices(2, 'device'))); - }); - }); - - group('when specified all', () { - testUsingContext('can return one device', () async { - testDeviceManager.specifiedDeviceId = 'all'; - testDeviceManager.addDevice(device1); - - final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, - logger: logger, - ); - final List? devices = await targetDevices.findAllTargetDevices(); - - expect(devices, [device1]); - }); - - testUsingContext('can return multiple devices', () async { - testDeviceManager.specifiedDeviceId = 'all'; - testDeviceManager.addDevice(device1); - testDeviceManager.addDevice(device2); - - final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, - logger: logger, - ); - final List? devices = await targetDevices.findAllTargetDevices(); - - expect(devices, [device1, device2]); - }); - - testUsingContext('show error when no device found', () async { - testDeviceManager.specifiedDeviceId = 'all'; - - final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, - logger: logger, - ); - final List? devices = await targetDevices.findAllTargetDevices(); - - expect(devices, null); - expect(logger.statusText, contains(UserMessages().flutterNoDevicesFound)); - }); - }); - - group('when device not specified', () { - testUsingContext('returns one device when only one device connected', () async { - testDeviceManager.addDevice(device1); - - final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, - logger: logger, - ); - final List? devices = await targetDevices.findAllTargetDevices(); - - expect(devices, [device1]); - }); - - testUsingContext('show error when no device found', () async { - final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, - logger: logger, - ); - final List? devices = await targetDevices.findAllTargetDevices(); - - expect(devices, null); - expect(logger.statusText, contains(UserMessages().flutterNoSupportedDevices)); - }); - - testUsingContext('show error when multiple devices found and not connected to terminal', () async { - testDeviceManager.addDevice(device1); - testDeviceManager.addDevice(device2); - - final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, - logger: logger, - ); - final List? devices = await targetDevices.findAllTargetDevices(); - - expect(devices, null); - expect(logger.statusText, contains(UserMessages().flutterSpecifyDeviceWithAllOption)); - }, overrides: { - AnsiTerminal: () => FakeTerminal(stdinHasTerminal: false), - }); - - // Prompt to choose device when multiple devices found and connected to terminal - group('show prompt', () { - late FakeTerminal terminal; + group('with hasSpecifiedDeviceId', () { setUp(() { - terminal = FakeTerminal(); + deviceManager.specifiedDeviceId = 'target-device'; }); - testUsingContext('choose first device', () async { - testDeviceManager.addDevice(device1); - testDeviceManager.addDevice(device2); - terminal.setPrompt(['1', '2', 'q', 'Q'], '1'); - + testUsingContext('when no devices', () async { final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, + deviceManager: deviceManager, logger: logger, ); final List? devices = await targetDevices.findAllTargetDevices(); - expect(devices, [device1]); - }, overrides: { - AnsiTerminal: () => terminal, + expect(logger.statusText, equals(''' +No supported devices found with name or id matching 'target-device'. +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 4); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); }); - testUsingContext('choose second device', () async { - testDeviceManager.addDevice(device1); - testDeviceManager.addDevice(device2); - terminal.setPrompt(['1', '2', 'q', 'Q'], '2'); + testUsingContext('when no devices match', () async { + final FakeDevice device1 = FakeDevice(deviceName: 'no-match-1'); + final FakeDevice device2 = FakeDevice.wireless(deviceName: 'no-match-2'); + deviceManager.androidDiscoverer.deviceList = [device1, device2]; final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, + deviceManager: deviceManager, logger: logger, ); final List? devices = await targetDevices.findAllTargetDevices(); - expect(devices, [device2]); + expect(logger.statusText, equals(''' +No supported devices found with name or id matching 'target-device'. + +The following devices were found: +no-match-1 (mobile) • xxx • android • Android 10 +no-match-2 (mobile) • xxx • android • Android 10 +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 4); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + + testUsingContext('when matching device is unsupported by flutter', () async { + deviceManager.androidDiscoverer.deviceList = [exactMatchattachedUnsupportedAndroidDevice]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +No supported devices found with name or id matching 'target-device'. + +The following devices were found: +target-device (mobile) • xxx • android • Android 10 (unsupported) +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 4); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + }); + + group('with hasSpecifiedAllDevices', () { + setUp(() { + deviceManager.hasSpecifiedAllDevices = true; + }); + + testUsingContext('when no devices', () async { + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +No devices found. +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + + testUsingContext('when devices are either unsupported by flutter or project or all', () async { + deviceManager.androidDiscoverer.deviceList = [ + attachedUnsupportedAndroidDevice, + attachedUnsupportedForProjectAndroidDevice, + ]; + deviceManager.otherDiscoverer.deviceList = [fuchsiaDevice]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +No devices found. + +The following devices were found, but are not supported by this project: +target-device-3 (mobile) • xxx • android • Android 10 (unsupported) +target-device-4 (mobile) • xxx • android • Android 10 +target-device-10 (mobile) • xxx • fuchsia-arm64 • tester +If you would like your app to run on android or fuchsia, consider running `flutter create .` to generate projects for these platforms. +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + + }); + + }); + + group('finds single device', () { + late BufferLogger logger; + late TestDeviceManager deviceManager; + + setUp(() { + logger = BufferLogger.test(); + deviceManager = TestDeviceManager( + logger: logger, + platform: platform, + ); + }); + + group('when device not specified', () { + testUsingContext('when single attached device', () async { + deviceManager.androidDiscoverer.deviceList = [attachedAndroidDevice1]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals('')); + expect(devices, [attachedAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 2); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + + testUsingContext('when single wireless device', () async { + deviceManager.androidDiscoverer.deviceList = [wirelessAndroidDevice1]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals('')); + expect(devices, [wirelessAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 2); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + + testUsingContext('when multiple but only one ephemeral', () async { + deviceManager.androidDiscoverer.deviceList = [nonEphemeralDevice, wirelessAndroidDevice1]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals('')); + expect(devices, [wirelessAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 2); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + }); + + group('with hasSpecifiedDeviceId', () { + setUp(() { + deviceManager.specifiedDeviceId = 'target-device'; + }); + + testUsingContext('when multiple matches but first is unsupported by flutter', () async { + deviceManager.androidDiscoverer.deviceList = [ + exactMatchattachedUnsupportedAndroidDevice, + exactMatchAndroidDevice, + ]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals('')); + expect(devices, [exactMatchAndroidDevice]); + expect(deviceManager.androidDiscoverer.devicesCalled, 1); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + + testUsingContext('when matching device is unsupported by project', () async { + deviceManager.androidDiscoverer.deviceList = [exactMatchUnsupportedByProjectDevice]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals('')); + expect(devices, [exactMatchUnsupportedByProjectDevice]); + expect(deviceManager.androidDiscoverer.devicesCalled, 1); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + + testUsingContext('when matching attached device', () async { + deviceManager.androidDiscoverer.deviceList = [exactMatchAndroidDevice]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals('')); + expect(devices, [exactMatchAndroidDevice]); + expect(deviceManager.androidDiscoverer.devicesCalled, 1); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + + testUsingContext('when matching wireless device', () async { + deviceManager.androidDiscoverer.deviceList = [exactMatchWirelessAndroidDevice]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals('')); + expect(devices, [exactMatchWirelessAndroidDevice]); + expect(deviceManager.androidDiscoverer.devicesCalled, 1); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + + testUsingContext('when exact match attached device and partial match wireless device', () async { + deviceManager.androidDiscoverer.deviceList = [exactMatchAndroidDevice, wirelessAndroidDevice1]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals('')); + expect(devices, [exactMatchAndroidDevice]); + expect(deviceManager.androidDiscoverer.devicesCalled, 1); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + }); + + group('with hasSpecifiedAllDevices', () { + setUp(() { + deviceManager.hasSpecifiedAllDevices = true; + }); + + testUsingContext('when only one device', () async { + deviceManager.androidDiscoverer.deviceList = [attachedAndroidDevice1]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals('')); + expect(devices, [attachedAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 2); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }); + }); + + }); + + group('Finds multiple devices', () { + late BufferLogger logger; + late TestDeviceManager deviceManager; + + setUp(() { + logger = BufferLogger.test(); + deviceManager = TestDeviceManager( + logger: logger, + platform: platform, + ); + }); + + group('when device not specified', () { + group('with stdinHasTerminal', () { + late FakeTerminal terminal; + + setUp(() { + terminal = FakeTerminal(); + }); + + testUsingContext('including attached, wireless, unsupported devices', () async { + deviceManager.androidDiscoverer.deviceList = [ + attachedAndroidDevice1, + attachedUnsupportedAndroidDevice, + attachedUnsupportedForProjectAndroidDevice, + wirelessAndroidDevice1, + wirelessUnsupportedAndroidDevice, + wirelessUnsupportedForProjectAndroidDevice, + ]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + terminal.setPrompt(['1', '2', 'q', 'Q'], '2'); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Multiple devices found: +target-device-1 (mobile) • xxx • android • Android 10 + +Wirelessly connected devices: +target-device-5 (mobile) • xxx • android • Android 10 + +[1]: target-device-1 (xxx) +[2]: target-device-5 (xxx) +''')); + expect(devices, [wirelessAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 2); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); }, overrides: { AnsiTerminal: () => terminal, }); - testUsingContext('exits without choosing device', () async { - testDeviceManager.addDevice(device1); - testDeviceManager.addDevice(device2); - terminal.setPrompt(['1', '2', 'q', 'Q'], 'q'); + testUsingContext('including only attached devices', () async { + deviceManager.androidDiscoverer.deviceList = [attachedAndroidDevice1, attachedAndroidDevice2]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + terminal.setPrompt(['1', '2', 'q', 'Q'], '1'); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Multiple devices found: +target-device-1 (mobile) • xxx • android • Android 10 +target-device-2 (mobile) • xxx • android • Android 10 +[1]: target-device-1 (xxx) +[2]: target-device-2 (xxx) +''')); + expect(devices, [attachedAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 2); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + testUsingContext('including only wireless devices', () async { + deviceManager.androidDiscoverer.deviceList = [wirelessAndroidDevice1, wirelessAndroidDevice2]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + terminal.setPrompt(['1', '2', 'q', 'Q'], '1'); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Multiple devices found: + +Wirelessly connected devices: +target-device-5 (mobile) • xxx • android • Android 10 +target-device-6 (mobile) • xxx • android • Android 10 + +[1]: target-device-5 (xxx) +[2]: target-device-6 (xxx) +''')); + expect(devices, [wirelessAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 2); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + }); + + group('without stdinHasTerminal', () { + late FakeTerminal terminal; + + setUp(() { + terminal = FakeTerminal(stdinHasTerminal: false); + }); + + testUsingContext('including attached, wireless, unsupported devices', () async { + deviceManager.androidDiscoverer.deviceList = [ + attachedAndroidDevice1, + attachedUnsupportedAndroidDevice, + attachedUnsupportedForProjectAndroidDevice, + wirelessAndroidDevice1, + wirelessUnsupportedAndroidDevice, + wirelessUnsupportedForProjectAndroidDevice, + ]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +More than one device connected; please specify a device with the '-d ' flag, or use '-d all' to act on all devices. + +target-device-1 (mobile) • xxx • android • Android 10 +target-device-4 (mobile) • xxx • android • Android 10 + +Wirelessly connected devices: +target-device-5 (mobile) • xxx • android • Android 10 +target-device-8 (mobile) • xxx • android • Android 10 +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 4); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + testUsingContext('including only attached devices', () async { + deviceManager.androidDiscoverer.deviceList = [attachedAndroidDevice1, attachedAndroidDevice2]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +More than one device connected; please specify a device with the '-d ' flag, or use '-d all' to act on all devices. + +target-device-1 (mobile) • xxx • android • Android 10 +target-device-2 (mobile) • xxx • android • Android 10 +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 4); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + testUsingContext('including only wireless devices', () async { + deviceManager.androidDiscoverer.deviceList = [wirelessAndroidDevice1, wirelessAndroidDevice2]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +More than one device connected; please specify a device with the '-d ' flag, or use '-d all' to act on all devices. + +Wirelessly connected devices: +target-device-5 (mobile) • xxx • android • Android 10 +target-device-6 (mobile) • xxx • android • Android 10 +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 4); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + }); + }); + + group('with hasSpecifiedDeviceId', () { + setUp(() { + deviceManager.specifiedDeviceId = 'target-device'; + }); + + group('with stdinHasTerminal', () { + late FakeTerminal terminal; + + setUp(() { + terminal = FakeTerminal(); + }); + + testUsingContext('including attached, wireless, unsupported devices', () async { + deviceManager.androidDiscoverer.deviceList = [ + attachedAndroidDevice1, + attachedUnsupportedAndroidDevice, + attachedUnsupportedForProjectAndroidDevice, + wirelessAndroidDevice1, + wirelessUnsupportedAndroidDevice, + wirelessUnsupportedForProjectAndroidDevice, + ]; + + terminal.setPrompt(['1', '2', '3', '4', 'q', 'Q'], '2'); + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Found 4 devices with name or id matching target-device: +target-device-1 (mobile) • xxx • android • Android 10 +target-device-4 (mobile) • xxx • android • Android 10 + +Wirelessly connected devices: +target-device-5 (mobile) • xxx • android • Android 10 +target-device-8 (mobile) • xxx • android • Android 10 + +[1]: target-device-1 (xxx) +[2]: target-device-4 (xxx) +[3]: target-device-5 (xxx) +[4]: target-device-8 (xxx) +''')); + expect(devices, [attachedUnsupportedForProjectAndroidDevice]); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + testUsingContext('including only attached devices', () async { + deviceManager.androidDiscoverer.deviceList = [attachedAndroidDevice1, attachedAndroidDevice2]; + + terminal.setPrompt(['1', '2', 'q', 'Q'], '1'); + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Found 2 devices with name or id matching target-device: +target-device-1 (mobile) • xxx • android • Android 10 +target-device-2 (mobile) • xxx • android • Android 10 +[1]: target-device-1 (xxx) +[2]: target-device-2 (xxx) +''')); + expect(devices, [attachedAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + testUsingContext('including only wireless devices', () async { + deviceManager.androidDiscoverer.deviceList = [wirelessAndroidDevice1, wirelessAndroidDevice2]; + + terminal.setPrompt(['1', '2', 'q', 'Q'], '1'); + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Found 2 devices with name or id matching target-device: + +Wirelessly connected devices: +target-device-5 (mobile) • xxx • android • Android 10 +target-device-6 (mobile) • xxx • android • Android 10 + +[1]: target-device-5 (xxx) +[2]: target-device-6 (xxx) +''')); + expect(devices, [wirelessAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + }); + + group('without stdinHasTerminal', () { + late FakeTerminal terminal; + + setUp(() { + terminal = FakeTerminal(stdinHasTerminal: false); + }); + + testUsingContext('including only one ephemeral', () async { + deviceManager.androidDiscoverer.deviceList = [nonEphemeralDevice, attachedAndroidDevice1]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Found 2 devices with name or id matching target-device: +target-device-9 (mobile) • xxx • android • Android 10 +target-device-1 (mobile) • xxx • android • Android 10 +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + testUsingContext('including matching attached, wireless, unsupported devices', () async { + deviceManager.androidDiscoverer.deviceList = [ + attachedAndroidDevice1, + attachedUnsupportedAndroidDevice, + attachedUnsupportedForProjectAndroidDevice, + wirelessAndroidDevice1, + wirelessUnsupportedAndroidDevice, + wirelessUnsupportedForProjectAndroidDevice, + ]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Found 4 devices with name or id matching target-device: +target-device-1 (mobile) • xxx • android • Android 10 +target-device-4 (mobile) • xxx • android • Android 10 + +Wirelessly connected devices: +target-device-5 (mobile) • xxx • android • Android 10 +target-device-8 (mobile) • xxx • android • Android 10 +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + testUsingContext('including only attached devices', () async { + deviceManager.androidDiscoverer.deviceList = [attachedAndroidDevice1, attachedAndroidDevice2]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Found 2 devices with name or id matching target-device: +target-device-1 (mobile) • xxx • android • Android 10 +target-device-2 (mobile) • xxx • android • Android 10 +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + testUsingContext('including only wireless devices', () async { + deviceManager.androidDiscoverer.deviceList = [wirelessAndroidDevice1, wirelessAndroidDevice2]; + + final TargetDevices targetDevices = TargetDevices( + deviceManager: deviceManager, + logger: logger, + ); + final List? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Found 2 devices with name or id matching target-device: + +Wirelessly connected devices: +target-device-5 (mobile) • xxx • android • Android 10 +target-device-6 (mobile) • xxx • android • Android 10 +''')); + expect(devices, isNull); + expect(deviceManager.androidDiscoverer.devicesCalled, 3); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); + }, overrides: { + AnsiTerminal: () => terminal, + }); + }); + }); + + group('with hasSpecifiedAllDevices', () { + setUp(() { + deviceManager.hasSpecifiedAllDevices = true; + }); + + testUsingContext('including attached, wireless, unsupported devices', () async { + deviceManager.androidDiscoverer.deviceList = [ + attachedAndroidDevice1, + attachedUnsupportedAndroidDevice, + attachedUnsupportedForProjectAndroidDevice, + wirelessAndroidDevice1, + wirelessUnsupportedAndroidDevice, + wirelessUnsupportedForProjectAndroidDevice, + ]; + deviceManager.otherDiscoverer.deviceList = [fuchsiaDevice]; final TargetDevices targetDevices = TargetDevices( - deviceManager: testDeviceManager, + deviceManager: deviceManager, logger: logger, ); + final List? devices = await targetDevices.findAllTargetDevices(); - await expectLater( - targetDevices.findAllTargetDevices(), - throwsToolExit(), - ); - }, overrides: { - AnsiTerminal: () => terminal, + expect(logger.statusText, equals('')); + expect(devices, [attachedAndroidDevice1, wirelessAndroidDevice1]); + expect(deviceManager.androidDiscoverer.devicesCalled, 2); + expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0); + expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1); }); }); }); - }); - group('Filter devices', () { - late BufferLogger logger; - final FakeDevice ephemeralOne = FakeDevice('ephemeralOne', 'ephemeralOne'); - final FakeDevice ephemeralTwo = FakeDevice('ephemeralTwo', 'ephemeralTwo'); - final FakeDevice nonEphemeralOne = FakeDevice('nonEphemeralOne', 'nonEphemeralOne', ephemeral: false); - - setUp(() { - logger = BufferLogger.test(); - }); - - testUsingContext('chooses ephemeral device', () async { - final List devices = [ - ephemeralOne, - nonEphemeralOne, - ]; - final DeviceManager deviceManager = TestDeviceManager( - devices, - logger: logger, - ); - - final TargetDevices targetDevices = TargetDevices(deviceManager: deviceManager, logger: logger); - final List filtered = await targetDevices.getDevices(); - - expect(filtered, [ephemeralOne]); - }); - - testUsingContext('returns all devices when multiple non ephemeral devices are found', () async { - final List devices = [ - ephemeralOne, - ephemeralTwo, - nonEphemeralOne, - ]; - final DeviceManager deviceManager = TestDeviceManager( - devices, - logger: logger, - ); - - final TargetDevices targetDevices = TargetDevices(deviceManager: deviceManager, logger: logger); - final List filtered = await targetDevices.getDevices(); - - expect(filtered, [ - ephemeralOne, - ephemeralTwo, - nonEphemeralOne, - ]); - }); }); } class TestDeviceManager extends DeviceManager { - TestDeviceManager( - List allDevices, { - List? deviceDiscoveryOverrides, - required super.logger, - String? wellKnownId, - FakePollingDeviceDiscovery? fakeDiscoverer, - }) : _fakeDeviceDiscoverer = fakeDiscoverer ?? FakePollingDeviceDiscovery(), - _deviceDiscoverers = [], - super() { - if (wellKnownId != null) { - _fakeDeviceDiscoverer.wellKnownIds.add(wellKnownId); - } - _deviceDiscoverers.add(_fakeDeviceDiscoverer); - if (deviceDiscoveryOverrides != null) { - _deviceDiscoverers.addAll(deviceDiscoveryOverrides); - } - resetDevices(allDevices); - } - @override - List get deviceDiscoverers => _deviceDiscoverers; - final List _deviceDiscoverers; - final FakePollingDeviceDiscovery _fakeDeviceDiscoverer; + TestDeviceManager({ + required this.logger, + required this.platform, + }) : super(logger: logger); - void resetDevices(List allDevices) { - _fakeDeviceDiscoverer.setDevices(allDevices); + final Logger logger; + final Platform platform; + + @override + String? specifiedDeviceId; + + @override + bool hasSpecifiedAllDevices = false; + + final TestPollingDeviceDiscovery androidDiscoverer = TestPollingDeviceDiscovery( + 'android', + ); + final TestPollingDeviceDiscovery otherDiscoverer = TestPollingDeviceDiscovery( + 'other', + ); + final TestPollingDeviceDiscovery iosDiscoverer = TestPollingDeviceDiscovery( + 'ios', + ); + + @override + List get deviceDiscoverers { + return [ + androidDiscoverer, + otherDiscoverer, + iosDiscoverer, + ]; } } -class MockDeviceDiscovery extends Fake implements DeviceDiscovery { +class TestPollingDeviceDiscovery extends PollingDeviceDiscovery { + TestPollingDeviceDiscovery(super.name); + + List deviceList = []; + List refreshDeviceList = []; int devicesCalled = 0; int discoverDevicesCalled = 0; + int numberOfTimesPolled = 0; @override - bool supportsPlatform = true; + bool get supportsPlatform => true; - List deviceValues = []; + @override + List get wellKnownIds => const []; + + @override + Future> pollingGetDevices({Duration? timeout}) async { + numberOfTimesPolled++; + return deviceList; + } @override Future> devices({DeviceDiscoveryFilter? filter}) async { devicesCalled += 1; - return deviceValues; + return super.devices(filter: filter); } @override Future> discoverDevices({ Duration? timeout, DeviceDiscoveryFilter? filter, - }) async { - discoverDevicesCalled += 1; - return deviceValues; + }) { + discoverDevicesCalled++; + if (refreshDeviceList.isNotEmpty) { + deviceList = refreshDeviceList; + } + return super.discoverDevices(timeout: timeout, filter: filter); } @override - List get wellKnownIds => []; + bool get canListAnything => true; +} + +// 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 FakeDevice extends Fake implements Device { + FakeDevice({ + String? deviceId, + String? deviceName, + bool deviceSupported = true, + bool deviceSupportForProject = true, + this.ephemeral = true, + this.isConnected = true, + this.connectionInterface = DeviceConnectionInterface.attached, + this.platformType = PlatformType.android, + TargetPlatform deviceTargetPlatform = TargetPlatform.android, + }) : id = deviceId ?? 'xxx', + name = deviceName ?? 'test', + _isSupported = deviceSupported, + _isSupportedForProject = deviceSupportForProject, + _targetPlatform = deviceTargetPlatform; + + FakeDevice.wireless({ + String? deviceId, + String? deviceName, + bool deviceSupported = true, + bool deviceSupportForProject = true, + this.ephemeral = true, + this.isConnected = true, + this.connectionInterface = DeviceConnectionInterface.wireless, + this.platformType = PlatformType.android, + TargetPlatform deviceTargetPlatform = TargetPlatform.android, + }) : id = deviceId ?? 'xxx', + name = deviceName ?? 'test', + _isSupported = deviceSupported, + _isSupportedForProject = deviceSupportForProject, + _targetPlatform = deviceTargetPlatform; + + FakeDevice.fuchsia({ + String? deviceId, + String? deviceName, + bool deviceSupported = true, + bool deviceSupportForProject = true, + this.ephemeral = true, + this.isConnected = true, + this.connectionInterface = DeviceConnectionInterface.attached, + this.platformType = PlatformType.fuchsia, + TargetPlatform deviceTargetPlatform = TargetPlatform.fuchsia_arm64, + }) : id = deviceId ?? 'xxx', + name = deviceName ?? 'test', + _isSupported = deviceSupported, + _isSupportedForProject = deviceSupportForProject, + _targetPlatform = deviceTargetPlatform, + _sdkNameAndVersion = 'tester'; + + final bool _isSupported; + final bool _isSupportedForProject; + final TargetPlatform _targetPlatform; + String _sdkNameAndVersion = 'Android 10'; + + @override + String name; + + @override + final bool ephemeral; + + @override + String id; + + @override + bool isSupported() => _isSupported; + + @override + bool isSupportedForProject(FlutterProject project) => _isSupportedForProject; + + @override + DeviceConnectionInterface connectionInterface; + + @override + bool isConnected; + + @override + Future get targetPlatform async => _targetPlatform; + + @override + final PlatformType? platformType; + + @override + Future get sdkNameAndVersion async => _sdkNameAndVersion; + + @override + Future get isLocalEmulator async => false; + + @override + Category? get category => Category.mobile; + + @override + Future get targetPlatformDisplayName async => + getNameForTargetPlatform(await targetPlatform); } class FakeTerminal extends Fake implements AnsiTerminal { @@ -446,31 +1108,11 @@ class FakeTerminal extends Fake implements AnsiTerminal { } } -class FakeDoctor extends Doctor { - FakeDoctor( - Logger logger, { +class FakeDoctor extends Fake implements Doctor { + FakeDoctor({ this.canLaunchAnything = true, - }) : super(logger: logger); + }); - // True for testing. - @override - bool get canListAnything => true; - - // True for testing. @override bool canLaunchAnything; - - @override - /// Replaces the android workflow with a version that overrides licensesAccepted, - /// to prevent individual tests from having to mock out the process for - /// the Doctor. - List get validators { - final List superValidators = super.validators; - return superValidators.map((DoctorValidator v) { - if (v is AndroidLicenseValidator) { - return FakeAndroidLicenseValidator(); - } - return v; - }).toList(); - } } diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index 9b8aff7f10..8d8358f14e 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -181,7 +181,8 @@ void _printBufferedErrors(AppContext testContext) { } class FakeDeviceManager implements DeviceManager { - List devices = []; + List attachedDevices = []; + List wirelessDevices = []; String? _specifiedDeviceId; @@ -209,20 +210,20 @@ class FakeDeviceManager implements DeviceManager { @override Future> getAllDevices({ DeviceDiscoveryFilter? filter, - }) async => devices; + }) async => filteredDevices(filter); @override Future> refreshAllDevices({ Duration? timeout, DeviceDiscoveryFilter? filter, - }) async => devices; + }) async => filteredDevices(filter); @override Future> getDevicesById( String deviceId, { DeviceDiscoveryFilter? filter, }) async { - return devices.where((Device device) { + return filteredDevices(filter).where((Device device) { return device.id == deviceId || device.id.startsWith(deviceId); }).toList(); } @@ -236,7 +237,8 @@ class FakeDeviceManager implements DeviceManager { : getAllDevices(filter: filter); } - void addDevice(Device device) => devices.add(device); + void addAttachedDevice(Device device) => attachedDevices.add(device); + void addWirelessDevice(Device device) => wirelessDevices.add(device); @override bool get canListAnything => true; @@ -257,6 +259,16 @@ class FakeDeviceManager implements DeviceManager { @override Device? getSingleEphemeralDevice(List devices) => null; + + List filteredDevices(DeviceDiscoveryFilter? filter) { + if (filter?.deviceConnectionInterface == DeviceConnectionInterface.attached) { + return attachedDevices; + } + if (filter?.deviceConnectionInterface == DeviceConnectionInterface.wireless) { + return wirelessDevices; + } + return attachedDevices + wirelessDevices; + } } class TestDeviceDiscoverySupportFilter extends Fake implements DeviceDiscoverySupportFilter { diff --git a/packages/flutter_tools/test/src/fake_devices.dart b/packages/flutter_tools/test/src/fake_devices.dart index 9463c153e3..c74f7adb64 100644 --- a/packages/flutter_tools/test/src/fake_devices.dart +++ b/packages/flutter_tools/test/src/fake_devices.dart @@ -54,6 +54,31 @@ List fakeDevices = [ }, }, ), + FakeDeviceJsonData( + FakeDevice( + 'wireless android', + 'wireless-android', + type: PlatformType.android, + connectionInterface: DeviceConnectionInterface.wireless, + ), + { + 'name': 'wireless android', + 'id': 'wireless-android', + 'isSupported': true, + 'targetPlatform': 'android-arm', + 'emulator': true, + 'sdk': 'Test SDK (1.2.3)', + 'capabilities': { + 'hotReload': true, + 'hotRestart': true, + 'screenshot': false, + 'fastStart': false, + 'flutterExit': true, + 'hardwareRendering': true, + 'startPaused': true, + }, + } + ), ]; /// Fake device to test `devices` command.