Unverified Commit 27248d4b authored by Victoria Ashworth's avatar Victoria Ashworth Committed by GitHub

Separate attached and wireless devices (#122615)

Separate attached and wireless devices
parent 5d10cc28
......@@ -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<Map<String, String>> _properties = () async {
Map<String, String> properties = <String, String>{};
......
......@@ -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<String> unsupportedDevicesType) =>
'If you would like your app to run on ${unsupportedDevicesType.join(' or ')}, consider running `flutter create .` to generate projects for these platforms.';
......
......@@ -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) {
......
......@@ -79,13 +79,59 @@ class DevicesCommandOutput {
final Duration? deviceDiscoveryTimeout;
Future<List<Device>> _getAttachedDevices(DeviceManager deviceManager) async {
return deviceManager.getAllDevices(
filter: DeviceDiscoveryFilter(
deviceConnectionInterface: DeviceConnectionInterface.attached,
),
);
}
Future<List<Device>> _getWirelessDevices(DeviceManager deviceManager) async {
return deviceManager.getAllDevices(
filter: DeviceDiscoveryFilter(
deviceConnectionInterface: DeviceConnectionInterface.wireless,
),
);
}
Future<void> findAndOutputAllTargetDevices({required bool machine}) async {
final List<Device> devices = await globals.deviceManager?.refreshAllDevices(timeout: deviceDiscoveryTimeout) ?? <Device>[];
List<Device> attachedDevices = <Device>[];
List<Device> wirelessDevices = <Device>[];
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<Device> allDevices = attachedDevices + wirelessDevices;
if (machine) {
await printDevicesAsJson(devices);
await printDevicesAsJson(allDevices);
return;
}
if (allDevices.isEmpty) {
_printNoDevicesDetected();
} else {
if (devices.isEmpty) {
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();
......@@ -98,12 +144,6 @@ class DevicesCommandOutput {
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();
}
}
Future<void> _printDiagnostics() async {
......
......@@ -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<bool> 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;
......
......@@ -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) {
......
......@@ -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<String, Object?> platformArgs, {
bool ipv6 = false,
IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.none
DeviceConnectionInterface interfaceType = DeviceConnectionInterface.attached,
}) {
final String dartVmFlags = computeDartVmFlags(this);
return <String>[
......@@ -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',
];
......
......@@ -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<IOSApp?, DeviceLogReader> _logReaders = <IOSApp?, DeviceLogReader>{};
......@@ -256,7 +257,7 @@ class IOSDevice extends Device {
bundlePath: bundle.path,
appDeltaDirectory: app.appDeltaDirectory,
launchArguments: <String>[],
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<void> takeScreenshot(File outputFile) async {
await _iMobileDevice.takeScreenshot(outputFile, id, interfaceType);
await _iMobileDevice.takeScreenshot(outputFile, id, connectionInterface);
}
@override
......
......@@ -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 List<String>launchArguments,
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) ...<String>[
'--args',
......@@ -126,7 +126,7 @@ class IOSDeploy {
required String deviceId,
required String bundlePath,
required List<String> 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) ...<String>[
'--args',
......@@ -171,7 +171,7 @@ class IOSDeploy {
required String deviceId,
required String bundlePath,
required List<String> 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',
......
......@@ -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.
......
......@@ -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<void> takeScreenshot(
File outputFile,
String deviceID,
IOSDeviceConnectionInterface interfaceType,
DeviceConnectionInterface interfaceType,
) {
return _processUtils.run(
<String>[
......@@ -95,7 +95,7 @@ class IMobileDevice {
outputFile.path,
'--udid',
deviceID,
if (interfaceType == IOSDeviceConnectionInterface.network)
if (interfaceType == DeviceConnectionInterface.wireless)
'--network',
],
throwOnError: true,
......
......@@ -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<String, Object?> deviceProperties) {
// Interface can be "usb", "network", or "none" for simulators
// and unknown future interfaces.
static DeviceConnectionInterface _interfaceType(Map<String, Object?> 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<String, Object?> deviceProperties) {
......
......@@ -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<List<Device>> _getAttachedDevices({
DeviceDiscoverySupportFilter? supportFilter,
}) async {
return _deviceManager.getDevices(
filter: DeviceDiscoveryFilter(
deviceConnectionInterface: DeviceConnectionInterface.attached,
supportFilter: supportFilter,
),
);
}
Future<List<Device>> _getWirelessDevices({
DeviceDiscoverySupportFilter? supportFilter,
}) async {
return _deviceManager.getDevices(
filter: DeviceDiscoveryFilter(
deviceConnectionInterface: DeviceConnectionInterface.wireless,
supportFilter: supportFilter,
),
);
}
Future<List<Device>> _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<List<Device>?> findAllTargetDevices({
Duration? deviceDiscoveryTimeout,
bool includeDevicesUnsupportedByProject = false,
......@@ -32,67 +90,175 @@ class TargetDevices {
_logger.printError(userMessages.flutterNoDevelopmentDevice);
return null;
}
List<Device> devices = await getDevices(
if (deviceDiscoveryTimeout != null) {
// Reset the cache with the specified timeout.
await _deviceManager.refreshAllDevices(timeout: deviceDiscoveryTimeout);
}
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<Device> devices = await _getDeviceById(
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
timeout: deviceDiscoveryTimeout,
);
if (devices.length == 1) {
return devices;
}
}
final List<Device> attachedDevices = await _getAttachedDevices(
supportFilter: _defaultSupportFilter(includeDevicesUnsupportedByProject),
);
final List<Device> wirelessDevices = await _getWirelessDevices(
supportFilter: _defaultSupportFilter(includeDevicesUnsupportedByProject),
);
final List<Device> 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;
}
/// When no supported devices are found, display a message and list of
/// unsupported devices found.
Future<List<Device>?> _handleNoDevices() async {
// Get connected devices from cache, including unsupported ones.
final List<Device> unsupportedDevices = await _deviceManager.getAllDevices();
if (devices.isEmpty) {
if (_deviceManager.hasSpecifiedDeviceId) {
_logger.printStatus(userMessages.flutterNoMatchingDevice(_deviceManager.specifiedDeviceId!));
final List<Device> allDevices = await _deviceManager.getAllDevices();
if (allDevices.isNotEmpty) {
_logger.printStatus(
userMessages.flutterNoMatchingDevice(_deviceManager.specifiedDeviceId!),
);
if (unsupportedDevices.isNotEmpty) {
_logger.printStatus('');
_logger.printStatus('The following devices were found:');
await Device.printDevices(allDevices, _logger);
await Device.printDevices(unsupportedDevices, _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!));
_logger.printStatus(_deviceManager.hasSpecifiedAllDevices
? userMessages.flutterNoDevicesFound
: userMessages.flutterNoSupportedDevices);
await _printUnsupportedDevice(unsupportedDevices);
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);
}
// Update the [DeviceManager.specifiedDeviceId] so that we will not be prompted again.
_deviceManager.specifiedDeviceId = chosenDevice.id;
/// 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<List<Device>?> _handleMultipleDevices(
List<Device> attachedDevices,
List<Device> wirelessDevices,
) async {
final List<Device> allDevices = attachedDevices + wirelessDevices;
final Device? ephemeralDevice = _deviceManager.getSingleEphemeralDevice(allDevices);
if (ephemeralDevice != null) {
return <Device>[ephemeralDevice];
}
if (globals.terminal.stdinHasTerminal) {
return _selectFromMultipleDevices(attachedDevices, wirelessDevices);
} else {
return _printMultipleDevices(attachedDevices, wirelessDevices);
}
}
devices = <Device>[chosenDevice];
/// 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<List<Device>?> _printMultipleDevices(
List<Device> attachedDevices,
List<Device> wirelessDevices,
) async {
List<Device> supportedAttachedDevices = attachedDevices;
List<Device> supportedWirelessDevices = wirelessDevices;
if (_deviceManager.hasSpecifiedDeviceId) {
final int allDeviceLength = supportedAttachedDevices.length + supportedWirelessDevices.length;
_logger.printStatus(userMessages.flutterFoundSpecifiedDevices(
allDeviceLength,
_deviceManager.specifiedDeviceId!,
));
} else {
// Show an error message asking the user to specify `-d all` if they
// want to run on multiple devices.
final List<Device> allDevices = await _deviceManager.getAllDevices();
// 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(allDevices, _logger);
}
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<List<Device>?> _selectFromMultipleDevices(
List<Device> attachedDevices,
List<Device> wirelessDevices,
) async {
final List<Device> 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('');
}
return devices;
final Device chosenDevice = await _chooseOneOfAvailableDevices(allDevices);
// Update the [DeviceManager.specifiedDeviceId] so that the user will not be prompted again.
_deviceManager.specifiedDeviceId = chosenDevice.id;
return <Device>[chosenDevice];
}
Future<void> _printUnsupportedDevice(DeviceManager deviceManager) async {
final List<Device> unsupportedDevices = await deviceManager.getDevices();
Future<void> _printUnsupportedDevice(List<Device> 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<List<Device>> getDevices({
bool includeDevicesUnsupportedByProject = false,
Duration? timeout,
}) async {
if (timeout != null) {
// Reset the cache with the specified timeout.
await _deviceManager.refreshAllDevices(timeout: timeout);
}
final List<Device> 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 <Device>[ephemeralDevice];
}
}
return devices;
}
}
......@@ -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>[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>[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>[device];
final FakeHotRunner hotRunner = FakeHotRunner();
......@@ -1237,6 +1236,10 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override
Future<TargetPlatform> 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!;
......
......@@ -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(<String>['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: <Type, Generator>{
AndroidSdk: () => null,
DeviceManager: () => NoDevicesManager(),
......@@ -48,16 +59,27 @@ void main() {
Artifacts: () => Artifacts.test(),
});
group('when includes both attached and wireless devices', () {
List<FakeDeviceJsonData>? deviceList;
setUp(() {
deviceList = <FakeDeviceJsonData>[
fakeDevices[0],
fakeDevices[1],
fakeDevices[2],
];
});
testUsingContext("get devices' platform types", () async {
final List<String> platformTypes = Device.devicesPlatformTypes(
await globals.deviceManager!.getAllDevices(),
);
expect(platformTypes, <String>['android', 'web']);
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(),
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Cache: () => cache,
Artifacts: () => Artifacts.test(),
Platform: () => platform,
});
testUsingContext('Outputs parsable JSON with --machine flag', () async {
......@@ -65,77 +87,115 @@ void main() {
await createTestCommandRunner(command).run(<String>['devices', '--machine']);
expect(
json.decode(testLogger.statusText),
<Map<String,Object>>[
<String, Object>{
'name': 'ephemeral',
'id': 'ephemeral',
'isSupported': true,
'targetPlatform': 'android-arm',
'emulator': true,
'sdk': 'Test SDK (1.2.3)',
'capabilities': <String, Object>{
'hotReload': true,
'hotRestart': true,
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': true,
'startPaused': true,
},
},
<String,Object>{
'name': 'webby',
'id': 'webby',
'isSupported': true,
'targetPlatform': 'web-javascript',
'emulator': true,
'sdk': 'Web SDK (1.2.4)',
'capabilities': <String, Object>{
'hotReload': true,
'hotRestart': true,
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': true,
'startPaused': true,
},
},
<Map<String, Object>>[
fakeDevices[0].json,
fakeDevices[1].json,
fakeDevices[2].json,
],
);
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(),
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(<String>['devices']);
expect(
testLogger.statusText,
'''
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: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
});
});
group('when includes only attached devices', () {
List<FakeDeviceJsonData>? deviceList;
setUp(() {
deviceList = <FakeDeviceJsonData>[
fakeDevices[0],
fakeDevices[1],
];
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['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: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(),
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
});
});
group('when includes only wireless devices', () {
List<FakeDeviceJsonData>? deviceList;
setUp(() {
deviceList = <FakeDeviceJsonData>[
fakeDevices[2],
];
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['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: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
});
});
});
}
class _FakeDeviceManager extends DeviceManager {
_FakeDeviceManager() : super(logger: testLogger);
_FakeDeviceManager({
List<FakeDeviceJsonData>? devices,
}) : fakeDevices = devices ?? <FakeDeviceJsonData>[],
super(logger: testLogger);
List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[];
@override
Future<List<Device>> getAllDevices({DeviceDiscoveryFilter? filter}) =>
Future<List<Device>>.value(fakeDevices.map((FakeDeviceJsonData d) => d.dev).toList());
Future<List<Device>> getAllDevices({DeviceDiscoveryFilter? filter}) async {
final List<Device> devices = <Device>[];
for (final FakeDeviceJsonData deviceJson in fakeDevices) {
if (filter?.deviceConnectionInterface == null ||
deviceJson.dev.connectionInterface == filter?.deviceConnectionInterface) {
devices.add(deviceJson.dev);
}
}
return devices;
}
@override
Future<List<Device>> refreshAllDevices({
......
......@@ -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 = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[screenshotDevice];
await expectLater(() => createTestCommandRunner(command).run(
<String>[
......@@ -104,7 +103,7 @@ void main() {
fileSystem.directory('drive_screenshots').createSync();
final Device screenshotDevice = ThrowingScreenshotDevice();
fakeDeviceManager.devices = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[screenshotDevice];
await expectLater(() => createTestCommandRunner(command).run(
<String>[
......@@ -142,7 +141,7 @@ void main() {
fileSystem.directory('drive_screenshots').createSync();
final Device screenshotDevice = ScreenshotDevice();
fakeDeviceManager.devices = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[screenshotDevice];
await expectLater(() => createTestCommandRunner(command).run(
<String>[
......@@ -184,7 +183,7 @@ void main() {
fileSystem.file('drive_screenshots').createSync();
final Device screenshotDevice = ThrowingScreenshotDevice();
fakeDeviceManager.devices = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[screenshotDevice];
await expectLater(() => createTestCommandRunner(command).run(
<String>[
......@@ -222,7 +221,7 @@ void main() {
fileSystem.directory('drive_screenshots').createSync();
final ScreenshotDevice screenshotDevice = ScreenshotDevice();
fakeDeviceManager.devices = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[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 = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[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 = <Device>[networkDevice];
..connectionInterface = DeviceConnectionInterface.wireless;
fakeDeviceManager.wirelessDevices = <Device>[networkDevice];
await expectLater(() => createTestCommandRunner(command).run(<String>[
'drive',
......@@ -456,8 +455,8 @@ void main() {
]), throwsToolExit());
final Device usbDevice = FakeIosDevice()
..interfaceType = IOSDeviceConnectionInterface.usb;
fakeDeviceManager.devices = <Device>[usbDevice];
..connectionInterface = DeviceConnectionInterface.attached;
fakeDeviceManager.attachedDevices = <Device>[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 = <Device>[networkDevice];
..connectionInterface = DeviceConnectionInterface.wireless;
fakeDeviceManager.wirelessDevices = <Device>[networkDevice];
await expectLater(() => createTestCommandRunner(command).run(<String>[
'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<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
......
......@@ -36,7 +36,7 @@ void main() {
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final FakeAndroidDevice device = FakeAndroidDevice();
testDeviceManager.addDevice(device);
testDeviceManager.addAttachedDevice(device);
await createTestCommandRunner(command).run(<String>['install']);
}, overrides: <Type, Generator>{
......@@ -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(<String>['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(<String>['install']);
}, overrides: <Type, Generator>{
......@@ -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(<String>['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(<String>['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(<String>['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;
}
......@@ -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<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.usb, sdkNameAndVersion: 'iOS 16.2'),
FakeIOSDevice(sdkNameAndVersion: 'iOS 16.2'),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CommandRunner<void> runner = createTestCommandRunner(command);
......@@ -752,7 +751,10 @@ void main() {
testUsingContext('with only iOS network device', () async {
final List<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.network, sdkNameAndVersion: 'iOS 16.2'),
FakeIOSDevice(
connectionInterface: DeviceConnectionInterface.wireless,
sdkNameAndVersion: 'iOS 16.2',
),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CommandRunner<void> runner = createTestCommandRunner(command);
......@@ -793,8 +795,11 @@ void main() {
testUsingContext('with both iOS usb and network devices', () async {
final List<Device> devices = <Device>[
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<void> 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<String> get sdkNameAndVersion => Future<String>.value(_sdkNameAndVersion);
@override
final IOSDeviceConnectionInterface interfaceType;
final DeviceConnectionInterface connectionInterface;
@override
bool get isWirelesslyConnected =>
connectionInterface == DeviceConnectionInterface.wireless;
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
......
......@@ -922,8 +922,15 @@ class _FakeDeviceManager extends DeviceManager {
@override
Future<List<Device>> getAllDevices({
DeviceDiscoveryFilter? filter,
}) async => _connectedDevices;
}) async => filteredDevices(filter);
@override
List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
List<Device> filteredDevices(DeviceDiscoveryFilter? filter) {
if (filter?.deviceConnectionInterface == DeviceConnectionInterface.wireless) {
return <Device>[];
}
return _connectedDevices;
}
}
......@@ -87,6 +87,13 @@ class FakeDeviceManager extends Fake implements DeviceManager {
@override
String? specifiedDeviceId;
@override
Future<List<Device>> getAllDevices({
DeviceDiscoveryFilter? filter,
}) async {
return devices;
}
@override
Future<List<Device>> refreshAllDevices({
Duration? timeout,
......
......@@ -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(<FakeCommand>[
const FakeCommand(
command: <String>['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<Device> 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 {
......
......@@ -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,
<String, Object?>{},
interfaceType: IOSDeviceConnectionInterface.network,
interfaceType: DeviceConnectionInterface.wireless,
);
expect(
......@@ -856,7 +855,7 @@ void main() {
null,
<String, Object?>{},
ipv6: true,
interfaceType: IOSDeviceConnectionInterface.network,
interfaceType: DeviceConnectionInterface.wireless,
);
expect(
......
......@@ -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,
);
});
......
......@@ -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: <String>['--enable-dart-profiling'],
interfaceType: IOSDeviceConnectionInterface.network,
interfaceType: DeviceConnectionInterface.wireless,
uninstallFirst: true,
);
......
......@@ -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,
);
}
......@@ -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,
);
}
......@@ -337,7 +337,7 @@ IOSDevice setUpIOSDevice({
cache: cache,
),
cpuArchitecture: DarwinArch.arm64,
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
}
......
......@@ -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,
);
}
......
......@@ -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);
});
......
......@@ -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<IOSDevice> 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: <Type, Generator>{
Platform: () => macPlatform,
......
......@@ -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();
......
......@@ -4,42 +4,60 @@
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', () {
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(() {
platform = FakePlatform();
});
group('when cannot launch anything', () {
late BufferLogger logger;
late FakeDoctor doctor;
final FakeDevice device1 = FakeDevice('device1', 'device1');
setUp(() {
logger = BufferLogger.test();
doctor = FakeDoctor(logger, canLaunchAnything: false);
doctor = FakeDoctor(canLaunchAnything: false);
});
testUsingContext('does not search for devices', () async {
final MockDeviceDiscovery deviceDiscovery = MockDeviceDiscovery()
..deviceValues = <Device>[device1];
final DeviceManager deviceManager = TestDeviceManager(
<Device>[],
deviceDiscoveryOverrides: <DeviceDiscovery>[
deviceDiscovery,
],
final TestDeviceManager deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
......@@ -47,373 +65,1017 @@ void main() {
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.errorText, contains(UserMessages().flutterNoDevelopmentDevice));
expect(logger.errorText, equals('''
Unable to locate a development device; please run 'flutter doctor' for information about installing additional components.
'''));
expect(devices, isNull);
expect(deviceDiscovery.devicesCalled, 0);
expect(deviceDiscovery.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.devicesCalled, 0);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
}, overrides: <Type, Generator>{
Doctor: () => doctor,
});
});
group('Ensure refresh when deviceDiscoveryTimeout is provided', () {
testUsingContext('ensure refresh when deviceDiscoveryTimeout is provided', () async {
final BufferLogger logger = BufferLogger.test();
final TestDeviceManager deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1];
deviceManager.androidDiscoverer.refreshDeviceList = <Device>[attachedAndroidDevice1, wirelessAndroidDevice1];
deviceManager.hasSpecifiedAllDevices = true;
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices(
deviceDiscoveryTimeout: const Duration(seconds: 2),
);
expect(logger.statusText, equals(''));
expect(devices, <Device>[attachedAndroidDevice1, wirelessAndroidDevice1]);
expect(deviceManager.androidDiscoverer.devicesCalled, 2);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
group('finds no devices', () {
late BufferLogger logger;
final FakeDevice device1 = FakeDevice('device1', 'device1');
late TestDeviceManager deviceManager;
setUp(() {
logger = BufferLogger.test();
deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
});
group('when device not specified', () {
testUsingContext('when no devices', () async {
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? 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);
});
testUsingContext('does not refresh device cache without a timeout', () async {
final MockDeviceDiscovery deviceDiscovery = MockDeviceDiscovery()
..deviceValues = <Device>[device1];
testUsingContext('when device is unsupported by flutter or project', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[
attachedUnsupportedAndroidDevice,
attachedUnsupportedForProjectAndroidDevice,
];
final DeviceManager deviceManager = TestDeviceManager(
<Device>[],
deviceDiscoveryOverrides: <DeviceDiscovery>[
deviceDiscovery,
],
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
deviceManager.specifiedDeviceId = device1.id;
final List<Device>? 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);
});
});
group('with hasSpecifiedDeviceId', () {
setUp(() {
deviceManager.specifiedDeviceId = 'target-device';
});
testUsingContext('when no devices', () async {
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices?.single, device1);
expect(deviceDiscovery.devicesCalled, 1);
expect(deviceDiscovery.discoverDevicesCalled, 0);
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('refreshes device cache with a timeout', () async {
final MockDeviceDiscovery deviceDiscovery = MockDeviceDiscovery()
..deviceValues = <Device>[device1];
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 = <Device>[device1, device2];
final DeviceManager deviceManager = TestDeviceManager(
<Device>[],
deviceDiscoveryOverrides: <DeviceDiscovery>[
deviceDiscovery,
],
logger: BufferLogger.test(),
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
deviceManager.specifiedDeviceId = device1.id;
final List<Device>? devices = await targetDevices.findAllTargetDevices();
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 = <Device>[exactMatchattachedUnsupportedAndroidDevice];
const Duration timeout = Duration(seconds: 2);
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices(
deviceDiscoveryTimeout: timeout,
final List<Device>? 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<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices?.single, device1);
expect(deviceDiscovery.devicesCalled, 1);
expect(deviceDiscovery.discoverDevicesCalled, 1);
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 = <Device>[
attachedUnsupportedAndroidDevice,
attachedUnsupportedForProjectAndroidDevice,
];
deviceManager.otherDiscoverer.deviceList = <Device>[fuchsiaDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? 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('findAllTargetDevices', () {
});
});
group('finds single device', () {
late BufferLogger logger;
final FakeDevice device1 = FakeDevice('device1', 'device1');
final FakeDevice device2 = FakeDevice('device2', 'device2');
late TestDeviceManager deviceManager;
setUp(() {
logger = BufferLogger.test();
deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
});
group('when specified device id', () {
testUsingContext('returns device when device is found', () async {
testDeviceManager.specifiedDeviceId = 'device1';
testDeviceManager.addDevice(device1);
group('when device not specified', () {
testUsingContext('when single attached device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, <Device>[device1]);
expect(logger.statusText, equals(''));
expect(devices, <Device>[attachedAndroidDevice1]);
expect(deviceManager.androidDiscoverer.devicesCalled, 2);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
testUsingContext('show error when no device found', () async {
testDeviceManager.specifiedDeviceId = 'device-id';
testUsingContext('when single wireless device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[wirelessAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, null);
expect(logger.statusText, contains(UserMessages().flutterNoMatchingDevice('device-id')));
expect(logger.statusText, equals(''));
expect(devices, <Device>[wirelessAndroidDevice1]);
expect(deviceManager.androidDiscoverer.devicesCalled, 2);
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);
testUsingContext('when multiple but only one ephemeral', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[nonEphemeralDevice, wirelessAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, null);
expect(logger.statusText, contains(UserMessages().flutterFoundSpecifiedDevices(2, 'device')));
expect(logger.statusText, equals(''));
expect(devices, <Device>[wirelessAndroidDevice1]);
expect(deviceManager.androidDiscoverer.devicesCalled, 2);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
});
group('when specified all', () {
testUsingContext('can return one device', () async {
testDeviceManager.specifiedDeviceId = 'all';
testDeviceManager.addDevice(device1);
group('with hasSpecifiedDeviceId', () {
setUp(() {
deviceManager.specifiedDeviceId = 'target-device';
});
testUsingContext('when multiple matches but first is unsupported by flutter', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[
exactMatchattachedUnsupportedAndroidDevice,
exactMatchAndroidDevice,
];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, <Device>[device1]);
expect(logger.statusText, equals(''));
expect(devices, <Device>[exactMatchAndroidDevice]);
expect(deviceManager.androidDiscoverer.devicesCalled, 1);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
testUsingContext('can return multiple devices', () async {
testDeviceManager.specifiedDeviceId = 'all';
testDeviceManager.addDevice(device1);
testDeviceManager.addDevice(device2);
testUsingContext('when matching device is unsupported by project', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[exactMatchUnsupportedByProjectDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, <Device>[device1, device2]);
expect(logger.statusText, equals(''));
expect(devices, <Device>[exactMatchUnsupportedByProjectDevice]);
expect(deviceManager.androidDiscoverer.devicesCalled, 1);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
testUsingContext('show error when no device found', () async {
testDeviceManager.specifiedDeviceId = 'all';
testUsingContext('when matching attached device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[exactMatchAndroidDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, null);
expect(logger.statusText, contains(UserMessages().flutterNoDevicesFound));
});
expect(logger.statusText, equals(''));
expect(devices, <Device>[exactMatchAndroidDevice]);
expect(deviceManager.androidDiscoverer.devicesCalled, 1);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
group('when device not specified', () {
testUsingContext('returns one device when only one device connected', () async {
testDeviceManager.addDevice(device1);
testUsingContext('when matching wireless device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[exactMatchWirelessAndroidDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, <Device>[device1]);
expect(logger.statusText, equals(''));
expect(devices, <Device>[exactMatchWirelessAndroidDevice]);
expect(deviceManager.androidDiscoverer.devicesCalled, 1);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
testUsingContext('show error when no device found', () async {
testUsingContext('when exact match attached device and partial match wireless device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[exactMatchAndroidDevice, wirelessAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, null);
expect(logger.statusText, contains(UserMessages().flutterNoSupportedDevices));
expect(logger.statusText, equals(''));
expect(devices, <Device>[exactMatchAndroidDevice]);
expect(deviceManager.androidDiscoverer.devicesCalled, 1);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
});
group('with hasSpecifiedAllDevices', () {
setUp(() {
deviceManager.hasSpecifiedAllDevices = true;
});
testUsingContext('show error when multiple devices found and not connected to terminal', () async {
testDeviceManager.addDevice(device1);
testDeviceManager.addDevice(device2);
testUsingContext('when only one device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, null);
expect(logger.statusText, contains(UserMessages().flutterSpecifyDeviceWithAllOption));
}, overrides: <Type, Generator>{
AnsiTerminal: () => FakeTerminal(stdinHasTerminal: false),
expect(logger.statusText, equals(''));
expect(devices, <Device>[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,
);
});
// Prompt to choose device when multiple devices found and connected to terminal
group('show prompt', () {
group('when device not specified', () {
group('with stdinHasTerminal', () {
late FakeTerminal terminal;
setUp(() {
terminal = FakeTerminal();
});
testUsingContext('choose first device', () async {
testDeviceManager.addDevice(device1);
testDeviceManager.addDevice(device2);
testUsingContext('including attached, wireless, unsupported devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[
attachedAndroidDevice1,
attachedUnsupportedAndroidDevice,
attachedUnsupportedForProjectAndroidDevice,
wirelessAndroidDevice1,
wirelessUnsupportedAndroidDevice,
wirelessUnsupportedForProjectAndroidDevice,
];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '2');
final List<Device>? 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, <Device>[wirelessAndroidDevice1]);
expect(deviceManager.androidDiscoverer.devicesCalled, 2);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only attached devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1, attachedAndroidDevice2];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '1');
final List<Device>? 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, <Device>[attachedAndroidDevice1]);
expect(deviceManager.androidDiscoverer.devicesCalled, 2);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only wireless devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[wirelessAndroidDevice1, wirelessAndroidDevice2];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '1');
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, <Device>[device1]);
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, <Device>[wirelessAndroidDevice1]);
expect(deviceManager.androidDiscoverer.devicesCalled, 2);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('choose second device', () async {
testDeviceManager.addDevice(device1);
testDeviceManager.addDevice(device2);
terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '2');
});
group('without stdinHasTerminal', () {
late FakeTerminal terminal;
setUp(() {
terminal = FakeTerminal(stdinHasTerminal: false);
});
testUsingContext('including attached, wireless, unsupported devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[
attachedAndroidDevice1,
attachedUnsupportedAndroidDevice,
attachedUnsupportedForProjectAndroidDevice,
wirelessAndroidDevice1,
wirelessUnsupportedAndroidDevice,
wirelessUnsupportedForProjectAndroidDevice,
];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(devices, <Device>[device2]);
expect(logger.statusText, equals('''
More than one device connected; please specify a device with the '-d <deviceId>' 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: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('exits without choosing device', () async {
testDeviceManager.addDevice(device1);
testDeviceManager.addDevice(device2);
terminal.setPrompt(<String>['1', '2', 'q', 'Q'], 'q');
testUsingContext('including only attached devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1, attachedAndroidDevice2];
final TargetDevices targetDevices = TargetDevices(
deviceManager: testDeviceManager,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
More than one device connected; please specify a device with the '-d <deviceId>' 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: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only wireless devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[wirelessAndroidDevice1, wirelessAndroidDevice2];
await expectLater(
targetDevices.findAllTargetDevices(),
throwsToolExit(),
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
More than one device connected; please specify a device with the '-d <deviceId>' 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: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
});
group('with hasSpecifiedDeviceId', () {
setUp(() {
deviceManager.specifiedDeviceId = 'target-device';
});
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);
group('with stdinHasTerminal', () {
late FakeTerminal terminal;
setUp(() {
logger = BufferLogger.test();
terminal = FakeTerminal();
});
testUsingContext('chooses ephemeral device', () async {
final List<Device> devices = <Device>[
ephemeralOne,
nonEphemeralOne,
testUsingContext('including attached, wireless, unsupported devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[
attachedAndroidDevice1,
attachedUnsupportedAndroidDevice,
attachedUnsupportedForProjectAndroidDevice,
wirelessAndroidDevice1,
wirelessUnsupportedAndroidDevice,
wirelessUnsupportedForProjectAndroidDevice,
];
final DeviceManager deviceManager = TestDeviceManager(
devices,
terminal.setPrompt(<String>['1', '2', '3', '4', 'q', 'Q'], '2');
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? 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, <Device>[attachedUnsupportedForProjectAndroidDevice]);
expect(deviceManager.androidDiscoverer.devicesCalled, 3);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
final TargetDevices targetDevices = TargetDevices(deviceManager: deviceManager, logger: logger);
final List<Device> filtered = await targetDevices.getDevices();
testUsingContext('including only attached devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1, attachedAndroidDevice2];
expect(filtered, <Device>[ephemeralOne]);
terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '1');
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? 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, <Device>[attachedAndroidDevice1]);
expect(deviceManager.androidDiscoverer.devicesCalled, 3);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('returns all devices when multiple non ephemeral devices are found', () async {
final List<Device> devices = <Device>[
ephemeralOne,
ephemeralTwo,
nonEphemeralOne,
testUsingContext('including only wireless devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[wirelessAndroidDevice1, wirelessAndroidDevice2];
terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '1');
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? 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, <Device>[wirelessAndroidDevice1]);
expect(deviceManager.androidDiscoverer.devicesCalled, 3);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
group('without stdinHasTerminal', () {
late FakeTerminal terminal;
setUp(() {
terminal = FakeTerminal(stdinHasTerminal: false);
});
testUsingContext('including only one ephemeral', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[nonEphemeralDevice, attachedAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? 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: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including matching attached, wireless, unsupported devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[
attachedAndroidDevice1,
attachedUnsupportedAndroidDevice,
attachedUnsupportedForProjectAndroidDevice,
wirelessAndroidDevice1,
wirelessUnsupportedAndroidDevice,
wirelessUnsupportedForProjectAndroidDevice,
];
final DeviceManager deviceManager = TestDeviceManager(
devices,
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? 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: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only attached devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1, attachedAndroidDevice2];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? 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: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only wireless devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[wirelessAndroidDevice1, wirelessAndroidDevice2];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? 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: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
});
final TargetDevices targetDevices = TargetDevices(deviceManager: deviceManager, logger: logger);
final List<Device> filtered = await targetDevices.getDevices();
group('with hasSpecifiedAllDevices', () {
setUp(() {
deviceManager.hasSpecifiedAllDevices = true;
});
testUsingContext('including attached, wireless, unsupported devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[
attachedAndroidDevice1,
attachedUnsupportedAndroidDevice,
attachedUnsupportedForProjectAndroidDevice,
wirelessAndroidDevice1,
wirelessUnsupportedAndroidDevice,
wirelessUnsupportedForProjectAndroidDevice,
];
deviceManager.otherDiscoverer.deviceList = <Device>[fuchsiaDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(filtered, <Device>[
ephemeralOne,
ephemeralTwo,
nonEphemeralOne,
]);
expect(logger.statusText, equals(''));
expect(devices, <Device>[attachedAndroidDevice1, wirelessAndroidDevice1]);
expect(deviceManager.androidDiscoverer.devicesCalled, 2);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
});
});
});
}
class TestDeviceManager extends DeviceManager {
TestDeviceManager(
List<Device> allDevices, {
List<DeviceDiscovery>? deviceDiscoveryOverrides,
required super.logger,
String? wellKnownId,
FakePollingDeviceDiscovery? fakeDiscoverer,
}) : _fakeDeviceDiscoverer = fakeDiscoverer ?? FakePollingDeviceDiscovery(),
_deviceDiscoverers = <DeviceDiscovery>[],
super() {
if (wellKnownId != null) {
_fakeDeviceDiscoverer.wellKnownIds.add(wellKnownId);
}
_deviceDiscoverers.add(_fakeDeviceDiscoverer);
if (deviceDiscoveryOverrides != null) {
_deviceDiscoverers.addAll(deviceDiscoveryOverrides);
}
resetDevices(allDevices);
}
TestDeviceManager({
required this.logger,
required this.platform,
}) : super(logger: logger);
final Logger logger;
final Platform platform;
@override
String? specifiedDeviceId;
@override
List<DeviceDiscovery> get deviceDiscoverers => _deviceDiscoverers;
final List<DeviceDiscovery> _deviceDiscoverers;
final FakePollingDeviceDiscovery _fakeDeviceDiscoverer;
bool hasSpecifiedAllDevices = false;
void resetDevices(List<Device> allDevices) {
_fakeDeviceDiscoverer.setDevices(allDevices);
final TestPollingDeviceDiscovery androidDiscoverer = TestPollingDeviceDiscovery(
'android',
);
final TestPollingDeviceDiscovery otherDiscoverer = TestPollingDeviceDiscovery(
'other',
);
final TestPollingDeviceDiscovery iosDiscoverer = TestPollingDeviceDiscovery(
'ios',
);
@override
List<DeviceDiscovery> get deviceDiscoverers {
return <DeviceDiscovery>[
androidDiscoverer,
otherDiscoverer,
iosDiscoverer,
];
}
}
class MockDeviceDiscovery extends Fake implements DeviceDiscovery {
class TestPollingDeviceDiscovery extends PollingDeviceDiscovery {
TestPollingDeviceDiscovery(super.name);
List<Device> deviceList = <Device>[];
List<Device> refreshDeviceList = <Device>[];
int devicesCalled = 0;
int discoverDevicesCalled = 0;
int numberOfTimesPolled = 0;
@override
bool supportsPlatform = true;
bool get supportsPlatform => true;
List<Device> deviceValues = <Device>[];
@override
List<String> get wellKnownIds => const <String>[];
@override
Future<List<Device>> pollingGetDevices({Duration? timeout}) async {
numberOfTimesPolled++;
return deviceList;
}
@override
Future<List<Device>> devices({DeviceDiscoveryFilter? filter}) async {
devicesCalled += 1;
return deviceValues;
return super.devices(filter: filter);
}
@override
Future<List<Device>> 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<String> get wellKnownIds => <String>[];
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<TargetPlatform> get targetPlatform async => _targetPlatform;
@override
final PlatformType? platformType;
@override
Future<String> get sdkNameAndVersion async => _sdkNameAndVersion;
@override
Future<bool> get isLocalEmulator async => false;
@override
Category? get category => Category.mobile;
@override
Future<String> 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<DoctorValidator> get validators {
final List<DoctorValidator> superValidators = super.validators;
return superValidators.map<DoctorValidator>((DoctorValidator v) {
if (v is AndroidLicenseValidator) {
return FakeAndroidLicenseValidator();
}
return v;
}).toList();
}
}
......@@ -181,7 +181,8 @@ void _printBufferedErrors(AppContext testContext) {
}
class FakeDeviceManager implements DeviceManager {
List<Device> devices = <Device>[];
List<Device> attachedDevices = <Device>[];
List<Device> wirelessDevices = <Device>[];
String? _specifiedDeviceId;
......@@ -209,20 +210,20 @@ class FakeDeviceManager implements DeviceManager {
@override
Future<List<Device>> getAllDevices({
DeviceDiscoveryFilter? filter,
}) async => devices;
}) async => filteredDevices(filter);
@override
Future<List<Device>> refreshAllDevices({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) async => devices;
}) async => filteredDevices(filter);
@override
Future<List<Device>> 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<Device> devices) => null;
List<Device> 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 {
......
......@@ -54,6 +54,31 @@ List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[
},
},
),
FakeDeviceJsonData(
FakeDevice(
'wireless android',
'wireless-android',
type: PlatformType.android,
connectionInterface: DeviceConnectionInterface.wireless,
),
<String, Object>{
'name': 'wireless android',
'id': 'wireless-android',
'isSupported': true,
'targetPlatform': 'android-arm',
'emulator': true,
'sdk': 'Test SDK (1.2.3)',
'capabilities': <String, Object>{
'hotReload': true,
'hotRestart': true,
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': true,
'startPaused': true,
},
}
),
];
/// Fake device to test `devices` command.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment