mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Update device selection to wait for wireless devices to load (#122932)
Update device selection to wait for wireless devices to load
This commit is contained in:
parent
820ec70a8d
commit
fa01649a59
@ -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,27 +206,14 @@ 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')) {
|
||||
_deviceIdentifierByEvent?.add(<XCDeviceEvent, String>{
|
||||
XCDeviceEvent.detach: identifier,
|
||||
});
|
||||
}
|
||||
final XCDeviceEventNotification? event = _processXCDeviceStdOut(
|
||||
line,
|
||||
XCDeviceEventInterface.usb,
|
||||
);
|
||||
if (event != null) {
|
||||
_deviceIdentifierByEvent?.add(<XCDeviceEvent, String>{
|
||||
event.eventType: event.deviceIdentifier,
|
||||
});
|
||||
}
|
||||
});
|
||||
final StreamSubscription<String> stderrSubscription = _deviceObservationProcess!.stderr
|
||||
@ -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,25 +29,54 @@ void main() {
|
||||
late Cache cache;
|
||||
late Platform platform;
|
||||
|
||||
setUp(() {
|
||||
cache = Cache.test(processManager: FakeProcessManager.any());
|
||||
platform = FakePlatform();
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
testUsingContext('returns 0 when called', () async {
|
||||
final DevicesCommand command = DevicesCommand();
|
||||
await createTestCommandRunner(command).run(<String>['devices']);
|
||||
}, overrides: <Type, Generator>{
|
||||
Cache: () => cache,
|
||||
Artifacts: () => Artifacts.test(),
|
||||
});
|
||||
group('when Platform is not MacOS', () {
|
||||
setUp(() {
|
||||
cache = Cache.test(processManager: FakeProcessManager.any());
|
||||
platform = FakePlatform();
|
||||
});
|
||||
|
||||
testUsingContext('no error when no connected devices', () async {
|
||||
final DevicesCommand command = DevicesCommand();
|
||||
await createTestCommandRunner(command).run(<String>['devices']);
|
||||
expect(
|
||||
testLogger.statusText,
|
||||
equals('''
|
||||
testUsingContext('returns 0 when called', () async {
|
||||
final DevicesCommand command = DevicesCommand();
|
||||
await createTestCommandRunner(command).run(<String>['devices']);
|
||||
}, overrides: <Type, Generator>{
|
||||
Cache: () => cache,
|
||||
Artifacts: () => Artifacts.test(),
|
||||
});
|
||||
|
||||
testUsingContext('no error when no connected devices', () async {
|
||||
final DevicesCommand command = DevicesCommand();
|
||||
await createTestCommandRunner(command).run(<String>['devices']);
|
||||
expect(
|
||||
testLogger.statusText,
|
||||
equals('''
|
||||
No devices detected.
|
||||
|
||||
Run "flutter emulators" to list and start any available device emulators.
|
||||
@ -51,127 +84,534 @@ 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(),
|
||||
});
|
||||
|
||||
group('when includes both attached and wireless devices', () {
|
||||
List<FakeDeviceJsonData>? deviceList;
|
||||
setUp(() {
|
||||
deviceList = <FakeDeviceJsonData>[
|
||||
fakeDevices[0],
|
||||
fakeDevices[1],
|
||||
fakeDevices[2],
|
||||
];
|
||||
});
|
||||
|
||||
testUsingContext("get devices' platform types", () async {
|
||||
final List<String> platformTypes = Device.devicesPlatformTypes(
|
||||
await globals.deviceManager!.getAllDevices(),
|
||||
);
|
||||
expect(platformTypes, <String>['android', 'web']);
|
||||
}, overrides: <Type, Generator>{
|
||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
||||
AndroidSdk: () => null,
|
||||
DeviceManager: () => NoDevicesManager(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
Cache: () => cache,
|
||||
Artifacts: () => Artifacts.test(),
|
||||
Platform: () => platform,
|
||||
});
|
||||
|
||||
testUsingContext('Outputs parsable JSON with --machine flag', () async {
|
||||
group('when includes both attached and wireless devices', () {
|
||||
List<FakeDeviceJsonData>? deviceList;
|
||||
setUp(() {
|
||||
deviceList = <FakeDeviceJsonData>[
|
||||
fakeDevices[0],
|
||||
fakeDevices[1],
|
||||
fakeDevices[2],
|
||||
];
|
||||
});
|
||||
|
||||
testUsingContext("get devices' platform types", () async {
|
||||
final List<String> platformTypes = Device.devicesPlatformTypes(
|
||||
await globals.deviceManager!.getAllDevices(),
|
||||
);
|
||||
expect(platformTypes, <String>['android', 'web']);
|
||||
}, overrides: <Type, Generator>{
|
||||
DeviceManager: () => _FakeDeviceManager(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,
|
||||
],
|
||||
);
|
||||
}, 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)
|
||||
|
||||
1 wirelessly connected device:
|
||||
|
||||
wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2.3) (emulator)
|
||||
|
||||
• Cannot connect to device ABC
|
||||
''');
|
||||
}, overrides: <Type, Generator>{
|
||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
Platform: () => platform,
|
||||
});
|
||||
});
|
||||
|
||||
group('when includes only attached devices', () {
|
||||
List<FakeDeviceJsonData>? deviceList;
|
||||
setUp(() {
|
||||
deviceList = <FakeDeviceJsonData>[
|
||||
fakeDevices[0],
|
||||
fakeDevices[1],
|
||||
];
|
||||
});
|
||||
|
||||
testUsingContext('available devices and diagnostics', () async {
|
||||
final DevicesCommand command = DevicesCommand();
|
||||
await createTestCommandRunner(command).run(<String>['devices']);
|
||||
expect(testLogger.statusText, '''
|
||||
2 connected devices:
|
||||
|
||||
ephemeral (mobile) • ephemeral • android-arm • Test SDK (1.2.3) (emulator)
|
||||
webby (mobile) • webby • web-javascript • Web SDK (1.2.4) (emulator)
|
||||
|
||||
• Cannot connect to device ABC
|
||||
''');
|
||||
}, overrides: <Type, Generator>{
|
||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
Platform: () => platform,
|
||||
});
|
||||
});
|
||||
|
||||
group('when includes only wireless devices', () {
|
||||
List<FakeDeviceJsonData>? deviceList;
|
||||
setUp(() {
|
||||
deviceList = <FakeDeviceJsonData>[
|
||||
fakeDevices[2],
|
||||
];
|
||||
});
|
||||
|
||||
testUsingContext('available devices and diagnostics', () async {
|
||||
final DevicesCommand command = DevicesCommand();
|
||||
await createTestCommandRunner(command).run(<String>['devices']);
|
||||
expect(testLogger.statusText, '''
|
||||
1 wirelessly connected device:
|
||||
|
||||
wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2.3) (emulator)
|
||||
|
||||
• Cannot connect to device ABC
|
||||
''');
|
||||
}, overrides: <Type, Generator>{
|
||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
Platform: () => platform,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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', '--machine']);
|
||||
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(
|
||||
json.decode(testLogger.statusText),
|
||||
<Map<String, Object>>[
|
||||
fakeDevices[0].json,
|
||||
fakeDevices[1].json,
|
||||
fakeDevices[2].json,
|
||||
],
|
||||
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>{
|
||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
||||
AndroidSdk: () => null,
|
||||
DeviceManager: () => NoDevicesManager(),
|
||||
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, '''
|
||||
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)
|
||||
|
||||
1 wirelessly connected device:
|
||||
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,
|
||||
});
|
||||
});
|
||||
}, overrides: <Type, Generator>{
|
||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
Platform: () => platform,
|
||||
});
|
||||
|
||||
group('when includes only attached devices', () {
|
||||
List<FakeDeviceJsonData>? deviceList;
|
||||
setUp(() {
|
||||
deviceList = <FakeDeviceJsonData>[
|
||||
fakeDevices[0],
|
||||
fakeDevices[1],
|
||||
];
|
||||
});
|
||||
group('with ansi terminal', () {
|
||||
late FakeTerminal terminal;
|
||||
late FakeBufferLogger fakeLogger;
|
||||
|
||||
testUsingContext('available devices and diagnostics', () async {
|
||||
final DevicesCommand command = DevicesCommand();
|
||||
await createTestCommandRunner(command).run(<String>['devices']);
|
||||
expect(testLogger.statusText, '''
|
||||
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)
|
||||
|
||||
• Cannot connect to device ABC
|
||||
''');
|
||||
}, overrides: <Type, Generator>{
|
||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
Platform: () => platform,
|
||||
});
|
||||
});
|
||||
Checking for wireless devices...
|
||||
''';
|
||||
});
|
||||
|
||||
group('when includes only wireless devices', () {
|
||||
List<FakeDeviceJsonData>? deviceList;
|
||||
setUp(() {
|
||||
deviceList = <FakeDeviceJsonData>[
|
||||
fakeDevices[2],
|
||||
];
|
||||
});
|
||||
testUsingContext('available devices and diagnostics', () async {
|
||||
final DevicesCommand command = DevicesCommand();
|
||||
await createTestCommandRunner(command).run(<String>['devices']);
|
||||
|
||||
testUsingContext('available devices and diagnostics', () async {
|
||||
final DevicesCommand command = DevicesCommand();
|
||||
await createTestCommandRunner(command).run(<String>['devices']);
|
||||
expect(testLogger.statusText, '''
|
||||
1 wirelessly connected device:
|
||||
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),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
Platform: () => platform,
|
||||
}, 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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -180,8 +620,9 @@ wireless android (mobile) • wireless-android • android-arm • Test SDK (1.2
|
||||
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']
|
||||
@ -215,18 +662,66 @@ class _FakeDeviceManager extends DeviceManager {
|
||||
class NoDevicesManager extends DeviceManager {
|
||||
NoDevicesManager() : super(logger: testLogger);
|
||||
|
||||
@override
|
||||
Future<List<Device>> getAllDevices({
|
||||
DeviceDiscoveryFilter? filter,
|
||||
}) async => <Device>[];
|
||||
|
||||
@override
|
||||
Future<List<Device>> refreshAllDevices({
|
||||
Duration? timeout,
|
||||
DeviceDiscoveryFilter? filter,
|
||||
}) =>
|
||||
getAllDevices();
|
||||
|
||||
@override
|
||||
List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
|
||||
}
|
||||
|
||||
class FakeTerminal extends Fake implements AnsiTerminal {
|
||||
FakeTerminal({
|
||||
this.supportsColor = false,
|
||||
});
|
||||
|
||||
@override
|
||||
final bool supportsColor;
|
||||
|
||||
@override
|
||||
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 TestDeviceManager deviceManager = TestDeviceManager(
|
||||
<Device>[device1],
|
||||
logger: BufferLogger.test(),
|
||||
);
|
||||
expect(await deviceManager.getAllDevices(), <Device>[device1]);
|
||||
final FakePollingDeviceDiscovery notSupportedDiscoverer = FakePollingDeviceDiscovery();
|
||||
final FakePollingDeviceDiscovery supportedDiscoverer = FakePollingDeviceDiscovery(requiresExtendedWirelessDeviceDiscovery: true);
|
||||
|
||||
final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
|
||||
deviceManager.resetDevices(<Device>[device2]);
|
||||
expect(await deviceManager.getAllDevices(), <Device>[device1]);
|
||||
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>[],
|
||||
logger: BufferLogger.test(),
|
||||
deviceDiscoveryOverrides: <DeviceDiscovery>[
|
||||
notSupportedDiscoverer,
|
||||
supportedDiscoverer,
|
||||
],
|
||||
);
|
||||
expect(await deviceManager.getAllDevices(), <Device>[attachedDevice, wirelessDevice]);
|
||||
|
||||
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 TestDeviceManager deviceManager = TestDeviceManager(
|
||||
<Device>[device1],
|
||||
logger: BufferLogger.test(),
|
||||
);
|
||||
expect(await deviceManager.refreshAllDevices(), <Device>[device1]);
|
||||
final FakePollingDeviceDiscovery notSupportedDiscoverer = FakePollingDeviceDiscovery();
|
||||
final FakePollingDeviceDiscovery supportedDiscoverer = FakePollingDeviceDiscovery(requiresExtendedWirelessDeviceDiscovery: true);
|
||||
|
||||
final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
|
||||
deviceManager.resetDevices(<Device>[device2]);
|
||||
expect(await deviceManager.refreshAllDevices(), <Device>[device2]);
|
||||
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>[],
|
||||
logger: BufferLogger.test(),
|
||||
deviceDiscoveryOverrides: <DeviceDiscovery>[
|
||||
notSupportedDiscoverer,
|
||||
supportedDiscoverer,
|
||||
],
|
||||
);
|
||||
expect(await deviceManager.refreshAllDevices(), <Device>[attachedDevice, wirelessDevice]);
|
||||
|
||||
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>{
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user