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>{};
......
......@@ -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;
}
......
......@@ -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';
......
......@@ -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>{
......
......@@ -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