Unverified Commit fa01649a authored by Victoria Ashworth's avatar Victoria Ashworth Committed by GitHub

Update device selection to wait for wireless devices to load (#122932)

Update device selection to wait for wireless devices to load
parent 820ec70a
......@@ -216,6 +216,7 @@ abstract class Logger {
VoidCallback? onFinish,
Duration? timeout,
SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
});
/// Send an event to be emitted.
......@@ -376,11 +377,13 @@ class DelegatingLogger implements Logger {
VoidCallback? onFinish,
Duration? timeout,
SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
}) {
return _delegate.startSpinner(
onFinish: onFinish,
timeout: timeout,
slowWarningCallback: slowWarningCallback,
warningColor: warningColor,
);
}
......@@ -587,6 +590,7 @@ class StdoutLogger extends Logger {
VoidCallback? onFinish,
Duration? timeout,
SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
}) {
if (_status != null || !supportsColor) {
return SilentStatus(
......@@ -606,6 +610,7 @@ class StdoutLogger extends Logger {
terminal: terminal,
timeout: timeout,
slowWarningCallback: slowWarningCallback,
warningColor: warningColor,
)..start();
return _status!;
}
......@@ -888,6 +893,7 @@ class BufferLogger extends Logger {
VoidCallback? onFinish,
Duration? timeout,
SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
}) {
return SilentStatus(
stopwatch: _stopwatchFactory.createStopwatch(),
......@@ -1269,6 +1275,7 @@ class AnonymousSpinnerStatus extends Status {
required Stdio stdio,
required Terminal terminal,
this.slowWarningCallback,
this.warningColor,
super.timeout,
}) : _stdio = stdio,
_terminal = terminal,
......@@ -1278,6 +1285,7 @@ class AnonymousSpinnerStatus extends Status {
final Terminal _terminal;
String _slowWarning = '';
final SlowWarningCallback? slowWarningCallback;
final TerminalColor? warningColor;
static const String _backspaceChar = '\b';
static const String _clearChar = ' ';
......@@ -1360,8 +1368,15 @@ class AnonymousSpinnerStatus extends Status {
_clear(_currentLineLength - _lastAnimationFrameLength);
}
}
if (_slowWarning == '' && slowWarningCallback != null) {
_slowWarning = slowWarningCallback!();
final SlowWarningCallback? callback = slowWarningCallback;
if (_slowWarning.isEmpty && callback != null) {
final TerminalColor? color = warningColor;
if (color != null) {
_slowWarning = _terminal.color(callback(), color);
} else {
_slowWarning = callback();
}
_writeToStdOut(_slowWarning);
}
}
......
......@@ -175,6 +175,15 @@ class AnsiTerminal implements Terminal {
static const String yellow = '\u001b[33m';
static const String grey = '\u001b[90m';
// Moves cursor up 1 line.
static const String cursorUpLineCode = '\u001b[1A';
// Moves cursor to the beginning of the line.
static const String cursorBeginningOfLineCode = '\u001b[1G';
// Clear the entire line, cursor position does not change.
static const String clearEntireLineCode = '\u001b[2K';
static const Map<TerminalColor, String> _colorMap = <TerminalColor, String>{
TerminalColor.red: red,
TerminalColor.green: green,
......@@ -268,6 +277,19 @@ class AnsiTerminal implements Terminal {
@override
String clearScreen() => supportsColor ? clear : '\n\n';
/// Returns ANSI codes to clear [numberOfLines] lines starting with the line
/// the cursor is on.
///
/// If the terminal does not support ANSI codes, returns an empty string.
String clearLines(int numberOfLines) {
if (!supportsColor) {
return '';
}
return cursorBeginningOfLineCode +
clearEntireLineCode +
(cursorUpLineCode + clearEntireLineCode) * (numberOfLines - 1);
}
@override
bool get singleCharMode {
if (!_stdio.stdinHasTerminal) {
......
......@@ -272,7 +272,6 @@ class UserMessages {
String get flutterFoundButUnsupportedDevices => 'The following devices were found, but are not supported by this project:';
String flutterFoundSpecifiedDevices(int count, String deviceId) =>
'Found $count devices with name or id matching $deviceId:';
String get flutterMultipleDevicesFound => 'Multiple devices found:';
String flutterChooseDevice(int option, String name, String deviceId) => '[$option]: $name ($deviceId)';
String get flutterChooseOne => 'Please choose one (or "q" to quit)';
String get flutterSpecifyDeviceWithAllOption =>
......
......@@ -175,6 +175,9 @@ known, it can be explicitly provided to attach via the command-line, e.g.
@override
final String category = FlutterCommandCategory.tools;
@override
bool get refreshWirelessDevices => true;
int? get debugPort {
if (argResults!['debug-port'] == null) {
return null;
......
......@@ -878,12 +878,12 @@ class DeviceDomain extends Domain {
final List<PollingDeviceDiscovery> _discoverers = <PollingDeviceDiscovery>[];
/// Return a list of the current devices, with each device represented as a map
/// of properties (id, name, platform, ...).
/// Return a list of the currently connected devices, with each device
/// represented as a map of properties (id, name, platform, ...).
Future<List<Map<String, Object?>>> getDevices([ Map<String, Object?>? args ]) async {
return <Map<String, Object?>>[
for (final PollingDeviceDiscovery discoverer in _discoverers)
for (final Device device in await discoverer.devices())
for (final Device device in await discoverer.devices(filter: DeviceDiscoveryFilter()))
await _deviceToMap(device),
];
}
......@@ -1066,10 +1066,12 @@ class DeviceDomain extends Domain {
return Future<void>.value();
}
/// Return the device matching the deviceId field in the args.
/// Return the connected device matching the deviceId field in the args.
Future<Device?> _getDevice(String? deviceId) async {
for (final PollingDeviceDiscovery discoverer in _discoverers) {
final List<Device> devices = await discoverer.devices();
final List<Device> devices = await discoverer.devices(
filter: DeviceDiscoveryFilter(),
);
Device? device;
for (final Device localDevice in devices) {
if (localDevice.id == deviceId) {
......
......@@ -3,6 +3,9 @@
// found in the LICENSE file.
import '../base/common.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/terminal.dart';
import '../base/utils.dart';
import '../convert.dart';
import '../device.dart';
......@@ -63,6 +66,9 @@ class DevicesCommand extends FlutterCommand {
}
final DevicesCommandOutput output = DevicesCommandOutput(
platform: globals.platform,
logger: globals.logger,
deviceManager: globals.deviceManager,
deviceDiscoveryTimeout: deviceDiscoveryTimeout,
);
......@@ -75,8 +81,35 @@ class DevicesCommand extends FlutterCommand {
}
class DevicesCommandOutput {
DevicesCommandOutput({this.deviceDiscoveryTimeout});
factory DevicesCommandOutput({
required Platform platform,
required Logger logger,
DeviceManager? deviceManager,
Duration? deviceDiscoveryTimeout,
}) {
if (platform.isMacOS) {
return DevicesCommandOutputWithExtendedWirelessDeviceDiscovery(
logger: logger,
deviceManager: deviceManager,
deviceDiscoveryTimeout: deviceDiscoveryTimeout,
);
}
return DevicesCommandOutput._private(
logger: logger,
deviceManager: deviceManager,
deviceDiscoveryTimeout: deviceDiscoveryTimeout,
);
}
DevicesCommandOutput._private({
required Logger logger,
required DeviceManager? deviceManager,
required this.deviceDiscoveryTimeout,
}) : _deviceManager = deviceManager,
_logger = logger;
final DeviceManager? _deviceManager;
final Logger _logger;
final Duration? deviceDiscoveryTimeout;
Future<List<Device>> _getAttachedDevices(DeviceManager deviceManager) async {
......@@ -98,7 +131,7 @@ class DevicesCommandOutput {
Future<void> findAndOutputAllTargetDevices({required bool machine}) async {
List<Device> attachedDevices = <Device>[];
List<Device> wirelessDevices = <Device>[];
final DeviceManager? deviceManager = globals.deviceManager;
final DeviceManager? deviceManager = _deviceManager;
if (deviceManager != null) {
// Refresh the cache and then get the attached and wireless devices from
// the cache.
......@@ -117,15 +150,15 @@ class DevicesCommandOutput {
_printNoDevicesDetected();
} else {
if (attachedDevices.isNotEmpty) {
globals.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n');
await Device.printDevices(attachedDevices, globals.logger);
_logger.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n');
await Device.printDevices(attachedDevices, _logger);
}
if (wirelessDevices.isNotEmpty) {
if (attachedDevices.isNotEmpty) {
globals.printStatus('');
_logger.printStatus('');
}
globals.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n');
await Device.printDevices(wirelessDevices, globals.logger);
_logger.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n');
await Device.printDevices(wirelessDevices, _logger);
}
}
await _printDiagnostics();
......@@ -143,24 +176,125 @@ class DevicesCommandOutput {
}
status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.');
globals.printStatus(status.toString());
_logger.printStatus(status.toString());
}
Future<void> _printDiagnostics() async {
final List<String> diagnostics = await globals.deviceManager?.getDeviceDiagnostics() ?? <String>[];
final List<String> diagnostics = await _deviceManager?.getDeviceDiagnostics() ?? <String>[];
if (diagnostics.isNotEmpty) {
globals.printStatus('');
_logger.printStatus('');
for (final String diagnostic in diagnostics) {
globals.printStatus('• $diagnostic', hangingIndent: 2);
_logger.printStatus('• $diagnostic', hangingIndent: 2);
}
}
}
Future<void> printDevicesAsJson(List<Device> devices) async {
globals.printStatus(
_logger.printStatus(
const JsonEncoder.withIndent(' ').convert(
await Future.wait(devices.map((Device d) => d.toJson()))
)
);
}
}
const String _checkingForWirelessDevicesMessage = 'Checking for wireless devices...';
const String _noAttachedCheckForWireless = 'No devices found yet. Checking for wireless devices...';
const String _noWirelessDevicesFoundMessage = 'No wireless devices were found.';
class DevicesCommandOutputWithExtendedWirelessDeviceDiscovery extends DevicesCommandOutput {
DevicesCommandOutputWithExtendedWirelessDeviceDiscovery({
required super.logger,
super.deviceManager,
super.deviceDiscoveryTimeout,
}) : super._private();
@override
Future<void> findAndOutputAllTargetDevices({required bool machine}) async {
// When a user defines the timeout, use the super function that does not do
// longer wireless device discovery.
if (deviceDiscoveryTimeout != null) {
return super.findAndOutputAllTargetDevices(machine: machine);
}
if (machine) {
final List<Device> devices = await _deviceManager?.refreshAllDevices(
filter: DeviceDiscoveryFilter(),
timeout: DeviceManager.minimumWirelessDeviceDiscoveryTimeout,
) ?? <Device>[];
await printDevicesAsJson(devices);
return;
}
final Future<void>? extendedWirelessDiscovery = _deviceManager?.refreshExtendedWirelessDeviceDiscoverers(
timeout: DeviceManager.minimumWirelessDeviceDiscoveryTimeout,
);
List<Device> attachedDevices = <Device>[];
final DeviceManager? deviceManager = _deviceManager;
if (deviceManager != null) {
attachedDevices = await _getAttachedDevices(deviceManager);
}
// Number of lines to clear starts at 1 because it's inclusive of the line
// the cursor is on, which will be blank for this use case.
int numLinesToClear = 1;
// Display list of attached devices.
if (attachedDevices.isNotEmpty) {
_logger.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n');
await Device.printDevices(attachedDevices, _logger);
_logger.printStatus('');
numLinesToClear += 1;
}
// Display waiting message.
if (attachedDevices.isEmpty) {
_logger.printStatus(_noAttachedCheckForWireless);
} else {
_logger.printStatus(_checkingForWirelessDevicesMessage);
}
numLinesToClear += 1;
final Status waitingStatus = _logger.startSpinner();
await extendedWirelessDiscovery;
List<Device> wirelessDevices = <Device>[];
if (deviceManager != null) {
wirelessDevices = await _getWirelessDevices(deviceManager);
}
waitingStatus.stop();
final Terminal terminal = _logger.terminal;
if (_logger.isVerbose) {
// Reprint the attach devices.
if (attachedDevices.isNotEmpty) {
_logger.printStatus('\n${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n');
await Device.printDevices(attachedDevices, _logger);
}
} else if (terminal.supportsColor && terminal is AnsiTerminal) {
_logger.printStatus(
terminal.clearLines(numLinesToClear),
newline: false,
);
}
if (attachedDevices.isNotEmpty || !_logger.terminal.supportsColor) {
_logger.printStatus('');
}
if (wirelessDevices.isEmpty) {
if (attachedDevices.isEmpty) {
// No wireless or attached devices were found.
_printNoDevicesDetected();
} else {
// Attached devices found, wireless devices not found.
_logger.printStatus(_noWirelessDevicesFoundMessage);
}
} else {
// Display list of wireless devices.
_logger.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n');
await Device.printDevices(wirelessDevices, _logger);
}
await _printDiagnostics();
}
}
......@@ -35,6 +35,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
@override
final String category = FlutterCommandCategory.tools;
@override
bool get refreshWirelessDevices => true;
Device? device;
bool get uninstallOnly => boolArg('uninstall-only');
......
......@@ -30,6 +30,9 @@ class LogsCommand extends FlutterCommand {
@override
final String category = FlutterCommandCategory.tools;
@override
bool get refreshWirelessDevices => true;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
......
......@@ -199,6 +199,9 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
bool get uninstallFirst => boolArg('uninstall-first');
bool get enableEmbedderApi => boolArg('enable-embedder-api');
@override
bool get refreshWirelessDevices => true;
@override
bool get reportNullSafety => true;
......
......@@ -65,6 +65,9 @@ class ScreenshotCommand extends FlutterCommand {
@override
final String category = FlutterCommandCategory.tools;
@override
bool get refreshWirelessDevices => true;
@override
final List<String> aliases = <String>['pic'];
......
......@@ -102,6 +102,11 @@ abstract class DeviceManager {
_specifiedDeviceId = id;
}
/// A minimum duration to use when discovering wireless iOS devices.
static const Duration minimumWirelessDeviceDiscoveryTimeout = Duration(
seconds: 5,
);
/// True when the user has specified a single specific device.
bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
......@@ -231,6 +236,22 @@ abstract class DeviceManager {
return devices.expand<Device>((List<Device> deviceList) => deviceList).toList();
}
/// Discard existing cache of discoverers that are known to take longer to
/// discover wireless devices.
///
/// Then, search for devices for those discoverers to populate the cache for
/// no longer than [timeout].
Future<void> refreshExtendedWirelessDeviceDiscoverers({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) async {
await Future.wait<List<Device>>(<Future<List<Device>>>[
for (final DeviceDiscovery discoverer in _platformDiscoverers)
if (discoverer.requiresExtendedWirelessDeviceDiscovery)
discoverer.discoverDevices(timeout: timeout)
]);
}
/// Whether we're capable of listing any devices given the current environment configuration.
bool get canListAnything {
return _platformDiscoverers.any((DeviceDiscovery discoverer) => discoverer.canListAnything);
......@@ -434,6 +455,10 @@ abstract class DeviceDiscovery {
/// current environment configuration.
bool get canListAnything;
/// Whether this device discovery is known to take longer to discover
/// wireless devices.
bool get requiresExtendedWirelessDeviceDiscovery => false;
/// Return all connected devices, cached on subsequent calls.
Future<List<Device>> devices({DeviceDiscoveryFilter? filter});
......@@ -504,6 +529,8 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
/// Get devices from cache filtered by [filter].
///
/// If the cache is empty, populate the cache.
///
/// If [filter] is null, it may return devices that are not connected.
@override
Future<List<Device>> devices({DeviceDiscoveryFilter? filter}) {
return _populateDevices(filter: filter);
......@@ -512,6 +539,8 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
/// Empty the cache and repopulate it before getting devices from cache filtered by [filter].
///
/// Search for devices to populate the cache for no longer than [timeout].
///
/// If [filter] is null, it may return devices that are not connected.
@override
Future<List<Device>> discoverDevices({
Duration? timeout,
......
......@@ -680,7 +680,9 @@ class DeviceValidator extends DoctorValidator {
@override
Future<ValidationResult> validate() async {
final List<Device> devices = await _deviceManager.getAllDevices();
final List<Device> devices = await _deviceManager.refreshAllDevices(
timeout: DeviceManager.minimumWirelessDeviceDiscoveryTimeout,
);
List<ValidationMessage> installedMessages = <ValidationMessage>[];
if (devices.isNotEmpty) {
installedMessages = (await Device.descriptions(devices))
......
......@@ -35,26 +35,30 @@ import 'mac.dart';
class IOSDevices extends PollingDeviceDiscovery {
IOSDevices({
required Platform platform,
required XCDevice xcdevice,
required this.xcdevice,
required IOSWorkflow iosWorkflow,
required Logger logger,
}) : _platform = platform,
_xcdevice = xcdevice,
_iosWorkflow = iosWorkflow,
_logger = logger,
super('iOS devices');
final Platform _platform;
final XCDevice _xcdevice;
final IOSWorkflow _iosWorkflow;
final Logger _logger;
@visibleForTesting
final XCDevice xcdevice;
@override
bool get supportsPlatform => _platform.isMacOS;
@override
bool get canListAnything => _iosWorkflow.canListDevices;
@override
bool get requiresExtendedWirelessDeviceDiscovery => true;
StreamSubscription<Map<XCDeviceEvent, String>>? _observedDeviceEventsSubscription;
@override
......@@ -64,18 +68,22 @@ class IOSDevices extends PollingDeviceDiscovery {
'Control of iOS devices or simulators only supported on macOS.'
);
}
if (!_xcdevice.isInstalled) {
if (!xcdevice.isInstalled) {
return;
}
deviceNotifier ??= ItemListNotifier<Device>();
// Start by populating all currently attached devices.
deviceNotifier!.updateWithNewList(await pollingGetDevices());
final List<Device> devices = await pollingGetDevices();
// Only show connected devices.
final List<Device> filteredDevices = devices.where((Device device) => device.isConnected == true).toList();
deviceNotifier!.updateWithNewList(filteredDevices);
// cancel any outstanding subscriptions.
await _observedDeviceEventsSubscription?.cancel();
_observedDeviceEventsSubscription = _xcdevice.observedDeviceEvents()?.listen(
_observedDeviceEventsSubscription = xcdevice.observedDeviceEvents()?.listen(
_onDeviceEvent,
onError: (Object error, StackTrace stack) {
_logger.printTrace('Process exception running xcdevice observe:\n$error\n$stack');
......@@ -109,7 +117,10 @@ class IOSDevices extends PollingDeviceDiscovery {
// There's no way to get details for an individual attached device,
// so repopulate them all.
final List<Device> devices = await pollingGetDevices();
notifier.updateWithNewList(devices);
// Only show connected devices.
final List<Device> filteredDevices = devices.where((Device device) => device.isConnected == true).toList();
notifier.updateWithNewList(filteredDevices);
} else if (eventType == XCDeviceEvent.detach && knownDevice != null) {
notifier.removeItem(knownDevice);
}
......@@ -128,7 +139,26 @@ class IOSDevices extends PollingDeviceDiscovery {
);
}
return _xcdevice.getAvailableIOSDevices(timeout: timeout);
return xcdevice.getAvailableIOSDevices(timeout: timeout);
}
Future<Device?> waitForDeviceToConnect(
IOSDevice device,
Logger logger,
) async {
final XCDeviceEventNotification? eventDetails =
await xcdevice.waitForDeviceToConnect(device.id);
if (eventDetails != null) {
device.isConnected = true;
device.connectionInterface = eventDetails.eventInterface.connectionInterface;
return device;
}
return null;
}
void cancelWaitForDeviceToConnect() {
xcdevice.cancelWaitForDeviceToConnect();
}
@override
......@@ -139,7 +169,7 @@ class IOSDevices extends PollingDeviceDiscovery {
];
}
return _xcdevice.getDiagnostics();
return xcdevice.getDiagnostics();
}
@override
......@@ -152,6 +182,7 @@ class IOSDevice extends Device {
required this.name,
required this.cpuArchitecture,
required this.connectionInterface,
required this.isConnected,
String? sdkVersion,
required Platform platform,
required IOSDeploy iosDeploy,
......@@ -200,7 +231,15 @@ class IOSDevice extends Device {
final DarwinArch cpuArchitecture;
@override
final DeviceConnectionInterface connectionInterface;
/// The [connectionInterface] provided from `XCDevice.getAvailableIOSDevices`
/// may not be accurate. Sometimes if it doesn't have a long enough time
/// to connect, wireless devices will have an interface of `usb`/`attached`.
/// This may change after waiting for the device to connect in
/// `waitForDeviceToConnect`.
DeviceConnectionInterface connectionInterface;
@override
bool isConnected;
final Map<IOSApp?, DeviceLogReader> _logReaders = <IOSApp?, DeviceLogReader>{};
......
......@@ -4,6 +4,7 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import '../artifacts.dart';
......@@ -23,11 +24,36 @@ import '../ios/mac.dart';
import '../reporting/reporting.dart';
import 'xcode.dart';
class XCDeviceEventNotification {
XCDeviceEventNotification(
this.eventType,
this.eventInterface,
this.deviceIdentifier,
);
final XCDeviceEvent eventType;
final XCDeviceEventInterface eventInterface;
final String deviceIdentifier;
}
enum XCDeviceEvent {
attach,
detach,
}
enum XCDeviceEventInterface {
usb(name: 'usb', connectionInterface: DeviceConnectionInterface.attached),
wifi(name: 'wifi', connectionInterface: DeviceConnectionInterface.wireless);
const XCDeviceEventInterface({
required this.name,
required this.connectionInterface,
});
final String name;
final DeviceConnectionInterface connectionInterface;
}
/// A utility class for interacting with Xcode xcdevice command line tools.
class XCDevice {
XCDevice({
......@@ -61,6 +87,8 @@ class XCDevice {
void dispose() {
_deviceObservationProcess?.kill();
_usbDeviceWaitProcess?.kill();
_wifiDeviceWaitProcess?.kill();
}
final ProcessUtils _processUtils;
......@@ -74,6 +102,12 @@ class XCDevice {
Process? _deviceObservationProcess;
StreamController<Map<XCDeviceEvent, String>>? _deviceIdentifierByEvent;
@visibleForTesting
StreamController<XCDeviceEventNotification>? waitStreamController;
Process? _usbDeviceWaitProcess;
Process? _wifiDeviceWaitProcess;
void _setupDeviceIdentifierByEventStream() {
// _deviceIdentifierByEvent Should always be available for listeners
// in case polling needs to be stopped and restarted.
......@@ -172,28 +206,15 @@ class XCDevice {
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
// xcdevice observe example output of UDIDs:
//
// Listening for all devices, on both interfaces.
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
// Attach: 00008027-00192736010F802E
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
final RegExpMatch? match = _observationIdentifierPattern.firstMatch(line);
if (match != null && match.groupCount == 2) {
final String verb = match.group(1)!.toLowerCase();
final String identifier = match.group(2)!;
if (verb.startsWith('attach')) {
_deviceIdentifierByEvent?.add(<XCDeviceEvent, String>{
XCDeviceEvent.attach: identifier,
});
} else if (verb.startsWith('detach')) {
final XCDeviceEventNotification? event = _processXCDeviceStdOut(
line,
XCDeviceEventInterface.usb,
);
if (event != null) {
_deviceIdentifierByEvent?.add(<XCDeviceEvent, String>{
XCDeviceEvent.detach: identifier,
event.eventType: event.deviceIdentifier,
});
}
}
});
final StreamSubscription<String> stderrSubscription = _deviceObservationProcess!.stderr
.transform<String>(utf8.decoder)
......@@ -222,10 +243,183 @@ class XCDevice {
}
}
XCDeviceEventNotification? _processXCDeviceStdOut(
String line,
XCDeviceEventInterface eventInterface,
) {
// xcdevice observe example output of UDIDs:
//
// Listening for all devices, on both interfaces.
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
// Attach: 00008027-00192736010F802E
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
final RegExpMatch? match = _observationIdentifierPattern.firstMatch(line);
if (match != null && match.groupCount == 2) {
final String verb = match.group(1)!.toLowerCase();
final String identifier = match.group(2)!;
if (verb.startsWith('attach')) {
return XCDeviceEventNotification(
XCDeviceEvent.attach,
eventInterface,
identifier,
);
} else if (verb.startsWith('detach')) {
return XCDeviceEventNotification(
XCDeviceEvent.detach,
eventInterface,
identifier,
);
}
}
return null;
}
void _stopObservingTetheredIOSDevices() {
_deviceObservationProcess?.kill();
}
/// Wait for a connect event for a specific device. Must use device's exact UDID.
///
/// To cancel this process, call [cancelWaitForDeviceToConnect].
Future<XCDeviceEventNotification?> waitForDeviceToConnect(
String deviceId,
) async {
try {
if (_usbDeviceWaitProcess != null || _wifiDeviceWaitProcess != null) {
throw Exception('xcdevice wait restart failed');
}
waitStreamController = StreamController<XCDeviceEventNotification>();
// Run in interactive mode (via script) to convince
// xcdevice it has a terminal attached in order to redirect stdout.
_usbDeviceWaitProcess = await _processUtils.start(
<String>[
'script',
'-t',
'0',
'/dev/null',
..._xcode.xcrunCommand(),
'xcdevice',
'wait',
'--${XCDeviceEventInterface.usb.name}',
deviceId,
],
);
_wifiDeviceWaitProcess = await _processUtils.start(
<String>[
'script',
'-t',
'0',
'/dev/null',
..._xcode.xcrunCommand(),
'xcdevice',
'wait',
'--${XCDeviceEventInterface.wifi.name}',
deviceId,
],
);
final StreamSubscription<String> usbStdoutSubscription = _processWaitStdOut(
_usbDeviceWaitProcess!,
XCDeviceEventInterface.usb,
);
final StreamSubscription<String> wifiStdoutSubscription = _processWaitStdOut(
_wifiDeviceWaitProcess!,
XCDeviceEventInterface.wifi,
);
final StreamSubscription<String> usbStderrSubscription = _usbDeviceWaitProcess!.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
_logger.printTrace('xcdevice wait --usb error: $line');
});
final StreamSubscription<String> wifiStderrSubscription = _wifiDeviceWaitProcess!.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
_logger.printTrace('xcdevice wait --wifi error: $line');
});
final Future<void> usbProcessExited = _usbDeviceWaitProcess!.exitCode.then((int status) {
_logger.printTrace('xcdevice wait --usb exited with code $exitCode');
// Kill other process in case only one was killed.
_wifiDeviceWaitProcess?.kill();
unawaited(usbStdoutSubscription.cancel());
unawaited(usbStderrSubscription.cancel());
});
final Future<void> wifiProcessExited = _wifiDeviceWaitProcess!.exitCode.then((int status) {
_logger.printTrace('xcdevice wait --wifi exited with code $exitCode');
// Kill other process in case only one was killed.
_usbDeviceWaitProcess?.kill();
unawaited(wifiStdoutSubscription.cancel());
unawaited(wifiStderrSubscription.cancel());
});
final Future<void> allProcessesExited = Future.wait(
<Future<void>>[
usbProcessExited,
wifiProcessExited,
]).whenComplete(() async {
_usbDeviceWaitProcess = null;
_wifiDeviceWaitProcess = null;
await waitStreamController?.close();
});
return await Future.any(
<Future<XCDeviceEventNotification?>>[
allProcessesExited.then((_) => null),
waitStreamController!.stream.first.whenComplete(() async {
cancelWaitForDeviceToConnect();
}),
],
);
} on ProcessException catch (exception, stackTrace) {
_logger.printTrace('Process exception running xcdevice wait:\n$exception\n$stackTrace');
} on ArgumentError catch (exception, stackTrace) {
_logger.printTrace('Process exception running xcdevice wait:\n$exception\n$stackTrace');
} on StateError {
_logger.printTrace('Stream broke before first was found');
return null;
}
return null;
}
StreamSubscription<String> _processWaitStdOut(
Process process,
XCDeviceEventInterface eventInterface,
) {
return process.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
final XCDeviceEventNotification? event = _processXCDeviceStdOut(
line,
eventInterface,
);
if (event != null && event.eventType == XCDeviceEvent.attach) {
waitStreamController?.add(event);
}
});
}
void cancelWaitForDeviceToConnect() {
_usbDeviceWaitProcess?.kill();
_wifiDeviceWaitProcess?.kill();
}
/// A list of [IOSDevice]s. This list includes connected devices and
/// disconnected wireless devices.
///
/// Sometimes devices may have incorrect connection information
/// (`isConnected`, `connectionInterface`) if it timed out before it could get the
/// information. Wireless devices can take longer to get the correct
/// information.
///
/// [timeout] defaults to 2 seconds.
Future<List<IOSDevice>> getAvailableIOSDevices({ Duration? timeout }) async {
final List<Object>? allAvailableDevices = await _getAllDevices(timeout: timeout ?? const Duration(seconds: 2));
......@@ -284,6 +478,7 @@ class XCDevice {
continue;
}
bool isConnected = true;
final Map<String, Object?>? errorProperties = _errorProperties(device);
if (errorProperties != null) {
final String? errorMessage = _parseErrorMessage(errorProperties);
......@@ -300,7 +495,7 @@ class XCDevice {
// Sometimes the app launch will fail on these devices until Xcode is done setting up the device.
// Other times this is a false positive and the app will successfully launch despite the error.
if (code != -10) {
continue;
isConnected = false;
}
}
......@@ -318,6 +513,7 @@ class XCDevice {
name: name,
cpuArchitecture: _cpuArchitecture(device),
connectionInterface: _interfaceType(device),
isConnected: isConnected,
sdkVersion: sdkVersion,
iProxy: _iProxy,
fileSystem: globals.fs,
......@@ -329,7 +525,6 @@ class XCDevice {
}
}
return devices;
}
/// Despite the name, com.apple.platform.iphoneos includes iPhone, iPads, and all iOS devices.
......
......@@ -210,6 +210,11 @@ abstract class FlutterCommand extends Command<void> {
bool get deprecated => false;
/// When the command runs and this is true, trigger an async process to
/// discover devices from discoverers that support wireless devices for an
/// extended amount of time and refresh the device cache with the results.
bool get refreshWirelessDevices => false;
@override
bool get hidden => deprecated;
......@@ -719,6 +724,7 @@ abstract class FlutterCommand extends Command<void> {
}();
late final TargetDevices _targetDevices = TargetDevices(
platform: globals.platform,
deviceManager: globals.deviceManager!,
logger: globals.logger,
);
......@@ -1466,6 +1472,14 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
}
globals.preRunValidator.validate();
if (refreshWirelessDevices) {
// Loading wireless devices takes longer so start it early.
_targetDevices.startExtendedWirelessDeviceDiscovery(
deviceDiscoveryTimeout: deviceDiscoveryTimeout,
);
}
// Populate the cache. We call this before pub get below so that the
// sky_engine package is available in the flutter cache for pub to find.
if (shouldUpdateCache) {
......@@ -1656,14 +1670,17 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
}
/// A mixin which applies an implementation of [requiredArtifacts] that only
/// downloads artifacts corresponding to an attached device.
/// downloads artifacts corresponding to potentially connected devices.
mixin DeviceBasedDevelopmentArtifacts on FlutterCommand {
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async {
// If there are no attached devices, use the default configuration.
// Otherwise, only add development artifacts which correspond to a
// connected device.
final List<Device> devices = await globals.deviceManager!.getDevices();
// If there are no devices, use the default configuration.
// Otherwise, only add development artifacts corresponding to
// potentially connected devices. We might not be able to determine if a
// device is connected yet, so include it in case it becomes connected.
final List<Device> devices = await globals.deviceManager!.getDevices(
filter: DeviceDiscoveryFilter(excludeDisconnected: false),
);
if (devices.isEmpty) {
return super.requiredArtifacts;
}
......
......@@ -2,20 +2,49 @@
// 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/platform.dart';
import '../base/terminal.dart';
import '../base/user_messages.dart';
import '../device.dart';
import '../globals.dart' as globals;
import '../ios/devices.dart';
const String _checkingForWirelessDevicesMessage = 'Checking for wireless devices...';
const String _connectedDevicesMessage = 'Connected devices:';
const String _noAttachedCheckForWireless = 'No devices found yet. Checking for wireless devices...';
const String _noWirelessDevicesFoundMessage = 'No wireless devices were found.';
const String _wirelesslyConnectedDevicesMessage = 'Wirelessly connected devices:';
String _foundMultipleSpecifiedDevices(String deviceId) =>
'Found multiple devices with name or id matching $deviceId:';
/// 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({
factory TargetDevices({
required Platform platform,
required DeviceManager deviceManager,
required Logger logger,
}) {
if (platform.isMacOS) {
return TargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
}
return TargetDevices._private(
deviceManager: deviceManager,
logger: logger,
);
}
TargetDevices._private({
required DeviceManager deviceManager,
required Logger logger,
}) : _deviceManager = deviceManager,
......@@ -48,9 +77,11 @@ class TargetDevices {
Future<List<Device>> _getDeviceById({
bool includeDevicesUnsupportedByProject = false,
bool includeDisconnected = false,
}) async {
return _deviceManager.getDevices(
filter: DeviceDiscoveryFilter(
excludeDisconnected: !includeDisconnected,
supportFilter: _deviceManager.deviceSupportFilter(
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
),
......@@ -66,6 +97,10 @@ class TargetDevices {
);
}
void startExtendedWirelessDeviceDiscovery({
Duration? deviceDiscoveryTimeout,
}) {}
/// Find and return all target [Device]s based upon criteria entered by the
/// user on the command line.
///
......@@ -235,7 +270,7 @@ class TargetDevices {
_deviceManager.specifiedDeviceId!,
));
} else {
_logger.printStatus(userMessages.flutterMultipleDevicesFound);
_logger.printStatus(_connectedDevicesMessage);
}
await Device.printDevices(attachedDevices, _logger);
......@@ -249,7 +284,8 @@ class TargetDevices {
final Device chosenDevice = await _chooseOneOfAvailableDevices(allDevices);
// Update the [DeviceManager.specifiedDeviceId] so that the user will not be prompted again.
// Update the [DeviceManager.specifiedDeviceId] so that the user will not
// be prompted again.
_deviceManager.specifiedDeviceId = chosenDevice.id;
return <Device>[chosenDevice];
......@@ -302,3 +338,406 @@ class TargetDevices {
return result;
}
}
@visibleForTesting
class TargetDevicesWithExtendedWirelessDeviceDiscovery extends TargetDevices {
TargetDevicesWithExtendedWirelessDeviceDiscovery({
required super.deviceManager,
required super.logger,
}) : super._private();
Future<void>? _wirelessDevicesRefresh;
@visibleForTesting
bool waitForWirelessBeforeInput = false;
@visibleForTesting
late final TargetDeviceSelection deviceSelection = TargetDeviceSelection(_logger);
@override
void startExtendedWirelessDeviceDiscovery({
Duration? deviceDiscoveryTimeout,
}) {
if (deviceDiscoveryTimeout == null) {
_wirelessDevicesRefresh ??= _deviceManager.refreshExtendedWirelessDeviceDiscoverers(
timeout: DeviceManager.minimumWirelessDeviceDiscoveryTimeout,
);
}
return;
}
Future<List<Device>> _getRefreshedWirelessDevices({
bool includeDevicesUnsupportedByProject = false,
}) async {
startExtendedWirelessDeviceDiscovery();
return () async {
await _wirelessDevicesRefresh;
return _deviceManager.getDevices(
filter: DeviceDiscoveryFilter(
deviceConnectionInterface: DeviceConnectionInterface.wireless,
supportFilter: _defaultSupportFilter(includeDevicesUnsupportedByProject),
),
);
}();
}
Future<Device?> _waitForIOSDeviceToConnect(IOSDevice device) async {
for (final DeviceDiscovery discoverer in _deviceManager.deviceDiscoverers) {
if (discoverer is IOSDevices) {
_logger.printStatus('Waiting for ${device.name} to connect...');
final Status waitingStatus = _logger.startSpinner(
timeout: const Duration(seconds: 30),
warningColor: TerminalColor.red,
slowWarningCallback: () {
return 'The device was unable to connect after 30 seconds. Ensure the device is paired and unlocked.';
},
);
final Device? connectedDevice = await discoverer.waitForDeviceToConnect(device, _logger);
waitingStatus.stop();
return connectedDevice;
}
}
return null;
}
/// 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 and
/// the device is connected, return it immediately. If an exact match or a
/// single partial match is found and the device is not connected and it's
/// an iOS device, wait for it to connect.
///
/// 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.
@override
Future<List<Device>?> findAllTargetDevices({
Duration? deviceDiscoveryTimeout,
bool includeDevicesUnsupportedByProject = false,
}) async {
if (!globals.doctor!.canLaunchAnything) {
_logger.printError(userMessages.flutterNoDevelopmentDevice);
return null;
}
// When a user defines the timeout, use the super function that does not do
// longer wireless device discovery and does not wait for devices to connect.
if (deviceDiscoveryTimeout != null) {
return super.findAllTargetDevices(
deviceDiscoveryTimeout: deviceDiscoveryTimeout,
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
);
}
// Start polling for wireless devices that need longer to load if it hasn't
// already been started.
startExtendedWirelessDeviceDiscovery();
if (_deviceManager.hasSpecifiedDeviceId) {
// Get devices matching the specified device regardless of whether they
// are currently connected or not.
// If there is a single matching connected device, return it immediately.
// If the only device found is an iOS device that is not connected yet,
// wait for it to connect.
// If there are multiple matches, continue on to wait for all attached
// and wireless devices to load so the user can select between all
// connected matches.
final List<Device> devices = await _getDeviceById(
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
includeDisconnected: true,
);
if (devices.length == 1) {
Device? matchedDevice = devices.first;
if (!matchedDevice.isConnected && matchedDevice is IOSDevice) {
matchedDevice = await _waitForIOSDeviceToConnect(matchedDevice);
}
if (matchedDevice != null && matchedDevice.isConnected) {
return <Device>[matchedDevice];
}
}
}
final List<Device> attachedDevices = await _getAttachedDevices(
supportFilter: _defaultSupportFilter(includeDevicesUnsupportedByProject),
);
// _getRefreshedWirelessDevices must be run after _getAttachedDevices is
// finished to prevent non-iOS discoverers from running simultaneously.
// `AndroidDevices` may error if run simultaneously.
final Future<List<Device>> futureWirelessDevices = _getRefreshedWirelessDevices(
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
);
if (attachedDevices.isEmpty) {
return _handleNoAttachedDevices(attachedDevices, futureWirelessDevices);
} else if (_deviceManager.hasSpecifiedAllDevices) {
return _handleAllDevices(attachedDevices, futureWirelessDevices);
}
// Even if there's only a single attached device, continue to
// `_handleRemainingDevices` since there might be wireless devices
// that are not loaded yet.
return _handleRemainingDevices(attachedDevices, futureWirelessDevices);
}
/// When no supported attached devices are found, wait for wireless devices
/// to load.
///
/// If no wireless devices are found, continue to `_handleNoDevices`.
///
/// If wireless devices are found, continue to `_handleMultipleDevices`.
Future<List<Device>?> _handleNoAttachedDevices(
List<Device> attachedDevices,
Future<List<Device>> futureWirelessDevices,
) async {
_logger.printStatus(_noAttachedCheckForWireless);
final List<Device> wirelessDevices = await futureWirelessDevices;
final List<Device> allDevices = attachedDevices + wirelessDevices;
if (allDevices.isEmpty) {
_logger.printStatus('');
return _handleNoDevices();
} else if (_deviceManager.hasSpecifiedAllDevices) {
return allDevices;
} else if (allDevices.length > 1) {
_logger.printStatus('');
return _handleMultipleDevices(attachedDevices, wirelessDevices);
}
return allDevices;
}
/// Wait for wireless devices to load and then return all attached and
/// wireless devices.
Future<List<Device>?> _handleAllDevices(
List<Device> devices,
Future<List<Device>> futureWirelessDevices,
) async {
_logger.printStatus(_checkingForWirelessDevicesMessage);
final List<Device> wirelessDevices = await futureWirelessDevices;
return devices + wirelessDevices;
}
/// Determine which device to use when one or more are 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>?> _handleRemainingDevices(
List<Device> attachedDevices,
Future<List<Device>> futureWirelessDevices,
) async {
final Device? ephemeralDevice = _deviceManager.getSingleEphemeralDevice(attachedDevices);
if (ephemeralDevice != null) {
return <Device>[ephemeralDevice];
}
if (!globals.terminal.stdinHasTerminal || !_logger.supportsColor) {
_logger.printStatus(_checkingForWirelessDevicesMessage);
final List<Device> wirelessDevices = await futureWirelessDevices;
if (attachedDevices.length + wirelessDevices.length == 1) {
return attachedDevices + wirelessDevices;
}
_logger.printStatus('');
// If the terminal has stdin but does not support color/ANSI (which is
// needed to clear lines), fallback to standard selection of device.
if (globals.terminal.stdinHasTerminal && !_logger.supportsColor) {
return _handleMultipleDevices(attachedDevices, wirelessDevices);
}
// If terminal does not have stdin, print out device list.
return _printMultipleDevices(attachedDevices, wirelessDevices);
}
return _selectFromDevicesAndCheckForWireless(
attachedDevices,
futureWirelessDevices,
);
}
/// Display a list of selectable attached devices and prompt the user to
/// choose one.
///
/// Also, display a message about waiting for wireless devices to load. Once
/// wireless devices have loaded, update waiting message, device list, and
/// selection options.
///
/// Wait for the user to select a device.
Future<List<Device>?> _selectFromDevicesAndCheckForWireless(
List<Device> attachedDevices,
Future<List<Device>> futureWirelessDevices,
) async {
if (attachedDevices.length == 1 || !_deviceManager.hasSpecifiedDeviceId) {
_logger.printStatus(_connectedDevicesMessage);
} else if (_deviceManager.hasSpecifiedDeviceId) {
// Multiple devices were found with part of the name/id provided.
_logger.printStatus(_foundMultipleSpecifiedDevices(
_deviceManager.specifiedDeviceId!,
));
}
// Display list of attached devices.
await Device.printDevices(attachedDevices, _logger);
// Display waiting message.
_logger.printStatus('');
_logger.printStatus(_checkingForWirelessDevicesMessage);
_logger.printStatus('');
// Start user device selection so user can select device while waiting
// for wireless devices to load if they want.
_displayDeviceOptions(attachedDevices);
deviceSelection.devices = attachedDevices;
final Future<Device> futureChosenDevice = deviceSelection.userSelectDevice();
Device? chosenDevice;
// Once wireless devices are found, we clear out the waiting message (3),
// device option list (attachedDevices.length), and device option prompt (1).
int numLinesToClear = attachedDevices.length + 4;
futureWirelessDevices = futureWirelessDevices.then((List<Device> wirelessDevices) async {
// If device is already chosen, don't update terminal with
// wireless device list.
if (chosenDevice != null) {
return wirelessDevices;
}
final List<Device> allDevices = attachedDevices + wirelessDevices;
if (_logger.isVerbose) {
await _verbosePrintWirelessDevices(attachedDevices, wirelessDevices);
} else {
// Also clear any invalid device selections.
numLinesToClear += deviceSelection.invalidAttempts;
await _printWirelessDevices(wirelessDevices, numLinesToClear);
}
_logger.printStatus('');
// Reprint device option list.
_displayDeviceOptions(allDevices);
deviceSelection.devices = allDevices;
// Reprint device option prompt.
_logger.printStatus(
'${userMessages.flutterChooseOne}: ',
emphasis: true,
newline: false,
);
return wirelessDevices;
});
// Used for testing.
if (waitForWirelessBeforeInput) {
await futureWirelessDevices;
}
// Wait for user to select a device.
chosenDevice = await futureChosenDevice;
// Update the [DeviceManager.specifiedDeviceId] so that the user will not
// be prompted again.
_deviceManager.specifiedDeviceId = chosenDevice.id;
return <Device>[chosenDevice];
}
/// Reprint list of attached devices before printing list of wireless devices.
Future<void> _verbosePrintWirelessDevices(
List<Device> attachedDevices,
List<Device> wirelessDevices,
) async {
if (wirelessDevices.isEmpty) {
_logger.printStatus(_noWirelessDevicesFoundMessage);
}
// The iOS xcdevice outputs once wireless devices are done loading, so
// reprint attached devices so they're grouped with the wireless ones.
_logger.printStatus(_connectedDevicesMessage);
await Device.printDevices(attachedDevices, _logger);
if (wirelessDevices.isNotEmpty) {
_logger.printStatus('');
_logger.printStatus(_wirelesslyConnectedDevicesMessage);
await Device.printDevices(wirelessDevices, _logger);
}
}
/// Clear [numLinesToClear] lines from terminal. Print message and list of
/// wireless devices.
Future<void> _printWirelessDevices(
List<Device> wirelessDevices,
int numLinesToClear,
) async {
_logger.printStatus(
globals.terminal.clearLines(numLinesToClear),
newline: false,
);
_logger.printStatus('');
if (wirelessDevices.isEmpty) {
_logger.printStatus(_noWirelessDevicesFoundMessage);
} else {
_logger.printStatus(_wirelesslyConnectedDevicesMessage);
await Device.printDevices(wirelessDevices, _logger);
}
}
}
@visibleForTesting
class TargetDeviceSelection {
TargetDeviceSelection(this._logger);
List<Device> devices = <Device>[];
final Logger _logger;
int invalidAttempts = 0;
/// Prompt user to select a device and wait until they select a valid device.
///
/// If the user selects `q`, exit the tool.
///
/// If the user selects an invalid number, reprompt them and continue waiting.
Future<Device> userSelectDevice() async {
Device? chosenDevice;
while (chosenDevice == null) {
final String userInputString = await readUserInput();
if (userInputString.toLowerCase() == 'q') {
throwToolExit('');
}
final int deviceIndex = int.parse(userInputString) - 1;
if (deviceIndex < devices.length) {
chosenDevice = devices[deviceIndex];
}
}
return chosenDevice;
}
/// Prompt user to select a device and wait until they select a valid
/// character.
///
/// Only allow input of a number or `q`.
@visibleForTesting
Future<String> readUserInput() async {
final RegExp pattern = RegExp(r'\d+$|q', caseSensitive: false);
final String prompt = userMessages.flutterChooseOne;
String? choice;
globals.terminal.singleCharMode = true;
while (choice == null || choice.length > 1 || !pattern.hasMatch(choice)) {
_logger.printStatus(prompt, emphasis: true, newline: false);
// prompt ends with ': '
_logger.printStatus(': ', emphasis: true, newline: false);
choice = (await globals.terminal.keystrokes.first).trim();
_logger.printStatus(choice);
invalidAttempts++;
}
globals.terminal.singleCharMode = false;
return choice;
}
}
......@@ -1137,6 +1137,7 @@ class StreamLogger extends Logger {
VoidCallback? onFinish,
Duration? timeout,
SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
}) {
return SilentStatus(
stopwatch: Stopwatch(),
......@@ -1353,6 +1354,9 @@ class FakeIOSDevice extends Fake implements IOSDevice {
@override
bool get isConnected => true;
@override
bool get ephemeral => true;
}
class FakeMDnsClient extends Fake implements MDnsClient {
......
......@@ -850,6 +850,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override
final bool ephemeral = false;
@override
final bool isConnected = true;
@override
Future<String> get sdkNameAndVersion async => 'Android 12';
......
......@@ -6,12 +6,16 @@ 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/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.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 'package:test/fake.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fake_devices.dart';
import '../../src/test_flutter_command_runner.dart';
......@@ -25,6 +29,35 @@ void main() {
late Cache cache;
late Platform platform;
group('ensure factory', () {
late FakeBufferLogger fakeLogger;
setUpAll(() {
fakeLogger = FakeBufferLogger();
});
testWithoutContext('returns DevicesCommandOutputWithExtendedWirelessDeviceDiscovery on MacOS', () async {
final Platform platform = FakePlatform(operatingSystem: 'macos');
final DevicesCommandOutput devicesCommandOutput = DevicesCommandOutput(
platform: platform,
logger: fakeLogger,
);
expect(devicesCommandOutput is DevicesCommandOutputWithExtendedWirelessDeviceDiscovery, true);
});
testWithoutContext('returns default when not on MacOS', () async {
final Platform platform = FakePlatform();
final DevicesCommandOutput devicesCommandOutput = DevicesCommandOutput(
platform: platform,
logger: fakeLogger,
);
expect(devicesCommandOutput is DevicesCommandOutputWithExtendedWirelessDeviceDiscovery, false);
});
});
group('when Platform is not MacOS', () {
setUp(() {
cache = Cache.test(processManager: FakeProcessManager.any());
platform = FakePlatform();
......@@ -57,6 +90,7 @@ If you expected your device to be detected, please run "flutter doctor" to diagn
ProcessManager: () => FakeProcessManager.any(),
Cache: () => cache,
Artifacts: () => Artifacts.test(),
Platform: () => platform,
});
group('when includes both attached and wireless devices', () {
......@@ -175,13 +209,420 @@ wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2
});
});
});
group('when Platform is MacOS', () {
setUp(() {
cache = Cache.test(processManager: FakeProcessManager.any());
platform = FakePlatform(operatingSystem: 'macos');
});
testUsingContext('returns 0 when called', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
}, overrides: <Type, Generator>{
Cache: () => cache,
Artifacts: () => Artifacts.test(),
Platform: () => platform,
});
testUsingContext('no error when no connected devices', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(
testLogger.statusText,
equals('''
No devices found yet. Checking for wireless devices...
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(),
ProcessManager: () => FakeProcessManager.any(),
Cache: () => cache,
Artifacts: () => Artifacts.test(),
Platform: () => platform,
});
group('when includes both attached and wireless devices', () {
List<FakeDeviceJsonData>? deviceList;
setUp(() {
deviceList = <FakeDeviceJsonData>[
fakeDevices[0],
fakeDevices[1],
fakeDevices[2],
fakeDevices[3],
];
});
testUsingContext("get devices' platform types", () async {
final List<String> platformTypes = Device.devicesPlatformTypes(
await globals.deviceManager!.getAllDevices(),
);
expect(platformTypes, <String>['android', 'ios', 'web']);
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Cache: () => cache,
Artifacts: () => Artifacts.test(),
Platform: () => platform,
});
testUsingContext('Outputs parsable JSON with --machine flag', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices', '--machine']);
expect(
json.decode(testLogger.statusText),
<Map<String, Object>>[
fakeDevices[0].json,
fakeDevices[1].json,
fakeDevices[2].json,
fakeDevices[3].json,
],
);
}, overrides: <Type, Generator>{
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, '''
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)
Checking for wireless devices...
2 wirelessly connected devices:
wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2.3) (emulator)
wireless ios (mobile) • wireless-ios • ios • iOS 16 (simulator)
• Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
});
group('with ansi terminal', () {
late FakeTerminal terminal;
late FakeBufferLogger fakeLogger;
setUp(() {
terminal = FakeTerminal(supportsColor: true);
fakeLogger = FakeBufferLogger(terminal: terminal);
fakeLogger.originalStatusText = '''
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)
Checking for wireless devices...
''';
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(fakeLogger.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)
2 wirelessly connected devices:
wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2.3) (emulator)
wireless ios (mobile) • wireless-ios • ios • iOS 16 (simulator)
• Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () =>
_FakeDeviceManager(devices: deviceList, logger: fakeLogger),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
AnsiTerminal: () => terminal,
Logger: () => fakeLogger,
});
});
group('with verbose logging', () {
late FakeBufferLogger fakeLogger;
setUp(() {
fakeLogger = FakeBufferLogger(verbose: true);
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(fakeLogger.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)
Checking for wireless devices...
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)
2 wirelessly connected devices:
wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2.3) (emulator)
wireless ios (mobile) • wireless-ios • ios • iOS 16 (simulator)
• Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(
devices: deviceList,
logger: fakeLogger,
),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
Logger: () => fakeLogger,
});
});
});
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)
Checking for wireless devices...
No wireless devices were found.
• Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
});
group('with ansi terminal', () {
late FakeTerminal terminal;
late FakeBufferLogger fakeLogger;
setUp(() {
terminal = FakeTerminal(supportsColor: true);
fakeLogger = FakeBufferLogger(terminal: terminal);
fakeLogger.originalStatusText = '''
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)
Checking for wireless devices...
''';
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(fakeLogger.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)
No wireless devices were found.
• Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(
devices: deviceList,
logger: fakeLogger,
),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
AnsiTerminal: () => terminal,
Logger: () => fakeLogger,
});
});
group('with verbose logging', () {
late FakeBufferLogger fakeLogger;
setUp(() {
fakeLogger = FakeBufferLogger(verbose: true);
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(fakeLogger.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)
Checking for wireless devices...
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)
No wireless devices were found.
• Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(
devices: deviceList,
logger: fakeLogger,
),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
Logger: () => fakeLogger,
});
});
});
group('when includes only wireless devices', () {
List<FakeDeviceJsonData>? deviceList;
setUp(() {
deviceList = <FakeDeviceJsonData>[
fakeDevices[2],
fakeDevices[3],
];
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(testLogger.statusText, '''
No devices found yet. Checking for wireless devices...
2 wirelessly connected devices:
wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2.3) (emulator)
wireless ios (mobile) • wireless-ios • ios • iOS 16 (simulator)
• Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
});
group('with ansi terminal', () {
late FakeTerminal terminal;
late FakeBufferLogger fakeLogger;
setUp(() {
terminal = FakeTerminal(supportsColor: true);
fakeLogger = FakeBufferLogger(terminal: terminal);
fakeLogger.originalStatusText = '''
No devices found yet. Checking for wireless devices...
''';
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(fakeLogger.statusText, '''
2 wirelessly connected devices:
wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2.3) (emulator)
wireless ios (mobile) • wireless-ios • ios • iOS 16 (simulator)
• Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(
devices: deviceList,
logger: fakeLogger,
),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
AnsiTerminal: () => terminal,
Logger: () => fakeLogger,
});
});
group('with verbose logging', () {
late FakeBufferLogger fakeLogger;
setUp(() {
fakeLogger = FakeBufferLogger(verbose: true);
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(fakeLogger.statusText, '''
No devices found yet. Checking for wireless devices...
2 wirelessly connected devices:
wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2.3) (emulator)
wireless ios (mobile) • wireless-ios • ios • iOS 16 (simulator)
• Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(
devices: deviceList,
logger: fakeLogger,
),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
Logger: () => fakeLogger,
});
});
});
});
});
}
class _FakeDeviceManager extends DeviceManager {
_FakeDeviceManager({
List<FakeDeviceJsonData>? devices,
FakeBufferLogger? logger,
}) : fakeDevices = devices ?? <FakeDeviceJsonData>[],
super(logger: testLogger);
super(logger: logger ?? testLogger);
List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[];
......@@ -203,6 +644,12 @@ class _FakeDeviceManager extends DeviceManager {
DeviceDiscoveryFilter? filter,
}) => getAllDevices(filter: filter);
@override
Future<List<Device>> refreshExtendedWirelessDeviceDiscoverers({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) => getAllDevices(filter: filter);
@override
Future<List<String>> getDeviceDiagnostics() => Future<List<String>>.value(
<String>['Cannot connect to device ABC']
......@@ -216,17 +663,65 @@ class NoDevicesManager extends DeviceManager {
NoDevicesManager() : super(logger: testLogger);
@override
Future<List<Device>> getAllDevices({
DeviceDiscoveryFilter? filter,
}) async => <Device>[];
List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
}
class FakeTerminal extends Fake implements AnsiTerminal {
FakeTerminal({
this.supportsColor = false,
});
@override
Future<List<Device>> refreshAllDevices({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) =>
getAllDevices();
final bool supportsColor;
@override
List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
bool singleCharMode = false;
@override
String clearLines(int numberOfLines) {
return 'CLEAR_LINES_$numberOfLines';
}
}
class FakeBufferLogger extends BufferLogger {
FakeBufferLogger({
super.terminal,
super.outputPreferences,
super.verbose,
}) : super.test();
String originalStatusText = '';
@override
void printStatus(
String message, {
bool? emphasis,
TerminalColor? color,
bool? newline,
int? indent,
int? hangingIndent,
bool? wrap,
}) {
if (message.startsWith('CLEAR_LINES_')) {
expect(statusText, equals(originalStatusText));
final int numberOfLinesToRemove =
int.parse(message.split('CLEAR_LINES_')[1]) - 1;
final List<String> lines = LineSplitter.split(statusText).toList();
// Clear string buffer and re-add lines not removed
clear();
for (int lineNumber = 0; lineNumber < lines.length - numberOfLinesToRemove; lineNumber++) {
super.printStatus(lines[lineNumber]);
}
} else {
super.printStatus(
message,
emphasis: emphasis,
color: color,
newline: newline,
indent: indent,
hangingIndent: hangingIndent,
wrap: wrap,
);
}
}
}
......@@ -1196,6 +1196,12 @@ class FakeDeviceManager extends Fake implements DeviceManager {
DeviceDiscoveryFilter? filter,
}) async => devices;
@override
Future<List<Device>> refreshAllDevices({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) async => devices;
@override
Future<List<String>> getDeviceDiagnostics() async => diagnostics;
}
......
......@@ -543,6 +543,9 @@ class ScreenshotDevice extends Fake implements Device {
@override
bool supportsScreenshot = true;
@override
bool get isConnected => true;
@override
Future<LaunchResult> startApp(
ApplicationPackage? package, {
......
......@@ -268,6 +268,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override
final bool ephemeral = false;
@override
bool get isConnected => true;
@override
Future<String> get sdkNameAndVersion async => 'Android 12';
......
......@@ -1170,6 +1170,9 @@ class FakeDevice extends Fake implements Device {
@override
bool get supportsFastStart => false;
@override
bool get ephemeral => true;
@override
bool get isConnected => true;
......
......@@ -480,7 +480,7 @@ void main() {
expect(done, isTrue);
});
testWithoutContext('AnonymousSpinnerStatus logs warning after timeout', () async {
testWithoutContext('AnonymousSpinnerStatus logs warning after timeout without color support', () async {
mockStopwatch = FakeStopwatch();
const String warningMessage = 'a warning message.';
final bool done = FakeAsync().run<bool>((FakeAsync time) {
......@@ -489,6 +489,7 @@ void main() {
stopwatch: mockStopwatch,
terminal: terminal,
slowWarningCallback: () => warningMessage,
warningColor: TerminalColor.red,
timeout: const Duration(milliseconds: 100),
)..start();
// must be greater than the spinner timer duration
......@@ -497,6 +498,7 @@ void main() {
time.elapse(timeLapse);
List<String> lines = outputStdout();
expect(lines.join().contains(RegExp(red)), isFalse);
expect(lines.join(), '⣽\ba warning message.⣻');
spinner.stop();
......@@ -506,6 +508,35 @@ void main() {
expect(done, isTrue);
});
testWithoutContext('AnonymousSpinnerStatus logs warning after timeout with color support', () async {
mockStopwatch = FakeStopwatch();
const String warningMessage = 'a warning message.';
final bool done = FakeAsync().run<bool>((FakeAsync time) {
final AnonymousSpinnerStatus spinner = AnonymousSpinnerStatus(
stdio: mockStdio,
stopwatch: mockStopwatch,
terminal: coloredTerminal,
slowWarningCallback: () => warningMessage,
warningColor: TerminalColor.red,
timeout: const Duration(milliseconds: 100),
)..start();
// must be greater than the spinner timer duration
const Duration timeLapse = Duration(milliseconds: 101);
mockStopwatch.elapsed += timeLapse;
time.elapse(timeLapse);
List<String> lines = outputStdout();
expect(lines.join().contains(RegExp(red)), isTrue);
expect(lines.join(), '⣽\b${AnsiTerminal.red}a warning message.${AnsiTerminal.resetColor}⣻');
expect(lines.join(), matches('$red$warningMessage$resetColor'));
spinner.stop();
lines = outputStdout();
return true;
});
expect(done, isTrue);
});
testWithoutContext('Stdout startProgress on colored terminal', () async {
final Logger logger = StdoutLogger(
terminal: coloredTerminal,
......
......@@ -34,7 +34,7 @@ void main() {
});
});
group('ANSI coloring and bold', () {
group('ANSI coloring, bold, and clearing', () {
late AnsiTerminal terminal;
setUp(() {
......@@ -103,6 +103,39 @@ void main() {
equals('${AnsiTerminal.bold}bold output still bold${AnsiTerminal.resetBold}'),
);
});
testWithoutContext('clearing lines works', () {
expect(
terminal.clearLines(3),
equals(
'${AnsiTerminal.cursorBeginningOfLineCode}'
'${AnsiTerminal.clearEntireLineCode}'
'${AnsiTerminal.cursorUpLineCode}'
'${AnsiTerminal.clearEntireLineCode}'
'${AnsiTerminal.cursorUpLineCode}'
'${AnsiTerminal.clearEntireLineCode}'
),
);
expect(
terminal.clearLines(1),
equals(
'${AnsiTerminal.cursorBeginningOfLineCode}'
'${AnsiTerminal.clearEntireLineCode}'
),
);
});
testWithoutContext('clearing lines when color is not supported does not work', () {
terminal = AnsiTerminal(
stdio: Stdio(), // Danger, using real stdio.
platform: FakePlatform()..stdoutSupportsAnsi = false,
);
expect(
terminal.clearLines(3),
equals(''),
);
});
});
group('character input prompt', () {
......
......@@ -141,29 +141,92 @@ void main() {
});
testWithoutContext('getAllDevices caches', () async {
final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f');
final FakePollingDeviceDiscovery notSupportedDiscoverer = FakePollingDeviceDiscovery();
final FakePollingDeviceDiscovery supportedDiscoverer = FakePollingDeviceDiscovery(requiresExtendedWirelessDeviceDiscovery: true);
final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f');
final FakeDevice wirelessDevice = FakeDevice('Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
notSupportedDiscoverer.addDevice(attachedDevice);
supportedDiscoverer.addDevice(wirelessDevice);
final TestDeviceManager deviceManager = TestDeviceManager(
<Device>[device1],
<Device>[],
logger: BufferLogger.test(),
deviceDiscoveryOverrides: <DeviceDiscovery>[
notSupportedDiscoverer,
supportedDiscoverer,
],
);
expect(await deviceManager.getAllDevices(), <Device>[device1]);
expect(await deviceManager.getAllDevices(), <Device>[attachedDevice, wirelessDevice]);
final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
deviceManager.resetDevices(<Device>[device2]);
expect(await deviceManager.getAllDevices(), <Device>[device1]);
final FakeDevice newAttachedDevice = FakeDevice('Nexus 5X', '01abfc49119c410e');
notSupportedDiscoverer.addDevice(newAttachedDevice);
final FakeDevice newWirelessDevice = FakeDevice('New wireless device', 'new-wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
supportedDiscoverer.addDevice(newWirelessDevice);
expect(await deviceManager.getAllDevices(), <Device>[attachedDevice, wirelessDevice]);
});
testWithoutContext('refreshAllDevices does not cache', () async {
final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f');
final FakePollingDeviceDiscovery notSupportedDiscoverer = FakePollingDeviceDiscovery();
final FakePollingDeviceDiscovery supportedDiscoverer = FakePollingDeviceDiscovery(requiresExtendedWirelessDeviceDiscovery: true);
final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f');
final FakeDevice wirelessDevice = FakeDevice('Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
notSupportedDiscoverer.addDevice(attachedDevice);
supportedDiscoverer.addDevice(wirelessDevice);
final TestDeviceManager deviceManager = TestDeviceManager(
<Device>[device1],
<Device>[],
logger: BufferLogger.test(),
deviceDiscoveryOverrides: <DeviceDiscovery>[
notSupportedDiscoverer,
supportedDiscoverer,
],
);
expect(await deviceManager.refreshAllDevices(), <Device>[device1]);
expect(await deviceManager.refreshAllDevices(), <Device>[attachedDevice, wirelessDevice]);
final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
deviceManager.resetDevices(<Device>[device2]);
expect(await deviceManager.refreshAllDevices(), <Device>[device2]);
final FakeDevice newAttachedDevice = FakeDevice('Nexus 5X', '01abfc49119c410e');
notSupportedDiscoverer.addDevice(newAttachedDevice);
final FakeDevice newWirelessDevice = FakeDevice('New wireless device', 'new-wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
supportedDiscoverer.addDevice(newWirelessDevice);
expect(await deviceManager.refreshAllDevices(), <Device>[attachedDevice, newAttachedDevice, wirelessDevice, newWirelessDevice]);
});
testWithoutContext('refreshExtendedWirelessDeviceDiscoverers only refreshes discoverers that require extended time', () async {
final FakePollingDeviceDiscovery normalDiscoverer = FakePollingDeviceDiscovery();
final FakePollingDeviceDiscovery extendedDiscoverer = FakePollingDeviceDiscovery(requiresExtendedWirelessDeviceDiscovery: true);
final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f');
final FakeDevice wirelessDevice = FakeDevice('Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
normalDiscoverer.addDevice(attachedDevice);
extendedDiscoverer.addDevice(wirelessDevice);
final TestDeviceManager deviceManager = TestDeviceManager(
<Device>[],
logger: BufferLogger.test(),
deviceDiscoveryOverrides: <DeviceDiscovery>[
normalDiscoverer,
extendedDiscoverer,
],
);
await deviceManager.refreshExtendedWirelessDeviceDiscoverers();
expect(await deviceManager.getAllDevices(), <Device>[attachedDevice, wirelessDevice]);
final FakeDevice newAttachedDevice = FakeDevice('Nexus 5X', '01abfc49119c410e');
normalDiscoverer.addDevice(newAttachedDevice);
final FakeDevice newWirelessDevice = FakeDevice('New wireless device', 'new-wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
extendedDiscoverer.addDevice(newWirelessDevice);
await deviceManager.refreshExtendedWirelessDeviceDiscoverers();
expect(await deviceManager.getAllDevices(), <Device>[attachedDevice, wirelessDevice, newWirelessDevice]);
});
});
......@@ -1034,34 +1097,6 @@ class TestDeviceManager extends DeviceManager {
}
}
class MockDeviceDiscovery extends Fake implements DeviceDiscovery {
int devicesCalled = 0;
int discoverDevicesCalled = 0;
@override
bool supportsPlatform = true;
List<Device> deviceValues = <Device>[];
@override
Future<List<Device>> devices({DeviceDiscoveryFilter? filter}) async {
devicesCalled += 1;
return deviceValues;
}
@override
Future<List<Device>> discoverDevices({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) async {
discoverDevicesCalled += 1;
return deviceValues;
}
@override
List<String> get wellKnownIds => <String>[];
}
class TestDeviceDiscoverySupportFilter extends DeviceDiscoverySupportFilter {
TestDeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutterOrProject({
required super.flutterProject,
......
......@@ -75,6 +75,7 @@ void main() {
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
);
expect(device.isSupported(), isTrue);
});
......@@ -91,6 +92,7 @@ void main() {
name: 'iPhone 1',
cpuArchitecture: DarwinArch.armv7,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
);
expect(device.isSupported(), isFalse);
});
......@@ -108,6 +110,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '1.0.0',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
).majorSdkVersion, 1);
expect(IOSDevice(
'device-123',
......@@ -121,6 +124,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '13.1.1',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
).majorSdkVersion, 13);
expect(IOSDevice(
'device-123',
......@@ -134,6 +138,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '10',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
).majorSdkVersion, 10);
expect(IOSDevice(
'device-123',
......@@ -147,6 +152,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '0',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
).majorSdkVersion, 0);
expect(IOSDevice(
'device-123',
......@@ -160,6 +166,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
sdkVersion: 'bogus',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
).majorSdkVersion, 0);
});
......@@ -176,6 +183,7 @@ void main() {
sdkVersion: '13.3 17C54',
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
);
expect(await device.sdkNameAndVersion,'iOS 13.3 17C54');
......@@ -194,6 +202,7 @@ void main() {
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
);
expect(device.supportsRuntimeMode(BuildMode.debug), true);
......@@ -218,6 +227,7 @@ void main() {
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
);
},
throwsAssertionError,
......@@ -308,6 +318,7 @@ void main() {
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
);
logReader1 = createLogReader(device, appPackage1, process1);
logReader2 = createLogReader(device, appPackage2, process2);
......@@ -369,6 +380,7 @@ void main() {
platform: macPlatform,
fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
);
device2 = IOSDevice(
......@@ -383,6 +395,7 @@ void main() {
platform: macPlatform,
fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
);
});
......@@ -587,6 +600,120 @@ void main() {
expect(diagnostics.first, 'Generic pairing error');
});
});
group('waitForDeviceToConnect', () {
late FakeXcdevice xcdevice;
late Cache cache;
late FakeProcessManager fakeProcessManager;
late BufferLogger logger;
late IOSDeploy iosDeploy;
late IMobileDevice iMobileDevice;
late IOSWorkflow iosWorkflow;
late IOSDevice notConnected1;
setUp(() {
xcdevice = FakeXcdevice();
final Artifacts artifacts = Artifacts.test();
cache = Cache.test(processManager: FakeProcessManager.any());
logger = BufferLogger.test();
iosWorkflow = FakeIOSWorkflow();
fakeProcessManager = FakeProcessManager.any();
iosDeploy = IOSDeploy(
artifacts: artifacts,
cache: cache,
logger: logger,
platform: macPlatform,
processManager: fakeProcessManager,
);
iMobileDevice = IMobileDevice(
artifacts: artifacts,
cache: cache,
processManager: fakeProcessManager,
logger: logger,
);
notConnected1 = IOSDevice(
'00000001-0000000000000000',
name: 'iPad',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()),
iosDeploy: iosDeploy,
iMobileDevice: iMobileDevice,
logger: logger,
platform: macPlatform,
fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached,
isConnected: false,
);
});
testWithoutContext('wait for device to connect via wifi', () async {
final IOSDevices iosDevices = IOSDevices(
platform: macPlatform,
xcdevice: xcdevice,
iosWorkflow: iosWorkflow,
logger: logger,
);
xcdevice.isInstalled = true;
xcdevice.waitForDeviceEvent = XCDeviceEventNotification(
XCDeviceEvent.attach,
XCDeviceEventInterface.wifi,
'00000001-0000000000000000'
);
final Device? device = await iosDevices.waitForDeviceToConnect(
notConnected1,
logger
);
expect(device?.isConnected, isTrue);
expect(device?.connectionInterface, DeviceConnectionInterface.wireless);
});
testWithoutContext('wait for device to connect via usb', () async {
final IOSDevices iosDevices = IOSDevices(
platform: macPlatform,
xcdevice: xcdevice,
iosWorkflow: iosWorkflow,
logger: logger,
);
xcdevice.isInstalled = true;
xcdevice.waitForDeviceEvent = XCDeviceEventNotification(
XCDeviceEvent.attach,
XCDeviceEventInterface.usb,
'00000001-0000000000000000'
);
final Device? device = await iosDevices.waitForDeviceToConnect(
notConnected1,
logger
);
expect(device?.isConnected, isTrue);
expect(device?.connectionInterface, DeviceConnectionInterface.attached);
});
testWithoutContext('wait for device returns null', () async {
final IOSDevices iosDevices = IOSDevices(
platform: macPlatform,
xcdevice: xcdevice,
iosWorkflow: iosWorkflow,
logger: logger,
);
xcdevice.isInstalled = true;
xcdevice.waitForDeviceEvent = null;
final Device? device = await iosDevices.waitForDeviceToConnect(
notConnected1,
logger
);
expect(device, isNull);
});
});
}
class FakeIOSApp extends Fake implements IOSApp {
......@@ -603,6 +730,7 @@ class FakeXcdevice extends Fake implements XCDevice {
final List<List<IOSDevice>> devices = <List<IOSDevice>>[];
final List<String> diagnostics = <String>[];
StreamController<Map<XCDeviceEvent, String>> deviceEventController = StreamController<Map<XCDeviceEvent, String>>();
XCDeviceEventNotification? waitForDeviceEvent;
@override
bool isInstalled = true;
......@@ -621,6 +749,16 @@ class FakeXcdevice extends Fake implements XCDevice {
Future<List<IOSDevice>> getAvailableIOSDevices({Duration? timeout}) async {
return devices[getAvailableIOSDevicesCount++];
}
@override
Future<XCDeviceEventNotification?> waitForDeviceToConnect(String deviceId) async {
final XCDeviceEventNotification? waitEvent = waitForDeviceEvent;
if (waitEvent != null) {
return XCDeviceEventNotification(waitEvent.eventType, waitEvent.eventInterface, waitEvent.deviceIdentifier);
} else {
return null;
}
}
}
class FakeProcess extends Fake implements Process {
......
......@@ -359,5 +359,6 @@ IOSDevice setUpIOSDevice({
),
iProxy: IProxy.test(logger: logger, processManager: processManager),
connectionInterface: interfaceType ?? DeviceConnectionInterface.attached,
isConnected: true,
);
}
......@@ -100,5 +100,6 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
cpuArchitecture: DarwinArch.arm64,
iProxy: IProxy.test(logger: logger, processManager: processManager),
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
);
}
......@@ -338,6 +338,7 @@ IOSDevice setUpIOSDevice({
),
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
);
}
......
......@@ -599,6 +599,7 @@ IOSDevice setUpIOSDevice({
),
cpuArchitecture: DarwinArch.arm64,
connectionInterface: interfaceType,
isConnected: true,
);
}
......
......@@ -377,6 +377,150 @@ void main() {
await detach1.future;
expect(logger.traceText, contains('xcdevice observe error: Some error'));
});
testUsingContext('handles exit code', () async {
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'observe',
'--both',
],
));
final Completer<void> doneCompleter = Completer<void>();
xcdevice.observedDeviceEvents()!.listen(null, onDone: () {
doneCompleter.complete();
});
await doneCompleter.future;
expect(logger.traceText, contains('xcdevice exited with code 0'));
});
});
group('wait device events', () {
testUsingContext('relays events', () async {
const String deviceId = '00000001-0000000000000000';
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--usb',
deviceId,
],
));
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--wifi',
deviceId,
],
stdout: 'Attach: 00000001-0000000000000000\n',
));
// Attach: 00000001-0000000000000000
final XCDeviceEventNotification? event = await xcdevice.waitForDeviceToConnect(deviceId);
expect(event?.deviceIdentifier, deviceId);
expect(event?.eventInterface, XCDeviceEventInterface.wifi);
expect(event?.eventType, XCDeviceEvent.attach);
});
testUsingContext('handles exit code', () async {
const String deviceId = '00000001-0000000000000000';
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--usb',
deviceId,
],
exitCode: 1,
));
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--wifi',
deviceId,
],
));
final XCDeviceEventNotification? event = await xcdevice.waitForDeviceToConnect(deviceId);
expect(event, isNull);
expect(logger.traceText, contains('xcdevice wait --usb exited with code 0'));
expect(logger.traceText, contains('xcdevice wait --wifi exited with code 0'));
expect(xcdevice.waitStreamController?.isClosed, isTrue);
});
testUsingContext('handles cancel', () async {
const String deviceId = '00000001-0000000000000000';
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--usb',
deviceId,
],
));
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--wifi',
deviceId,
],
));
final Future<XCDeviceEventNotification?> futureEvent = xcdevice.waitForDeviceToConnect(deviceId);
xcdevice.cancelWaitForDeviceToConnect();
final XCDeviceEventNotification? event = await futureEvent;
expect(event, isNull);
expect(logger.traceText, contains('xcdevice wait --usb exited with code 0'));
expect(logger.traceText, contains('xcdevice wait --wifi exited with code 0'));
expect(xcdevice.waitStreamController?.isClosed, isTrue);
});
});
group('available devices', () {
......@@ -480,31 +624,41 @@ void main() {
stdout: devicesOutput,
));
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
expect(devices, hasLength(4));
expect(devices, hasLength(5));
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[0].isConnected, true);
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[1].isConnected, true);
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[2].isConnected, true);
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(devices[3].isConnected, true);
expect(devices[4].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2');
expect(devices[4].name, 'iPhone');
expect(await devices[4].sdkNameAndVersion, 'iOS 13.3 17C54');
expect(devices[4].cpuArchitecture, DarwinArch.arm64);
expect(devices[4].connectionInterface, DeviceConnectionInterface.attached);
expect(devices[4].isConnected, false);
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
......
......@@ -8,8 +8,12 @@ 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/build_info.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/ios_workflow.dart';
import 'package:flutter_tools/src/macos/xcdevice.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/runner/target_devices.dart';
import 'package:test/fake.dart';
......@@ -18,7 +22,41 @@ import '../../src/common.dart';
import '../../src/context.dart';
void main() {
group('findAllTargetDevices', () {
testWithoutContext('Ensure factory returns TargetDevicesWithExtendedWirelessDeviceDiscovery on MacOS', () async {
final BufferLogger logger = BufferLogger.test();
final Platform platform = FakePlatform(operatingSystem: 'macos');
final TestDeviceManager deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
final TargetDevices targetDevices = TargetDevices(
platform: platform,
deviceManager: deviceManager,
logger: logger,
);
expect(targetDevices is TargetDevicesWithExtendedWirelessDeviceDiscovery, true);
});
testWithoutContext('Ensure factory returns default when not on MacOS', () async {
final BufferLogger logger = BufferLogger.test();
final Platform platform = FakePlatform();
final TestDeviceManager deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
final TargetDevices targetDevices = TargetDevices(
platform: platform,
deviceManager: deviceManager,
logger: logger,
);
expect(targetDevices is TargetDevicesWithExtendedWirelessDeviceDiscovery, false);
});
group('findAllTargetDevices on non-MacOS platform', () {
late Platform platform;
final FakeDevice attachedAndroidDevice1 = FakeDevice(deviceName: 'target-device-1');
......@@ -36,7 +74,7 @@ void main() {
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 exactMatchAttachedUnsupportedAndroidDevice = FakeDevice(deviceName: 'target-device', deviceSupported: false);
final FakeDevice exactMatchUnsupportedByProjectDevice = FakeDevice(deviceName: 'target-device', deviceSupportForProject: false);
setUp(() {
......@@ -60,6 +98,7 @@ void main() {
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
platform: platform,
deviceManager: deviceManager,
logger: logger,
);
......@@ -87,6 +126,7 @@ Unable to locate a development device; please run 'flutter doctor' for informati
deviceManager.hasSpecifiedAllDevices = true;
final TargetDevices targetDevices = TargetDevices(
platform: platform,
deviceManager: deviceManager,
logger: logger,
);
......@@ -101,9 +141,34 @@ Unable to locate a development device; please run 'flutter doctor' for informati
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
testUsingContext('ensure unsupported for projects are included when includeDevicesUnsupportedByProject is true', () async {
final BufferLogger logger = BufferLogger.test();
final TestDeviceManager deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
deviceManager.androidDiscoverer.deviceList = <Device>[attachedUnsupportedAndroidDevice, attachedUnsupportedForProjectAndroidDevice];
final TargetDevices targetDevices = TargetDevices(
platform: platform,
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices(
includeDevicesUnsupportedByProject: true,
);
expect(logger.statusText, equals(''));
expect(devices, <Device>[attachedUnsupportedForProjectAndroidDevice]);
expect(deviceManager.androidDiscoverer.devicesCalled, 2);
expect(deviceManager.androidDiscoverer.discoverDevicesCalled, 0);
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
group('finds no devices', () {
late BufferLogger logger;
late TestDeviceManager deviceManager;
late TargetDevices targetDevices;
setUp(() {
logger = BufferLogger.test();
......@@ -111,14 +176,15 @@ Unable to locate a development device; please run 'flutter doctor' for informati
logger: logger,
platform: platform,
);
});
group('when device not specified', () {
testUsingContext('when no devices', () async {
final TargetDevices targetDevices = TargetDevices(
targetDevices = TargetDevices(
platform: platform,
deviceManager: deviceManager,
logger: logger,
);
});
group('with device not specified', () {
testUsingContext('when no devices', () async {
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
......@@ -136,10 +202,6 @@ No supported devices connected.
attachedUnsupportedForProjectAndroidDevice,
];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
......@@ -163,10 +225,6 @@ If you would like your app to run on android, consider running `flutter create .
});
testUsingContext('when no devices', () async {
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
......@@ -183,10 +241,6 @@ No supported devices found with name or id matching 'target-device'.
final FakeDevice device2 = FakeDevice.wireless(deviceName: 'no-match-2');
deviceManager.androidDiscoverer.deviceList = <Device>[device1, device2];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
......@@ -203,12 +257,8 @@ no-match-2 (mobile) • xxx • android • Android 10
});
testUsingContext('when matching device is unsupported by flutter', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[exactMatchattachedUnsupportedAndroidDevice];
deviceManager.androidDiscoverer.deviceList = <Device>[exactMatchAttachedUnsupportedAndroidDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
......@@ -230,10 +280,6 @@ target-device (mobile) • xxx • android • Android 10 (unsupported)
});
testUsingContext('when no devices', () async {
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
......@@ -252,10 +298,6 @@ No devices found.
];
deviceManager.otherDiscoverer.deviceList = <Device>[fuchsiaDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
......@@ -274,12 +316,12 @@ If you would like your app to run on android or fuchsia, consider running `flutt
});
});
});
group('finds single device', () {
late BufferLogger logger;
late TestDeviceManager deviceManager;
late TargetDevices targetDevices;
setUp(() {
logger = BufferLogger.test();
......@@ -287,16 +329,17 @@ If you would like your app to run on android or fuchsia, consider running `flutt
logger: logger,
platform: platform,
);
targetDevices = TargetDevices(
platform: platform,
deviceManager: deviceManager,
logger: logger,
);
});
group('when device not specified', () {
group('with device not specified', () {
testUsingContext('when single attached device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
......@@ -309,10 +352,6 @@ If you would like your app to run on android or fuchsia, consider running `flutt
testUsingContext('when single wireless device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[wirelessAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
......@@ -325,10 +364,6 @@ If you would like your app to run on android or fuchsia, consider running `flutt
testUsingContext('when multiple but only one ephemeral', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[nonEphemeralDevice, wirelessAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
......@@ -346,14 +381,10 @@ If you would like your app to run on android or fuchsia, consider running `flutt
testUsingContext('when multiple matches but first is unsupported by flutter', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[
exactMatchattachedUnsupportedAndroidDevice,
exactMatchAttachedUnsupportedAndroidDevice,
exactMatchAndroidDevice,
];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
......@@ -366,10 +397,6 @@ If you would like your app to run on android or fuchsia, consider running `flutt
testUsingContext('when matching device is unsupported by project', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[exactMatchUnsupportedByProjectDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
......@@ -382,10 +409,6 @@ If you would like your app to run on android or fuchsia, consider running `flutt
testUsingContext('when matching attached device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[exactMatchAndroidDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
......@@ -398,10 +421,6 @@ If you would like your app to run on android or fuchsia, consider running `flutt
testUsingContext('when matching wireless device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[exactMatchWirelessAndroidDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
......@@ -411,13 +430,9 @@ If you would like your app to run on android or fuchsia, consider running `flutt
expect(deviceManager.androidDiscoverer.numberOfTimesPolled, 1);
});
testUsingContext('when exact match attached device and partial match wireless device', () async {
testUsingContext('when exact matching an attached device and partial matching a wireless device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[exactMatchAndroidDevice, wirelessAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
......@@ -436,10 +451,6 @@ If you would like your app to run on android or fuchsia, consider running `flutt
testUsingContext('when only one device', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
......@@ -452,9 +463,10 @@ If you would like your app to run on android or fuchsia, consider running `flutt
});
group('Finds multiple devices', () {
group('finds multiple devices', () {
late BufferLogger logger;
late TestDeviceManager deviceManager;
late TargetDevices targetDevices;
setUp(() {
logger = BufferLogger.test();
......@@ -462,9 +474,14 @@ If you would like your app to run on android or fuchsia, consider running `flutt
logger: logger,
platform: platform,
);
targetDevices = TargetDevices(
platform: platform,
deviceManager: deviceManager,
logger: logger,
);
});
group('when device not specified', () {
group('with device not specified', () {
group('with stdinHasTerminal', () {
late FakeTerminal terminal;
......@@ -481,16 +498,12 @@ If you would like your app to run on android or fuchsia, consider running `flutt
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:
Connected devices:
target-device-1 (mobile) • xxx • android • Android 10
Wirelessly connected devices:
......@@ -509,16 +522,12 @@ target-device-5 (mobile) • xxx • android • Android 10
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:
Connected devices:
target-device-1 (mobile) • xxx • android • Android 10
target-device-2 (mobile) • xxx • android • Android 10
[1]: target-device-1 (xxx)
......@@ -534,16 +543,12 @@ target-device-2 (mobile) • xxx • android • Android 10
testUsingContext('including only wireless devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[wirelessAndroidDevice1, wirelessAndroidDevice2];
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:
Connected devices:
Wirelessly connected devices:
target-device-5 (mobile) • xxx • android • Android 10
......@@ -559,7 +564,6 @@ target-device-6 (mobile) • xxx • android • Android 10
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
group('without stdinHasTerminal', () {
......@@ -579,10 +583,6 @@ target-device-6 (mobile) • xxx • android • Android 10
wirelessUnsupportedForProjectAndroidDevice,
];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
......@@ -606,10 +606,6 @@ target-device-8 (mobile) • xxx • android • Android 10
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('''
......@@ -629,10 +625,6 @@ target-device-2 (mobile) • xxx • android • Android 10
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('''
......@@ -673,12 +665,8 @@ target-device-6 (mobile) • xxx • android • Android 10
wirelessUnsupportedAndroidDevice,
wirelessUnsupportedForProjectAndroidDevice,
];
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('''
......@@ -705,12 +693,8 @@ target-device-8 (mobile) • xxx • android • Android 10
testUsingContext('including only attached devices', () async {
deviceManager.androidDiscoverer.deviceList = <Device>[attachedAndroidDevice1, attachedAndroidDevice2];
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('''
......@@ -730,12 +714,8 @@ target-device-2 (mobile) • xxx • android • Android 10
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('''
......@@ -767,10 +747,6 @@ target-device-6 (mobile) • xxx • android • Android 10
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('''
......@@ -796,10 +772,6 @@ target-device-1 (mobile) • xxx • android • Android 10
wirelessUnsupportedForProjectAndroidDevice,
];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
......@@ -822,10 +794,6 @@ target-device-8 (mobile) • xxx • android • Android 10
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('''
......@@ -844,10 +812,6 @@ target-device-2 (mobile) • xxx • android • Android 10
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('''
......@@ -883,10 +847,6 @@ target-device-6 (mobile) • xxx • android • Android 10
];
deviceManager.otherDiscoverer.deviceList = <Device>[fuchsiaDevice];
final TargetDevices targetDevices = TargetDevices(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
......@@ -897,36 +857,1420 @@ target-device-6 (mobile) • xxx • android • Android 10
});
});
});
});
group('findAllTargetDevices on mac platform', () {
late Platform platform;
final FakeIOSDevice attachedIOSDevice1 = FakeIOSDevice(deviceName: 'target-device-1');
final FakeIOSDevice attachedIOSDevice2 = FakeIOSDevice(deviceName: 'target-device-2');
final FakeIOSDevice attachedUnsupportedIOSDevice = FakeIOSDevice(deviceName: 'target-device-3', deviceSupported: false);
final FakeIOSDevice attachedUnsupportedForProjectIOSDevice = FakeIOSDevice(deviceName: 'target-device-4', deviceSupportForProject: false);
final FakeIOSDevice disconnectedWirelessIOSDevice1 = FakeIOSDevice.notConnectedWireless(deviceName: 'target-device-5');
final FakeIOSDevice connectedWirelessIOSDevice1 = FakeIOSDevice.connectedWireless(deviceName: 'target-device-5');
final FakeIOSDevice disconnectedWirelessIOSDevice2 = FakeIOSDevice.notConnectedWireless(deviceName: 'target-device-6');
final FakeIOSDevice connectedWirelessIOSDevice2 = FakeIOSDevice.connectedWireless(deviceName: 'target-device-6');
final FakeIOSDevice disconnectedWirelessUnsupportedIOSDevice = FakeIOSDevice.notConnectedWireless(deviceName: 'target-device-7', deviceSupported: false);
final FakeIOSDevice connectedWirelessUnsupportedIOSDevice = FakeIOSDevice.connectedWireless(deviceName: 'target-device-7', deviceSupported: false);
final FakeIOSDevice disconnectedWirelessUnsupportedForProjectIOSDevice = FakeIOSDevice.notConnectedWireless(deviceName: 'target-device-8', deviceSupportForProject: false);
final FakeIOSDevice connectedWirelessUnsupportedForProjectIOSDevice = FakeIOSDevice.connectedWireless(deviceName: 'target-device-8', deviceSupportForProject: false);
final FakeIOSDevice nonEphemeralDevice = FakeIOSDevice(deviceName: 'target-device-9', ephemeral: false);
final FakeDevice fuchsiaDevice = FakeDevice.fuchsia(deviceName: 'target-device-10');
final FakeIOSDevice exactMatchAttachedIOSDevice = FakeIOSDevice(deviceName: 'target-device');
final FakeIOSDevice exactMatchAttachedUnsupportedIOSDevice = FakeIOSDevice(deviceName: 'target-device', deviceSupported: false);
final FakeIOSDevice exactMatchUnsupportedByProjectDevice = FakeIOSDevice(deviceName: 'target-device', deviceSupportForProject: false);
setUp(() {
platform = FakePlatform(operatingSystem: 'macos');
});
}
class TestDeviceManager extends DeviceManager {
TestDeviceManager({
required this.logger,
required this.platform,
}) : super(logger: logger);
group('when cannot launch anything', () {
late BufferLogger logger;
late FakeDoctor doctor;
final Logger logger;
final Platform platform;
setUp(() {
logger = BufferLogger.test();
doctor = FakeDoctor(canLaunchAnything: false);
});
@override
String? specifiedDeviceId;
testUsingContext('does not search for devices', () async {
final TestDeviceManager deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1];
@override
bool hasSpecifiedAllDevices = false;
final TargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices = TargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
final TestPollingDeviceDiscovery androidDiscoverer = TestPollingDeviceDiscovery(
'android',
expect(logger.errorText, equals('''
Unable to locate a development device; please run 'flutter doctor' for information about installing additional components.
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 0);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 0);
}, overrides: <Type, Generator>{
Doctor: () => doctor,
});
});
testUsingContext('ensure refresh when deviceDiscoveryTimeout is provided', () async {
final BufferLogger logger = BufferLogger.test();
final TestDeviceManager deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
final TestPollingDeviceDiscovery otherDiscoverer = TestPollingDeviceDiscovery(
'other',
deviceManager.iosDiscoverer.deviceList = <Device>[disconnectedWirelessIOSDevice1];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[connectedWirelessIOSDevice1];
final TargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices = TargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
final TestPollingDeviceDiscovery iosDiscoverer = TestPollingDeviceDiscovery(
'ios',
final List<Device>? devices = await targetDevices.findAllTargetDevices(
deviceDiscoveryTimeout: const Duration(seconds: 2),
);
@override
expect(logger.statusText, equals(''));
expect(devices, <Device>[connectedWirelessIOSDevice1]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 1);
});
testUsingContext('ensure unsupported for projects are included when includeDevicesUnsupportedByProject is true', () async {
final BufferLogger logger = BufferLogger.test();
final TestDeviceManager deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
deviceManager.iosDiscoverer.deviceList = <Device>[attachedUnsupportedIOSDevice, attachedUnsupportedForProjectIOSDevice];
final TargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices = TargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
final List<Device>? devices = await targetDevices.findAllTargetDevices(
includeDevicesUnsupportedByProject: true,
);
expect(logger.statusText, equals(''));
expect(devices, <Device>[attachedUnsupportedForProjectIOSDevice]);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
group('finds no devices', () {
late BufferLogger logger;
late TestDeviceManager deviceManager;
late TargetDevices targetDevices;
setUp(() {
logger = BufferLogger.test();
deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
targetDevices = TargetDevices(
platform: platform,
deviceManager: deviceManager,
logger: logger,
);
});
group('with device not specified', () {
testUsingContext('when no devices', () async {
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
No supported devices connected.
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
testUsingContext('when device is unsupported by flutter or project', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
disconnectedWirelessUnsupportedIOSDevice,
disconnectedWirelessUnsupportedForProjectIOSDevice,
];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
connectedWirelessUnsupportedIOSDevice,
connectedWirelessUnsupportedForProjectIOSDevice,
];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
No supported devices connected.
The following devices were found, but are not supported by this project:
target-device-3 (mobile) • xxx • ios • iOS 16 (unsupported)
target-device-4 (mobile) • xxx • ios • iOS 16
target-device-7 (mobile) • xxx • ios • iOS 16 (unsupported)
target-device-8 (mobile) • xxx • ios • iOS 16
If you would like your app to run on ios, consider running `flutter create .` to generate projects for these platforms.
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
testUsingContext('when all found devices are not connected', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[
disconnectedWirelessIOSDevice1,
disconnectedWirelessIOSDevice2,
];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[
disconnectedWirelessIOSDevice1,
disconnectedWirelessIOSDevice2,
];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
No supported devices connected.
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
});
group('with hasSpecifiedDeviceId', () {
setUp(() {
deviceManager.specifiedDeviceId = 'target-device';
});
testUsingContext('when no devices', () async {
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
No supported devices found with name or id matching 'target-device'.
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 4);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
});
testUsingContext('when no devices match', () async {
final FakeIOSDevice device1 = FakeIOSDevice(deviceName: 'no-match-1');
final FakeIOSDevice device2 = FakeIOSDevice.notConnectedWireless(deviceName: 'no-match-2');
final FakeIOSDevice device2Connected = FakeIOSDevice.connectedWireless(deviceName: 'no-match-2');
deviceManager.iosDiscoverer.deviceList = <Device>[device1, device2];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[device1,device2Connected];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
No supported devices found with name or id matching 'target-device'.
The following devices were found:
no-match-1 (mobile) • xxx • ios • iOS 16
no-match-2 (mobile) • xxx • ios • iOS 16
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 4);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
});
testUsingContext('when matching device is unsupported by flutter', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[exactMatchAttachedUnsupportedIOSDevice];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
No supported devices found with name or id matching 'target-device'.
The following devices were found:
target-device (mobile) • xxx • ios • iOS 16 (unsupported)
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 4);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
});
});
group('with hasSpecifiedAllDevices', () {
setUp(() {
deviceManager.hasSpecifiedAllDevices = true;
});
testUsingContext('when no devices', () async {
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
No devices found.
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
testUsingContext('when devices are either unsupported by flutter or project or all', () async {
deviceManager.otherDiscoverer.deviceList = <Device>[fuchsiaDevice];
deviceManager.iosDiscoverer.deviceList = <Device>[
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
disconnectedWirelessUnsupportedIOSDevice,
disconnectedWirelessUnsupportedForProjectIOSDevice,
];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
connectedWirelessUnsupportedIOSDevice,
connectedWirelessUnsupportedForProjectIOSDevice,
];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
No devices found.
The following devices were found, but are not supported by this project:
target-device-10 (mobile) • xxx • fuchsia-arm64 • tester
target-device-3 (mobile) • xxx • ios • iOS 16 (unsupported)
target-device-4 (mobile) • xxx • ios • iOS 16
target-device-7 (mobile) • xxx • ios • iOS 16 (unsupported)
target-device-8 (mobile) • xxx • ios • iOS 16
If you would like your app to run on fuchsia or ios, consider running `flutter create .` to generate projects for these platforms.
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
});
});
group('finds single device', () {
late TestBufferLogger logger;
late TestDeviceManager deviceManager;
late TargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices;
setUp(() {
logger = TestBufferLogger.test();
deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
targetDevices = TargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
});
group('with device not specified', () {
testUsingContext('when single ephemeral attached device', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
expect(devices, <Device>[attachedIOSDevice1]);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
testUsingContext('when single wireless device', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[disconnectedWirelessIOSDevice1];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[connectedWirelessIOSDevice1];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
'''));
expect(devices, <Device>[connectedWirelessIOSDevice1]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
testUsingContext('when multiple but only one attached ephemeral', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[nonEphemeralDevice, attachedIOSDevice1, disconnectedWirelessIOSDevice1];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
expect(devices, <Device>[attachedIOSDevice1]);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
group('with stdinHasTerminal', () {
late FakeTerminal terminal;
setUp(() {
terminal = FakeTerminal(supportsColor: true);
logger = TestBufferLogger.test(terminal: terminal);
});
testUsingContext('when single non-ephemeral attached device', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[nonEphemeralDevice];
final TestTargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices = TestTargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
targetDevices.waitForWirelessBeforeInput = true;
targetDevices.deviceSelection.input = '1';
logger.originalStatusText = '''
Connected devices:
target-device-9 (mobile) • xxx • ios • iOS 16
Checking for wireless devices...
[1]: target-device-9 (xxx)
''';
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Connected devices:
target-device-9 (mobile) • xxx • ios • iOS 16
No wireless devices were found.
[1]: target-device-9 (xxx)
Please choose one (or "q" to quit): '''));
expect(devices, <Device>[nonEphemeralDevice]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
group('without stdinHasTerminal', () {
late FakeTerminal terminal;
setUp(() {
terminal = FakeTerminal(stdinHasTerminal: false);
targetDevices = TargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
});
testUsingContext('when single non-ephemeral attached device', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[nonEphemeralDevice];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
'''));
expect(devices, <Device>[nonEphemeralDevice]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
});
group('with hasSpecifiedDeviceId', () {
setUp(() {
deviceManager.specifiedDeviceId = 'target-device';
});
testUsingContext('when multiple matches but first is unsupported by flutter', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[
exactMatchAttachedUnsupportedIOSDevice,
exactMatchAttachedIOSDevice,
];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
expect(devices, <Device>[exactMatchAttachedIOSDevice]);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
});
testUsingContext('when matching device is unsupported by project', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[exactMatchUnsupportedByProjectDevice];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
expect(devices, <Device>[exactMatchUnsupportedByProjectDevice]);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
});
testUsingContext('when matching attached device', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[exactMatchAttachedIOSDevice, disconnectedWirelessIOSDevice1];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
expect(devices, <Device>[exactMatchAttachedIOSDevice]);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
});
testUsingContext('when exact matching wireless device', () async {
final FakeIOSDevice exactMatchWirelessDevice = FakeIOSDevice.notConnectedWireless(deviceName: 'target-device');
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, exactMatchWirelessDevice];
deviceManager.setDeviceToWaitFor(exactMatchWirelessDevice, DeviceConnectionInterface.wireless);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Waiting for target-device to connect...
'''));
expect(devices, <Device>[exactMatchWirelessDevice]);
expect(devices?.first.isConnected, true);
expect(devices?.first.connectionInterface, DeviceConnectionInterface.wireless);
expect(deviceManager.iosDiscoverer.devicesCalled, 1);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isTrue);
});
testUsingContext('when partially matching single wireless devices', () async {
final FakeIOSDevice partialMatchWirelessDevice = FakeIOSDevice.notConnectedWireless(deviceName: 'target-device-1');
deviceManager.iosDiscoverer.deviceList = <Device>[partialMatchWirelessDevice];
deviceManager.setDeviceToWaitFor(partialMatchWirelessDevice, DeviceConnectionInterface.wireless);
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Waiting for target-device-1 to connect...
'''));
expect(devices, <Device>[partialMatchWirelessDevice]);
expect(devices?.first.isConnected, true);
expect(devices?.first.connectionInterface, DeviceConnectionInterface.wireless);
expect(deviceManager.iosDiscoverer.devicesCalled, 1);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isTrue);
});
testUsingContext('when exact matching an attached device and partial matching a wireless device', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[exactMatchAttachedIOSDevice, connectedWirelessIOSDevice1];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[exactMatchAttachedIOSDevice, connectedWirelessIOSDevice1];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
expect(devices, <Device>[exactMatchAttachedIOSDevice]);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
});
testUsingContext('when partially matching multiple device but only one is connected', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, disconnectedWirelessIOSDevice1];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[attachedIOSDevice1, disconnectedWirelessIOSDevice1];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
'''));
expect(devices, <Device>[attachedIOSDevice1]);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
});
testUsingContext('when partially matching single attached device', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
expect(devices, <Device>[attachedIOSDevice1]);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
});
testUsingContext('when partially matching wireless device and an attached device from different discoverer', () async {
final FakeDevice androidDevice = FakeDevice(deviceName: 'target-device-android');
deviceManager.androidDiscoverer.deviceList = <Device>[androidDevice];
deviceManager.iosDiscoverer.deviceList = <Device>[disconnectedWirelessIOSDevice1];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[disconnectedWirelessIOSDevice1];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
'''));
expect(devices, <Device>[androidDevice]);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
});
testUsingContext('when matching single non-ephemeral attached device', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[nonEphemeralDevice];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals(''));
expect(devices, <Device>[nonEphemeralDevice]);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
});
group('with hasSpecifiedAllDevices', () {
setUp(() {
deviceManager.hasSpecifiedAllDevices = true;
});
testUsingContext('when only one device', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
'''));
expect(devices, <Device>[attachedIOSDevice1]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
testUsingContext('when single non-ephemeral attached device', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[nonEphemeralDevice];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
'''));
expect(devices, <Device>[nonEphemeralDevice]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
});
});
group('finds multiple devices', () {
late TestBufferLogger logger;
late TestDeviceManager deviceManager;
setUp(() {
logger = TestBufferLogger.test();
deviceManager = TestDeviceManager(
logger: logger,
platform: platform,
);
});
group('with device not specified', () {
group('with stdinHasTerminal', () {
late FakeTerminal terminal;
late TestTargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices;
setUp(() {
terminal = FakeTerminal(supportsColor: true);
logger = TestBufferLogger.test(terminal: terminal);
targetDevices = TestTargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
});
testUsingContext('including attached, wireless, unsupported devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[
attachedIOSDevice1,
attachedIOSDevice2,
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
disconnectedWirelessIOSDevice1,
disconnectedWirelessUnsupportedIOSDevice,
disconnectedWirelessUnsupportedForProjectIOSDevice,
];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[
attachedIOSDevice1,
attachedIOSDevice2,
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
connectedWirelessIOSDevice1,
connectedWirelessUnsupportedIOSDevice,
connectedWirelessUnsupportedForProjectIOSDevice,
];
targetDevices.waitForWirelessBeforeInput = true;
targetDevices.deviceSelection.input = '3';
logger.originalStatusText = '''
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
Checking for wireless devices...
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
''';
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
Wirelessly connected devices:
target-device-5 (mobile) • xxx • ios • iOS 16
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
[3]: target-device-5 (xxx)
Please choose one (or "q" to quit): '''));
expect(devices, <Device>[connectedWirelessIOSDevice1]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only attached devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2];
targetDevices.waitForWirelessBeforeInput = true;
targetDevices.deviceSelection.input = '2';
logger.originalStatusText = '''
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
Checking for wireless devices...
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
''';
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
No wireless devices were found.
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
Please choose one (or "q" to quit): '''));
expect(devices, <Device>[attachedIOSDevice2]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only wireless devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[disconnectedWirelessIOSDevice1, disconnectedWirelessIOSDevice2];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[connectedWirelessIOSDevice1, connectedWirelessIOSDevice2];
targetDevices.waitForWirelessBeforeInput = true;
targetDevices.deviceSelection.input = '2';
terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '1');
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
Connected devices:
Wirelessly connected devices:
target-device-5 (mobile) • xxx • ios • iOS 16
target-device-6 (mobile) • xxx • ios • iOS 16
[1]: target-device-5 (xxx)
[2]: target-device-6 (xxx)
'''));
expect(devices, <Device>[connectedWirelessIOSDevice1]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
group('but no color support', () {
setUp(() {
terminal = FakeTerminal();
logger = TestBufferLogger.test(terminal: terminal);
targetDevices = TestTargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
});
testUsingContext('and waits for wireless devices to return', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2, disconnectedWirelessIOSDevice1];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2, connectedWirelessIOSDevice1];
terminal.setPrompt(<String>['1', '2', '3', 'q', 'Q'], '1');
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
Wirelessly connected devices:
target-device-5 (mobile) • xxx • ios • iOS 16
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
[3]: target-device-5 (xxx)
'''));
expect(devices, <Device>[attachedIOSDevice1]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
group('with verbose logging', () {
setUp(() {
logger = TestBufferLogger.test(terminal: terminal, verbose: true);
targetDevices = TestTargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
});
testUsingContext('including only attached devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2];
targetDevices.waitForWirelessBeforeInput = true;
targetDevices.deviceSelection.input = '2';
logger.originalStatusText = '''
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
Checking for wireless devices...
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
''';
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
Checking for wireless devices...
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
No wireless devices were found.
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
Please choose one (or "q" to quit): '''));
expect(devices, <Device>[attachedIOSDevice2]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including attached and wireless devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2, disconnectedWirelessIOSDevice1];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2, connectedWirelessIOSDevice1];
targetDevices.waitForWirelessBeforeInput = true;
targetDevices.deviceSelection.input = '2';
logger.originalStatusText = '''
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
Checking for wireless devices...
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
''';
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
Checking for wireless devices...
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
Connected devices:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
Wirelessly connected devices:
target-device-5 (mobile) • xxx • ios • iOS 16
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
[3]: target-device-5 (xxx)
Please choose one (or "q" to quit): '''));
expect(devices, <Device>[attachedIOSDevice2]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
});
group('without stdinHasTerminal', () {
late FakeTerminal terminal;
late TargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices;
setUp(() {
terminal = FakeTerminal(stdinHasTerminal: false);
targetDevices = TargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
});
testUsingContext('including attached, wireless, unsupported devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[
attachedIOSDevice1,
attachedIOSDevice2,
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
disconnectedWirelessIOSDevice1,
disconnectedWirelessUnsupportedIOSDevice,
disconnectedWirelessUnsupportedForProjectIOSDevice,
];
deviceManager.iosDiscoverer.deviceList = <Device>[
attachedIOSDevice1,
attachedIOSDevice2,
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
connectedWirelessIOSDevice1,
connectedWirelessUnsupportedIOSDevice,
connectedWirelessUnsupportedForProjectIOSDevice,
];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
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 • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
target-device-4 (mobile) • xxx • ios • iOS 16
Wirelessly connected devices:
target-device-5 (mobile) • xxx • ios • iOS 16
target-device-8 (mobile) • xxx • ios • iOS 16
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 4);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only attached devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
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 • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 4);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only wireless devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[disconnectedWirelessIOSDevice1, disconnectedWirelessIOSDevice2];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[connectedWirelessIOSDevice1, connectedWirelessIOSDevice2];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
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 • ios • iOS 16
target-device-6 (mobile) • xxx • ios • iOS 16
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 4);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
});
group('with hasSpecifiedDeviceId', () {
setUp(() {
deviceManager.specifiedDeviceId = 'target-device';
});
group('with stdinHasTerminal', () {
late FakeTerminal terminal;
late TestTargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices;
setUp(() {
terminal = FakeTerminal(supportsColor: true);
logger = TestBufferLogger.test(terminal: terminal);
targetDevices = TestTargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
});
testUsingContext('including attached, wireless, unsupported devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[
attachedIOSDevice1,
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
disconnectedWirelessIOSDevice1,
disconnectedWirelessUnsupportedIOSDevice,
disconnectedWirelessUnsupportedForProjectIOSDevice,
];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[
attachedIOSDevice1,
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
connectedWirelessIOSDevice1,
connectedWirelessUnsupportedIOSDevice,
connectedWirelessUnsupportedForProjectIOSDevice,
];
targetDevices.waitForWirelessBeforeInput = true;
targetDevices.deviceSelection.input = '3';
logger.originalStatusText = '''
Found multiple devices with name or id matching target-device:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-4 (mobile) • xxx • ios • iOS 16
Checking for wireless devices...
[1]: target-device-1 (xxx)
[2]: target-device-4 (xxx)
''';
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Found multiple devices with name or id matching target-device:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-4 (mobile) • xxx • ios • iOS 16
Wirelessly connected devices:
target-device-5 (mobile) • xxx • ios • iOS 16
target-device-8 (mobile) • xxx • ios • iOS 16
[1]: target-device-1 (xxx)
[2]: target-device-4 (xxx)
[3]: target-device-5 (xxx)
[4]: target-device-8 (xxx)
Please choose one (or "q" to quit): '''));
expect(devices, <Device>[connectedWirelessIOSDevice1]);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only attached devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2];
targetDevices.waitForWirelessBeforeInput = true;
targetDevices.deviceSelection.input = '2';
logger.originalStatusText = '''
Found multiple devices with name or id matching target-device:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
Checking for wireless devices...
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
''';
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Found multiple devices with name or id matching target-device:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
No wireless devices were found.
[1]: target-device-1 (xxx)
[2]: target-device-2 (xxx)
Please choose one (or "q" to quit): '''));
expect(devices, <Device>[attachedIOSDevice2]);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only wireless devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[disconnectedWirelessIOSDevice1, disconnectedWirelessIOSDevice2];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[connectedWirelessIOSDevice1, connectedWirelessIOSDevice2];
terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '1');
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
Found 2 devices with name or id matching target-device:
Wirelessly connected devices:
target-device-5 (mobile) • xxx • ios • iOS 16
target-device-6 (mobile) • xxx • ios • iOS 16
[1]: target-device-5 (xxx)
[2]: target-device-6 (xxx)
'''));
expect(devices, <Device>[connectedWirelessIOSDevice1]);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
group('without stdinHasTerminal', () {
late FakeTerminal terminal;
late TargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices;
setUp(() {
terminal = FakeTerminal(stdinHasTerminal: false);
targetDevices = TargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
});
testUsingContext('including only one ephemeral', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[nonEphemeralDevice, attachedIOSDevice1];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
Found 2 devices with name or id matching target-device:
target-device-9 (mobile) • xxx • ios • iOS 16
target-device-1 (mobile) • xxx • ios • iOS 16
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including matching attached, wireless, unsupported devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[
attachedIOSDevice1,
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
disconnectedWirelessIOSDevice1,
disconnectedWirelessUnsupportedIOSDevice,
disconnectedWirelessUnsupportedForProjectIOSDevice,
];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[
attachedIOSDevice1,
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
connectedWirelessIOSDevice1,
connectedWirelessUnsupportedIOSDevice,
connectedWirelessUnsupportedForProjectIOSDevice,
];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
Found 4 devices with name or id matching target-device:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-4 (mobile) • xxx • ios • iOS 16
Wirelessly connected devices:
target-device-5 (mobile) • xxx • ios • iOS 16
target-device-8 (mobile) • xxx • ios • iOS 16
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only attached devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
Found 2 devices with name or id matching target-device:
target-device-1 (mobile) • xxx • ios • iOS 16
target-device-2 (mobile) • xxx • ios • iOS 16
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
testUsingContext('including only wireless devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[disconnectedWirelessIOSDevice1, disconnectedWirelessIOSDevice2];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[connectedWirelessIOSDevice1, connectedWirelessIOSDevice2];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
Found 2 devices with name or id matching target-device:
Wirelessly connected devices:
target-device-5 (mobile) • xxx • ios • iOS 16
target-device-6 (mobile) • xxx • ios • iOS 16
'''));
expect(devices, isNull);
expect(deviceManager.iosDiscoverer.devicesCalled, 3);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
}, overrides: <Type, Generator>{
AnsiTerminal: () => terminal,
});
});
});
group('with hasSpecifiedAllDevices', () {
late TargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices;
setUp(() {
deviceManager.hasSpecifiedAllDevices = true;
targetDevices = TargetDevicesWithExtendedWirelessDeviceDiscovery(
deviceManager: deviceManager,
logger: logger,
);
});
testUsingContext('including attached, wireless, unsupported devices', () async {
deviceManager.otherDiscoverer.deviceList = <Device>[fuchsiaDevice];
deviceManager.iosDiscoverer.deviceList = <Device>[
attachedIOSDevice1,
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
disconnectedWirelessIOSDevice1,
disconnectedWirelessUnsupportedIOSDevice,
disconnectedWirelessUnsupportedForProjectIOSDevice,
];
deviceManager.iosDiscoverer.deviceList = <Device>[
attachedIOSDevice1,
attachedUnsupportedIOSDevice,
attachedUnsupportedForProjectIOSDevice,
connectedWirelessIOSDevice1,
connectedWirelessUnsupportedIOSDevice,
connectedWirelessUnsupportedForProjectIOSDevice,
];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
'''));
expect(devices, <Device>[attachedIOSDevice1, connectedWirelessIOSDevice1]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
testUsingContext('including only attached devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
Checking for wireless devices...
'''));
expect(devices, <Device>[attachedIOSDevice1, attachedIOSDevice2]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
testUsingContext('including only wireless devices', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[disconnectedWirelessIOSDevice1, disconnectedWirelessIOSDevice2];
deviceManager.iosDiscoverer.refreshDeviceList = <Device>[connectedWirelessIOSDevice1, connectedWirelessIOSDevice2];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
No devices found yet. Checking for wireless devices...
'''));
expect(devices, <Device>[connectedWirelessIOSDevice1, connectedWirelessIOSDevice2]);
expect(deviceManager.iosDiscoverer.devicesCalled, 2);
expect(deviceManager.iosDiscoverer.discoverDevicesCalled, 1);
expect(deviceManager.iosDiscoverer.numberOfTimesPolled, 2);
});
});
});
});
}
class TestTargetDevicesWithExtendedWirelessDeviceDiscovery extends TargetDevicesWithExtendedWirelessDeviceDiscovery {
TestTargetDevicesWithExtendedWirelessDeviceDiscovery({
required super.deviceManager,
required super.logger,
}) : _deviceSelection = TestTargetDeviceSelection(logger);
final TestTargetDeviceSelection _deviceSelection;
@override
TestTargetDeviceSelection get deviceSelection => _deviceSelection;
}
class TestTargetDeviceSelection extends TargetDeviceSelection {
TestTargetDeviceSelection(super.logger);
String input = '';
@override
Future<String> readUserInput() async {
return input;
}
}
class TestDeviceManager extends DeviceManager {
TestDeviceManager({
required this.logger,
required this.platform,
}) : super(logger: logger);
final Logger logger;
final Platform platform;
@override
String? specifiedDeviceId;
@override
bool hasSpecifiedAllDevices = false;
final TestPollingDeviceDiscovery androidDiscoverer = TestPollingDeviceDiscovery(
'android',
);
final TestPollingDeviceDiscovery otherDiscoverer = TestPollingDeviceDiscovery(
'other',
);
late final TestIOSDeviceDiscovery iosDiscoverer = TestIOSDeviceDiscovery(
platform: platform,
xcdevice: FakeXcdevice(),
iosWorkflow: FakeIOSWorkflow(),
logger: logger,
);
@override
List<DeviceDiscovery> get deviceDiscoverers {
return <DeviceDiscovery>[
androidDiscoverer,
......@@ -934,6 +2278,21 @@ class TestDeviceManager extends DeviceManager {
iosDiscoverer,
];
}
void setDeviceToWaitFor(
IOSDevice device,
DeviceConnectionInterface connectionInterface,
) {
final XCDeviceEventInterface eventInterface =
connectionInterface == DeviceConnectionInterface.wireless
? XCDeviceEventInterface.wifi
: XCDeviceEventInterface.usb;
iosDiscoverer.xcdevice.waitForDeviceEvent = XCDeviceEventNotification(
XCDeviceEvent.attach,
eventInterface,
device.id,
);
}
}
class TestPollingDeviceDiscovery extends PollingDeviceDiscovery {
......@@ -979,6 +2338,83 @@ class TestPollingDeviceDiscovery extends PollingDeviceDiscovery {
bool get canListAnything => true;
}
class TestIOSDeviceDiscovery extends IOSDevices {
TestIOSDeviceDiscovery({
required super.platform,
required FakeXcdevice xcdevice,
required super.iosWorkflow,
required super.logger,
}) : _platform = platform,
_xcdevice = xcdevice,
super(xcdevice: xcdevice);
final Platform _platform;
List<Device> deviceList = <Device>[];
List<Device> refreshDeviceList = <Device>[];
int devicesCalled = 0;
int discoverDevicesCalled = 0;
int numberOfTimesPolled = 0;
final FakeXcdevice _xcdevice;
@override
FakeXcdevice get xcdevice => _xcdevice;
@override
Future<List<Device>> pollingGetDevices({Duration? timeout}) async {
numberOfTimesPolled++;
if (!_platform.isMacOS) {
throw UnsupportedError(
'Control of iOS devices or simulators only supported on macOS.',
);
}
return deviceList;
}
@override
Future<List<Device>> devices({DeviceDiscoveryFilter? filter}) async {
devicesCalled += 1;
return super.devices(filter: filter);
}
@override
Future<List<Device>> discoverDevices({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) {
discoverDevicesCalled++;
if (refreshDeviceList.isNotEmpty) {
deviceList = refreshDeviceList;
}
return super.discoverDevices(timeout: timeout, filter: filter);
}
@override
bool get canListAnything => true;
}
class FakeXcdevice extends Fake implements XCDevice {
XCDeviceEventNotification? waitForDeviceEvent;
bool waitedForDeviceToConnect = false;
@override
Future<XCDeviceEventNotification?> waitForDeviceToConnect(String deviceId) async {
final XCDeviceEventNotification? waitEvent = waitForDeviceEvent;
if (waitEvent != null) {
waitedForDeviceToConnect = true;
return XCDeviceEventNotification(waitEvent.eventType, waitEvent.eventInterface, waitEvent.deviceIdentifier);
} else {
return null;
}
}
@override
void cancelWaitForDeviceToConnect() {}
}
class FakeIOSWorkflow extends Fake implements IOSWorkflow {}
// 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
......@@ -1078,15 +2514,113 @@ class FakeDevice extends Fake implements Device {
getNameForTargetPlatform(await targetPlatform);
}
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
class FakeIOSDevice extends Fake implements IOSDevice {
FakeIOSDevice({
String? deviceId,
String? deviceName,
bool deviceSupported = true,
bool deviceSupportForProject = true,
this.ephemeral = true,
this.isConnected = true,
this.platformType = PlatformType.ios,
this.connectionInterface = DeviceConnectionInterface.attached,
}) : id = deviceId ?? 'xxx',
name = deviceName ?? 'test',
_isSupported = deviceSupported,
_isSupportedForProject = deviceSupportForProject;
FakeIOSDevice.notConnectedWireless({
String? deviceId,
String? deviceName,
bool deviceSupported = true,
bool deviceSupportForProject = true,
this.ephemeral = true,
this.isConnected = false,
this.platformType = PlatformType.ios,
this.connectionInterface = DeviceConnectionInterface.wireless,
}) : id = deviceId ?? 'xxx',
name = deviceName ?? 'test',
_isSupported = deviceSupported,
_isSupportedForProject = deviceSupportForProject;
FakeIOSDevice.connectedWireless({
String? deviceId,
String? deviceName,
bool deviceSupported = true,
bool deviceSupportForProject = true,
this.ephemeral = true,
this.isConnected = true,
this.platformType = PlatformType.ios,
this.connectionInterface = DeviceConnectionInterface.wireless,
}) : id = deviceId ?? 'xxx',
name = deviceName ?? 'test',
_isSupported = deviceSupported,
_isSupportedForProject = deviceSupportForProject;
final bool _isSupported;
final bool _isSupportedForProject;
@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
final PlatformType? platformType;
@override
Future<String> get sdkNameAndVersion async => 'iOS 16';
@override
Future<bool> get isLocalEmulator async => false;
@override
Category? get category => Category.mobile;
@override
Future<String> get targetPlatformDisplayName async => 'ios';
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester;
}
class FakeTerminal extends Fake implements AnsiTerminal {
FakeTerminal({this.stdinHasTerminal = true});
FakeTerminal({
this.stdinHasTerminal = true,
this.supportsColor = false,
});
@override
final bool stdinHasTerminal;
@override
final bool supportsColor;
@override
bool usesTerminalUi = true;
@override
bool singleCharMode = false;
void setPrompt(List<String> characters, String result) {
_nextPrompt = characters;
_nextResult = result;
......@@ -1106,6 +2640,54 @@ class FakeTerminal extends Fake implements AnsiTerminal {
expect(acceptedCharacters, _nextPrompt);
return _nextResult;
}
@override
String clearLines(int numberOfLines) {
return 'CLEAR_LINES_$numberOfLines';
}
}
class TestBufferLogger extends BufferLogger {
TestBufferLogger.test({
super.terminal,
super.outputPreferences,
super.verbose,
}) : super.test();
String originalStatusText = '';
@override
void printStatus(
String message, {
bool? emphasis,
TerminalColor? color,
bool? newline,
int? indent,
int? hangingIndent,
bool? wrap,
}) {
if (message.startsWith('CLEAR_LINES_')) {
expect(statusText, equals(originalStatusText));
final int numberOfLinesToRemove =
int.parse(message.split('CLEAR_LINES_')[1]) - 1;
final List<String> lines = LineSplitter.split(statusText).toList();
// Clear string buffer and re-add lines not removed
clear();
for (int lineNumber = 0; lineNumber < lines.length - numberOfLinesToRemove; lineNumber++) {
super.printStatus(lines[lineNumber]);
}
} else {
super.printStatus(
message,
emphasis: emphasis,
color: color,
newline: newline,
indent: indent,
hangingIndent: hangingIndent,
wrap: wrap,
);
}
}
}
class FakeDoctor extends Fake implements Doctor {
......
......@@ -218,10 +218,17 @@ class FakeDeviceManager implements DeviceManager {
DeviceDiscoveryFilter? filter,
}) async => filteredDevices(filter);
@override
Future<List<Device>> refreshExtendedWirelessDeviceDiscoverers({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) async => filteredDevices(filter);
@override
Future<List<Device>> getDevicesById(
String deviceId, {
DeviceDiscoveryFilter? filter,
bool waitForDeviceToConnect = false,
}) async {
return filteredDevices(filter).where((Device device) {
return device.id == deviceId || device.id.startsWith(deviceId);
......@@ -231,6 +238,7 @@ class FakeDeviceManager implements DeviceManager {
@override
Future<List<Device>> getDevices({
DeviceDiscoveryFilter? filter,
bool waitForDeviceToConnect = false,
}) {
return hasSpecifiedDeviceId
? getDevicesById(specifiedDeviceId!, filter: filter)
......
......@@ -79,6 +79,33 @@ List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[
},
}
),
FakeDeviceJsonData(
FakeDevice(
'wireless ios',
'wireless-ios',
type:PlatformType.ios,
connectionInterface: DeviceConnectionInterface.wireless,
)
..targetPlatform = Future<TargetPlatform>.value(TargetPlatform.ios)
..sdkNameAndVersion = Future<String>.value('iOS 16'),
<String,Object>{
'name': 'wireless ios',
'id': 'wireless-ios',
'isSupported': true,
'targetPlatform': 'ios',
'emulator': true,
'sdk': 'iOS 16',
'capabilities': <String, Object>{
'hotReload': true,
'hotRestart': true,
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': true,
'startPaused': true,
},
},
),
];
/// Fake device to test `devices` command.
......@@ -167,7 +194,9 @@ class FakeDeviceJsonData {
}
class FakePollingDeviceDiscovery extends PollingDeviceDiscovery {
FakePollingDeviceDiscovery() : super('mock');
FakePollingDeviceDiscovery({
this.requiresExtendedWirelessDeviceDiscovery = false,
}) : super('mock');
final List<Device> _devices = <Device>[];
final StreamController<Device> _onAddedController = StreamController<Device>.broadcast();
......@@ -187,6 +216,9 @@ class FakePollingDeviceDiscovery extends PollingDeviceDiscovery {
@override
bool get canListAnything => true;
@override
bool requiresExtendedWirelessDeviceDiscovery;
void addDevice(Device device) {
_devices.add(device);
_onAddedController.add(device);
......
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