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,
|
VoidCallback? onFinish,
|
||||||
Duration? timeout,
|
Duration? timeout,
|
||||||
SlowWarningCallback? slowWarningCallback,
|
SlowWarningCallback? slowWarningCallback,
|
||||||
|
TerminalColor? warningColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Send an event to be emitted.
|
/// Send an event to be emitted.
|
||||||
@ -376,11 +377,13 @@ class DelegatingLogger implements Logger {
|
|||||||
VoidCallback? onFinish,
|
VoidCallback? onFinish,
|
||||||
Duration? timeout,
|
Duration? timeout,
|
||||||
SlowWarningCallback? slowWarningCallback,
|
SlowWarningCallback? slowWarningCallback,
|
||||||
|
TerminalColor? warningColor,
|
||||||
}) {
|
}) {
|
||||||
return _delegate.startSpinner(
|
return _delegate.startSpinner(
|
||||||
onFinish: onFinish,
|
onFinish: onFinish,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
slowWarningCallback: slowWarningCallback,
|
slowWarningCallback: slowWarningCallback,
|
||||||
|
warningColor: warningColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,6 +590,7 @@ class StdoutLogger extends Logger {
|
|||||||
VoidCallback? onFinish,
|
VoidCallback? onFinish,
|
||||||
Duration? timeout,
|
Duration? timeout,
|
||||||
SlowWarningCallback? slowWarningCallback,
|
SlowWarningCallback? slowWarningCallback,
|
||||||
|
TerminalColor? warningColor,
|
||||||
}) {
|
}) {
|
||||||
if (_status != null || !supportsColor) {
|
if (_status != null || !supportsColor) {
|
||||||
return SilentStatus(
|
return SilentStatus(
|
||||||
@ -606,6 +610,7 @@ class StdoutLogger extends Logger {
|
|||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
slowWarningCallback: slowWarningCallback,
|
slowWarningCallback: slowWarningCallback,
|
||||||
|
warningColor: warningColor,
|
||||||
)..start();
|
)..start();
|
||||||
return _status!;
|
return _status!;
|
||||||
}
|
}
|
||||||
@ -888,6 +893,7 @@ class BufferLogger extends Logger {
|
|||||||
VoidCallback? onFinish,
|
VoidCallback? onFinish,
|
||||||
Duration? timeout,
|
Duration? timeout,
|
||||||
SlowWarningCallback? slowWarningCallback,
|
SlowWarningCallback? slowWarningCallback,
|
||||||
|
TerminalColor? warningColor,
|
||||||
}) {
|
}) {
|
||||||
return SilentStatus(
|
return SilentStatus(
|
||||||
stopwatch: _stopwatchFactory.createStopwatch(),
|
stopwatch: _stopwatchFactory.createStopwatch(),
|
||||||
@ -1269,6 +1275,7 @@ class AnonymousSpinnerStatus extends Status {
|
|||||||
required Stdio stdio,
|
required Stdio stdio,
|
||||||
required Terminal terminal,
|
required Terminal terminal,
|
||||||
this.slowWarningCallback,
|
this.slowWarningCallback,
|
||||||
|
this.warningColor,
|
||||||
super.timeout,
|
super.timeout,
|
||||||
}) : _stdio = stdio,
|
}) : _stdio = stdio,
|
||||||
_terminal = terminal,
|
_terminal = terminal,
|
||||||
@ -1278,6 +1285,7 @@ class AnonymousSpinnerStatus extends Status {
|
|||||||
final Terminal _terminal;
|
final Terminal _terminal;
|
||||||
String _slowWarning = '';
|
String _slowWarning = '';
|
||||||
final SlowWarningCallback? slowWarningCallback;
|
final SlowWarningCallback? slowWarningCallback;
|
||||||
|
final TerminalColor? warningColor;
|
||||||
|
|
||||||
static const String _backspaceChar = '\b';
|
static const String _backspaceChar = '\b';
|
||||||
static const String _clearChar = ' ';
|
static const String _clearChar = ' ';
|
||||||
@ -1360,8 +1368,15 @@ class AnonymousSpinnerStatus extends Status {
|
|||||||
_clear(_currentLineLength - _lastAnimationFrameLength);
|
_clear(_currentLineLength - _lastAnimationFrameLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_slowWarning == '' && slowWarningCallback != null) {
|
final SlowWarningCallback? callback = slowWarningCallback;
|
||||||
_slowWarning = slowWarningCallback!();
|
if (_slowWarning.isEmpty && callback != null) {
|
||||||
|
final TerminalColor? color = warningColor;
|
||||||
|
if (color != null) {
|
||||||
|
_slowWarning = _terminal.color(callback(), color);
|
||||||
|
} else {
|
||||||
|
_slowWarning = callback();
|
||||||
|
}
|
||||||
|
|
||||||
_writeToStdOut(_slowWarning);
|
_writeToStdOut(_slowWarning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,6 +175,15 @@ class AnsiTerminal implements Terminal {
|
|||||||
static const String yellow = '\u001b[33m';
|
static const String yellow = '\u001b[33m';
|
||||||
static const String grey = '\u001b[90m';
|
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>{
|
static const Map<TerminalColor, String> _colorMap = <TerminalColor, String>{
|
||||||
TerminalColor.red: red,
|
TerminalColor.red: red,
|
||||||
TerminalColor.green: green,
|
TerminalColor.green: green,
|
||||||
@ -268,6 +277,19 @@ class AnsiTerminal implements Terminal {
|
|||||||
@override
|
@override
|
||||||
String clearScreen() => supportsColor ? clear : '\n\n';
|
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
|
@override
|
||||||
bool get singleCharMode {
|
bool get singleCharMode {
|
||||||
if (!_stdio.stdinHasTerminal) {
|
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 get flutterFoundButUnsupportedDevices => 'The following devices were found, but are not supported by this project:';
|
||||||
String flutterFoundSpecifiedDevices(int count, String deviceId) =>
|
String flutterFoundSpecifiedDevices(int count, String deviceId) =>
|
||||||
'Found $count devices with name or id matching $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 flutterChooseDevice(int option, String name, String deviceId) => '[$option]: $name ($deviceId)';
|
||||||
String get flutterChooseOne => 'Please choose one (or "q" to quit)';
|
String get flutterChooseOne => 'Please choose one (or "q" to quit)';
|
||||||
String get flutterSpecifyDeviceWithAllOption =>
|
String get flutterSpecifyDeviceWithAllOption =>
|
||||||
|
@ -175,6 +175,9 @@ known, it can be explicitly provided to attach via the command-line, e.g.
|
|||||||
@override
|
@override
|
||||||
final String category = FlutterCommandCategory.tools;
|
final String category = FlutterCommandCategory.tools;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get refreshWirelessDevices => true;
|
||||||
|
|
||||||
int? get debugPort {
|
int? get debugPort {
|
||||||
if (argResults!['debug-port'] == null) {
|
if (argResults!['debug-port'] == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -878,12 +878,12 @@ class DeviceDomain extends Domain {
|
|||||||
|
|
||||||
final List<PollingDeviceDiscovery> _discoverers = <PollingDeviceDiscovery>[];
|
final List<PollingDeviceDiscovery> _discoverers = <PollingDeviceDiscovery>[];
|
||||||
|
|
||||||
/// Return a list of the current devices, with each device represented as a map
|
/// Return a list of the currently connected devices, with each device
|
||||||
/// of properties (id, name, platform, ...).
|
/// represented as a map of properties (id, name, platform, ...).
|
||||||
Future<List<Map<String, Object?>>> getDevices([ Map<String, Object?>? args ]) async {
|
Future<List<Map<String, Object?>>> getDevices([ Map<String, Object?>? args ]) async {
|
||||||
return <Map<String, Object?>>[
|
return <Map<String, Object?>>[
|
||||||
for (final PollingDeviceDiscovery discoverer in _discoverers)
|
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),
|
await _deviceToMap(device),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -1066,10 +1066,12 @@ class DeviceDomain extends Domain {
|
|||||||
return Future<void>.value();
|
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 {
|
Future<Device?> _getDevice(String? deviceId) async {
|
||||||
for (final PollingDeviceDiscovery discoverer in _discoverers) {
|
for (final PollingDeviceDiscovery discoverer in _discoverers) {
|
||||||
final List<Device> devices = await discoverer.devices();
|
final List<Device> devices = await discoverer.devices(
|
||||||
|
filter: DeviceDiscoveryFilter(),
|
||||||
|
);
|
||||||
Device? device;
|
Device? device;
|
||||||
for (final Device localDevice in devices) {
|
for (final Device localDevice in devices) {
|
||||||
if (localDevice.id == deviceId) {
|
if (localDevice.id == deviceId) {
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
|
import '../base/logger.dart';
|
||||||
|
import '../base/platform.dart';
|
||||||
|
import '../base/terminal.dart';
|
||||||
import '../base/utils.dart';
|
import '../base/utils.dart';
|
||||||
import '../convert.dart';
|
import '../convert.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
@ -63,6 +66,9 @@ class DevicesCommand extends FlutterCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final DevicesCommandOutput output = DevicesCommandOutput(
|
final DevicesCommandOutput output = DevicesCommandOutput(
|
||||||
|
platform: globals.platform,
|
||||||
|
logger: globals.logger,
|
||||||
|
deviceManager: globals.deviceManager,
|
||||||
deviceDiscoveryTimeout: deviceDiscoveryTimeout,
|
deviceDiscoveryTimeout: deviceDiscoveryTimeout,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -75,8 +81,35 @@ class DevicesCommand extends FlutterCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DevicesCommandOutput {
|
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;
|
final Duration? deviceDiscoveryTimeout;
|
||||||
|
|
||||||
Future<List<Device>> _getAttachedDevices(DeviceManager deviceManager) async {
|
Future<List<Device>> _getAttachedDevices(DeviceManager deviceManager) async {
|
||||||
@ -98,7 +131,7 @@ class DevicesCommandOutput {
|
|||||||
Future<void> findAndOutputAllTargetDevices({required bool machine}) async {
|
Future<void> findAndOutputAllTargetDevices({required bool machine}) async {
|
||||||
List<Device> attachedDevices = <Device>[];
|
List<Device> attachedDevices = <Device>[];
|
||||||
List<Device> wirelessDevices = <Device>[];
|
List<Device> wirelessDevices = <Device>[];
|
||||||
final DeviceManager? deviceManager = globals.deviceManager;
|
final DeviceManager? deviceManager = _deviceManager;
|
||||||
if (deviceManager != null) {
|
if (deviceManager != null) {
|
||||||
// Refresh the cache and then get the attached and wireless devices from
|
// Refresh the cache and then get the attached and wireless devices from
|
||||||
// the cache.
|
// the cache.
|
||||||
@ -117,15 +150,15 @@ class DevicesCommandOutput {
|
|||||||
_printNoDevicesDetected();
|
_printNoDevicesDetected();
|
||||||
} else {
|
} else {
|
||||||
if (attachedDevices.isNotEmpty) {
|
if (attachedDevices.isNotEmpty) {
|
||||||
globals.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n');
|
_logger.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n');
|
||||||
await Device.printDevices(attachedDevices, globals.logger);
|
await Device.printDevices(attachedDevices, _logger);
|
||||||
}
|
}
|
||||||
if (wirelessDevices.isNotEmpty) {
|
if (wirelessDevices.isNotEmpty) {
|
||||||
if (attachedDevices.isNotEmpty) {
|
if (attachedDevices.isNotEmpty) {
|
||||||
globals.printStatus('');
|
_logger.printStatus('');
|
||||||
}
|
}
|
||||||
globals.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n');
|
_logger.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n');
|
||||||
await Device.printDevices(wirelessDevices, globals.logger);
|
await Device.printDevices(wirelessDevices, _logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await _printDiagnostics();
|
await _printDiagnostics();
|
||||||
@ -143,24 +176,125 @@ class DevicesCommandOutput {
|
|||||||
}
|
}
|
||||||
status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.');
|
status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.');
|
||||||
|
|
||||||
globals.printStatus(status.toString());
|
_logger.printStatus(status.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _printDiagnostics() async {
|
Future<void> _printDiagnostics() async {
|
||||||
final List<String> diagnostics = await globals.deviceManager?.getDeviceDiagnostics() ?? <String>[];
|
final List<String> diagnostics = await _deviceManager?.getDeviceDiagnostics() ?? <String>[];
|
||||||
if (diagnostics.isNotEmpty) {
|
if (diagnostics.isNotEmpty) {
|
||||||
globals.printStatus('');
|
_logger.printStatus('');
|
||||||
for (final String diagnostic in diagnostics) {
|
for (final String diagnostic in diagnostics) {
|
||||||
globals.printStatus('• $diagnostic', hangingIndent: 2);
|
_logger.printStatus('• $diagnostic', hangingIndent: 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> printDevicesAsJson(List<Device> devices) async {
|
Future<void> printDevicesAsJson(List<Device> devices) async {
|
||||||
globals.printStatus(
|
_logger.printStatus(
|
||||||
const JsonEncoder.withIndent(' ').convert(
|
const JsonEncoder.withIndent(' ').convert(
|
||||||
await Future.wait(devices.map((Device d) => d.toJson()))
|
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
|
@override
|
||||||
final String category = FlutterCommandCategory.tools;
|
final String category = FlutterCommandCategory.tools;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get refreshWirelessDevices => true;
|
||||||
|
|
||||||
Device? device;
|
Device? device;
|
||||||
|
|
||||||
bool get uninstallOnly => boolArg('uninstall-only');
|
bool get uninstallOnly => boolArg('uninstall-only');
|
||||||
|
@ -30,6 +30,9 @@ class LogsCommand extends FlutterCommand {
|
|||||||
@override
|
@override
|
||||||
final String category = FlutterCommandCategory.tools;
|
final String category = FlutterCommandCategory.tools;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get refreshWirelessDevices => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
|
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 uninstallFirst => boolArg('uninstall-first');
|
||||||
bool get enableEmbedderApi => boolArg('enable-embedder-api');
|
bool get enableEmbedderApi => boolArg('enable-embedder-api');
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get refreshWirelessDevices => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get reportNullSafety => true;
|
bool get reportNullSafety => true;
|
||||||
|
|
||||||
|
@ -65,6 +65,9 @@ class ScreenshotCommand extends FlutterCommand {
|
|||||||
@override
|
@override
|
||||||
final String category = FlutterCommandCategory.tools;
|
final String category = FlutterCommandCategory.tools;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get refreshWirelessDevices => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final List<String> aliases = <String>['pic'];
|
final List<String> aliases = <String>['pic'];
|
||||||
|
|
||||||
|
@ -102,6 +102,11 @@ abstract class DeviceManager {
|
|||||||
_specifiedDeviceId = id;
|
_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.
|
/// True when the user has specified a single specific device.
|
||||||
bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
|
bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
|
||||||
|
|
||||||
@ -231,6 +236,22 @@ abstract class DeviceManager {
|
|||||||
return devices.expand<Device>((List<Device> deviceList) => deviceList).toList();
|
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.
|
/// Whether we're capable of listing any devices given the current environment configuration.
|
||||||
bool get canListAnything {
|
bool get canListAnything {
|
||||||
return _platformDiscoverers.any((DeviceDiscovery discoverer) => discoverer.canListAnything);
|
return _platformDiscoverers.any((DeviceDiscovery discoverer) => discoverer.canListAnything);
|
||||||
@ -434,6 +455,10 @@ abstract class DeviceDiscovery {
|
|||||||
/// current environment configuration.
|
/// current environment configuration.
|
||||||
bool get canListAnything;
|
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.
|
/// Return all connected devices, cached on subsequent calls.
|
||||||
Future<List<Device>> devices({DeviceDiscoveryFilter? filter});
|
Future<List<Device>> devices({DeviceDiscoveryFilter? filter});
|
||||||
|
|
||||||
@ -504,6 +529,8 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
|
|||||||
/// Get devices from cache filtered by [filter].
|
/// Get devices from cache filtered by [filter].
|
||||||
///
|
///
|
||||||
/// If the cache is empty, populate the cache.
|
/// If the cache is empty, populate the cache.
|
||||||
|
///
|
||||||
|
/// If [filter] is null, it may return devices that are not connected.
|
||||||
@override
|
@override
|
||||||
Future<List<Device>> devices({DeviceDiscoveryFilter? filter}) {
|
Future<List<Device>> devices({DeviceDiscoveryFilter? filter}) {
|
||||||
return _populateDevices(filter: 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].
|
/// 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].
|
/// 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
|
@override
|
||||||
Future<List<Device>> discoverDevices({
|
Future<List<Device>> discoverDevices({
|
||||||
Duration? timeout,
|
Duration? timeout,
|
||||||
|
@ -680,7 +680,9 @@ class DeviceValidator extends DoctorValidator {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ValidationResult> validate() async {
|
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>[];
|
List<ValidationMessage> installedMessages = <ValidationMessage>[];
|
||||||
if (devices.isNotEmpty) {
|
if (devices.isNotEmpty) {
|
||||||
installedMessages = (await Device.descriptions(devices))
|
installedMessages = (await Device.descriptions(devices))
|
||||||
|
@ -35,26 +35,30 @@ import 'mac.dart';
|
|||||||
class IOSDevices extends PollingDeviceDiscovery {
|
class IOSDevices extends PollingDeviceDiscovery {
|
||||||
IOSDevices({
|
IOSDevices({
|
||||||
required Platform platform,
|
required Platform platform,
|
||||||
required XCDevice xcdevice,
|
required this.xcdevice,
|
||||||
required IOSWorkflow iosWorkflow,
|
required IOSWorkflow iosWorkflow,
|
||||||
required Logger logger,
|
required Logger logger,
|
||||||
}) : _platform = platform,
|
}) : _platform = platform,
|
||||||
_xcdevice = xcdevice,
|
|
||||||
_iosWorkflow = iosWorkflow,
|
_iosWorkflow = iosWorkflow,
|
||||||
_logger = logger,
|
_logger = logger,
|
||||||
super('iOS devices');
|
super('iOS devices');
|
||||||
|
|
||||||
final Platform _platform;
|
final Platform _platform;
|
||||||
final XCDevice _xcdevice;
|
|
||||||
final IOSWorkflow _iosWorkflow;
|
final IOSWorkflow _iosWorkflow;
|
||||||
final Logger _logger;
|
final Logger _logger;
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
final XCDevice xcdevice;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get supportsPlatform => _platform.isMacOS;
|
bool get supportsPlatform => _platform.isMacOS;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get canListAnything => _iosWorkflow.canListDevices;
|
bool get canListAnything => _iosWorkflow.canListDevices;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get requiresExtendedWirelessDeviceDiscovery => true;
|
||||||
|
|
||||||
StreamSubscription<Map<XCDeviceEvent, String>>? _observedDeviceEventsSubscription;
|
StreamSubscription<Map<XCDeviceEvent, String>>? _observedDeviceEventsSubscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -64,18 +68,22 @@ class IOSDevices extends PollingDeviceDiscovery {
|
|||||||
'Control of iOS devices or simulators only supported on macOS.'
|
'Control of iOS devices or simulators only supported on macOS.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!_xcdevice.isInstalled) {
|
if (!xcdevice.isInstalled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceNotifier ??= ItemListNotifier<Device>();
|
deviceNotifier ??= ItemListNotifier<Device>();
|
||||||
|
|
||||||
// Start by populating all currently attached devices.
|
// 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.
|
// cancel any outstanding subscriptions.
|
||||||
await _observedDeviceEventsSubscription?.cancel();
|
await _observedDeviceEventsSubscription?.cancel();
|
||||||
_observedDeviceEventsSubscription = _xcdevice.observedDeviceEvents()?.listen(
|
_observedDeviceEventsSubscription = xcdevice.observedDeviceEvents()?.listen(
|
||||||
_onDeviceEvent,
|
_onDeviceEvent,
|
||||||
onError: (Object error, StackTrace stack) {
|
onError: (Object error, StackTrace stack) {
|
||||||
_logger.printTrace('Process exception running xcdevice observe:\n$error\n$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,
|
// There's no way to get details for an individual attached device,
|
||||||
// so repopulate them all.
|
// so repopulate them all.
|
||||||
final List<Device> devices = await pollingGetDevices();
|
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) {
|
} else if (eventType == XCDeviceEvent.detach && knownDevice != null) {
|
||||||
notifier.removeItem(knownDevice);
|
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
|
@override
|
||||||
@ -139,7 +169,7 @@ class IOSDevices extends PollingDeviceDiscovery {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return _xcdevice.getDiagnostics();
|
return xcdevice.getDiagnostics();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -152,6 +182,7 @@ class IOSDevice extends Device {
|
|||||||
required this.name,
|
required this.name,
|
||||||
required this.cpuArchitecture,
|
required this.cpuArchitecture,
|
||||||
required this.connectionInterface,
|
required this.connectionInterface,
|
||||||
|
required this.isConnected,
|
||||||
String? sdkVersion,
|
String? sdkVersion,
|
||||||
required Platform platform,
|
required Platform platform,
|
||||||
required IOSDeploy iosDeploy,
|
required IOSDeploy iosDeploy,
|
||||||
@ -200,7 +231,15 @@ class IOSDevice extends Device {
|
|||||||
final DarwinArch cpuArchitecture;
|
final DarwinArch cpuArchitecture;
|
||||||
|
|
||||||
@override
|
@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>{};
|
final Map<IOSApp?, DeviceLogReader> _logReaders = <IOSApp?, DeviceLogReader>{};
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
import '../artifacts.dart';
|
import '../artifacts.dart';
|
||||||
@ -23,11 +24,36 @@ import '../ios/mac.dart';
|
|||||||
import '../reporting/reporting.dart';
|
import '../reporting/reporting.dart';
|
||||||
import 'xcode.dart';
|
import 'xcode.dart';
|
||||||
|
|
||||||
|
class XCDeviceEventNotification {
|
||||||
|
XCDeviceEventNotification(
|
||||||
|
this.eventType,
|
||||||
|
this.eventInterface,
|
||||||
|
this.deviceIdentifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
final XCDeviceEvent eventType;
|
||||||
|
final XCDeviceEventInterface eventInterface;
|
||||||
|
final String deviceIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
enum XCDeviceEvent {
|
enum XCDeviceEvent {
|
||||||
attach,
|
attach,
|
||||||
detach,
|
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.
|
/// A utility class for interacting with Xcode xcdevice command line tools.
|
||||||
class XCDevice {
|
class XCDevice {
|
||||||
XCDevice({
|
XCDevice({
|
||||||
@ -61,6 +87,8 @@ class XCDevice {
|
|||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_deviceObservationProcess?.kill();
|
_deviceObservationProcess?.kill();
|
||||||
|
_usbDeviceWaitProcess?.kill();
|
||||||
|
_wifiDeviceWaitProcess?.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
final ProcessUtils _processUtils;
|
final ProcessUtils _processUtils;
|
||||||
@ -74,6 +102,12 @@ class XCDevice {
|
|||||||
Process? _deviceObservationProcess;
|
Process? _deviceObservationProcess;
|
||||||
StreamController<Map<XCDeviceEvent, String>>? _deviceIdentifierByEvent;
|
StreamController<Map<XCDeviceEvent, String>>? _deviceIdentifierByEvent;
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
StreamController<XCDeviceEventNotification>? waitStreamController;
|
||||||
|
|
||||||
|
Process? _usbDeviceWaitProcess;
|
||||||
|
Process? _wifiDeviceWaitProcess;
|
||||||
|
|
||||||
void _setupDeviceIdentifierByEventStream() {
|
void _setupDeviceIdentifierByEventStream() {
|
||||||
// _deviceIdentifierByEvent Should always be available for listeners
|
// _deviceIdentifierByEvent Should always be available for listeners
|
||||||
// in case polling needs to be stopped and restarted.
|
// in case polling needs to be stopped and restarted.
|
||||||
@ -172,27 +206,14 @@ class XCDevice {
|
|||||||
.transform<String>(utf8.decoder)
|
.transform<String>(utf8.decoder)
|
||||||
.transform<String>(const LineSplitter())
|
.transform<String>(const LineSplitter())
|
||||||
.listen((String line) {
|
.listen((String line) {
|
||||||
|
final XCDeviceEventNotification? event = _processXCDeviceStdOut(
|
||||||
// xcdevice observe example output of UDIDs:
|
line,
|
||||||
//
|
XCDeviceEventInterface.usb,
|
||||||
// Listening for all devices, on both interfaces.
|
);
|
||||||
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
if (event != null) {
|
||||||
// Attach: 00008027-00192736010F802E
|
_deviceIdentifierByEvent?.add(<XCDeviceEvent, String>{
|
||||||
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
event.eventType: event.deviceIdentifier,
|
||||||
// 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 StreamSubscription<String> stderrSubscription = _deviceObservationProcess!.stderr
|
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() {
|
void _stopObservingTetheredIOSDevices() {
|
||||||
_deviceObservationProcess?.kill();
|
_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.
|
/// [timeout] defaults to 2 seconds.
|
||||||
Future<List<IOSDevice>> getAvailableIOSDevices({ Duration? timeout }) async {
|
Future<List<IOSDevice>> getAvailableIOSDevices({ Duration? timeout }) async {
|
||||||
final List<Object>? allAvailableDevices = await _getAllDevices(timeout: timeout ?? const Duration(seconds: 2));
|
final List<Object>? allAvailableDevices = await _getAllDevices(timeout: timeout ?? const Duration(seconds: 2));
|
||||||
@ -284,6 +478,7 @@ class XCDevice {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isConnected = true;
|
||||||
final Map<String, Object?>? errorProperties = _errorProperties(device);
|
final Map<String, Object?>? errorProperties = _errorProperties(device);
|
||||||
if (errorProperties != null) {
|
if (errorProperties != null) {
|
||||||
final String? errorMessage = _parseErrorMessage(errorProperties);
|
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.
|
// 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.
|
// Other times this is a false positive and the app will successfully launch despite the error.
|
||||||
if (code != -10) {
|
if (code != -10) {
|
||||||
continue;
|
isConnected = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,6 +513,7 @@ class XCDevice {
|
|||||||
name: name,
|
name: name,
|
||||||
cpuArchitecture: _cpuArchitecture(device),
|
cpuArchitecture: _cpuArchitecture(device),
|
||||||
connectionInterface: _interfaceType(device),
|
connectionInterface: _interfaceType(device),
|
||||||
|
isConnected: isConnected,
|
||||||
sdkVersion: sdkVersion,
|
sdkVersion: sdkVersion,
|
||||||
iProxy: _iProxy,
|
iProxy: _iProxy,
|
||||||
fileSystem: globals.fs,
|
fileSystem: globals.fs,
|
||||||
@ -329,7 +525,6 @@ class XCDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return devices;
|
return devices;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Despite the name, com.apple.platform.iphoneos includes iPhone, iPads, and all iOS 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;
|
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
|
@override
|
||||||
bool get hidden => deprecated;
|
bool get hidden => deprecated;
|
||||||
|
|
||||||
@ -719,6 +724,7 @@ abstract class FlutterCommand extends Command<void> {
|
|||||||
}();
|
}();
|
||||||
|
|
||||||
late final TargetDevices _targetDevices = TargetDevices(
|
late final TargetDevices _targetDevices = TargetDevices(
|
||||||
|
platform: globals.platform,
|
||||||
deviceManager: globals.deviceManager!,
|
deviceManager: globals.deviceManager!,
|
||||||
logger: globals.logger,
|
logger: globals.logger,
|
||||||
);
|
);
|
||||||
@ -1466,6 +1472,14 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
|
|||||||
}
|
}
|
||||||
|
|
||||||
globals.preRunValidator.validate();
|
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
|
// 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.
|
// sky_engine package is available in the flutter cache for pub to find.
|
||||||
if (shouldUpdateCache) {
|
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
|
/// 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 {
|
mixin DeviceBasedDevelopmentArtifacts on FlutterCommand {
|
||||||
@override
|
@override
|
||||||
Future<Set<DevelopmentArtifact>> get requiredArtifacts async {
|
Future<Set<DevelopmentArtifact>> get requiredArtifacts async {
|
||||||
// If there are no attached devices, use the default configuration.
|
// If there are no devices, use the default configuration.
|
||||||
// Otherwise, only add development artifacts which correspond to a
|
// Otherwise, only add development artifacts corresponding to
|
||||||
// connected device.
|
// potentially connected devices. We might not be able to determine if a
|
||||||
final List<Device> devices = await globals.deviceManager!.getDevices();
|
// 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) {
|
if (devices.isEmpty) {
|
||||||
return super.requiredArtifacts;
|
return super.requiredArtifacts;
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,49 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
import '../base/logger.dart';
|
import '../base/logger.dart';
|
||||||
|
import '../base/platform.dart';
|
||||||
|
import '../base/terminal.dart';
|
||||||
import '../base/user_messages.dart';
|
import '../base/user_messages.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
import '../globals.dart' as globals;
|
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:';
|
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.
|
/// This class handles functionality of finding and selecting target devices.
|
||||||
///
|
///
|
||||||
/// Target devices are devices that are supported and selectable to run
|
/// Target devices are devices that are supported and selectable to run
|
||||||
/// a flutter application on.
|
/// a flutter application on.
|
||||||
class TargetDevices {
|
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 DeviceManager deviceManager,
|
||||||
required Logger logger,
|
required Logger logger,
|
||||||
}) : _deviceManager = deviceManager,
|
}) : _deviceManager = deviceManager,
|
||||||
@ -48,9 +77,11 @@ class TargetDevices {
|
|||||||
|
|
||||||
Future<List<Device>> _getDeviceById({
|
Future<List<Device>> _getDeviceById({
|
||||||
bool includeDevicesUnsupportedByProject = false,
|
bool includeDevicesUnsupportedByProject = false,
|
||||||
|
bool includeDisconnected = false,
|
||||||
}) async {
|
}) async {
|
||||||
return _deviceManager.getDevices(
|
return _deviceManager.getDevices(
|
||||||
filter: DeviceDiscoveryFilter(
|
filter: DeviceDiscoveryFilter(
|
||||||
|
excludeDisconnected: !includeDisconnected,
|
||||||
supportFilter: _deviceManager.deviceSupportFilter(
|
supportFilter: _deviceManager.deviceSupportFilter(
|
||||||
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
|
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
|
/// Find and return all target [Device]s based upon criteria entered by the
|
||||||
/// user on the command line.
|
/// user on the command line.
|
||||||
///
|
///
|
||||||
@ -235,7 +270,7 @@ class TargetDevices {
|
|||||||
_deviceManager.specifiedDeviceId!,
|
_deviceManager.specifiedDeviceId!,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
_logger.printStatus(userMessages.flutterMultipleDevicesFound);
|
_logger.printStatus(_connectedDevicesMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Device.printDevices(attachedDevices, _logger);
|
await Device.printDevices(attachedDevices, _logger);
|
||||||
@ -249,7 +284,8 @@ class TargetDevices {
|
|||||||
|
|
||||||
final Device chosenDevice = await _chooseOneOfAvailableDevices(allDevices);
|
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;
|
_deviceManager.specifiedDeviceId = chosenDevice.id;
|
||||||
|
|
||||||
return <Device>[chosenDevice];
|
return <Device>[chosenDevice];
|
||||||
@ -302,3 +338,406 @@ class TargetDevices {
|
|||||||
return result;
|
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,
|
VoidCallback? onFinish,
|
||||||
Duration? timeout,
|
Duration? timeout,
|
||||||
SlowWarningCallback? slowWarningCallback,
|
SlowWarningCallback? slowWarningCallback,
|
||||||
|
TerminalColor? warningColor,
|
||||||
}) {
|
}) {
|
||||||
return SilentStatus(
|
return SilentStatus(
|
||||||
stopwatch: Stopwatch(),
|
stopwatch: Stopwatch(),
|
||||||
@ -1353,6 +1354,9 @@ class FakeIOSDevice extends Fake implements IOSDevice {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool get isConnected => true;
|
bool get isConnected => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get ephemeral => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeMDnsClient extends Fake implements MDnsClient {
|
class FakeMDnsClient extends Fake implements MDnsClient {
|
||||||
|
@ -850,6 +850,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
|
|||||||
@override
|
@override
|
||||||
final bool ephemeral = false;
|
final bool ephemeral = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool isConnected = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> get sdkNameAndVersion async => 'Android 12';
|
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/android/android_sdk.dart';
|
||||||
import 'package:flutter_tools/src/artifacts.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/platform.dart';
|
||||||
|
import 'package:flutter_tools/src/base/terminal.dart';
|
||||||
import 'package:flutter_tools/src/cache.dart';
|
import 'package:flutter_tools/src/cache.dart';
|
||||||
import 'package:flutter_tools/src/commands/devices.dart';
|
import 'package:flutter_tools/src/commands/devices.dart';
|
||||||
import 'package:flutter_tools/src/device.dart';
|
import 'package:flutter_tools/src/device.dart';
|
||||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||||
|
import 'package:test/fake.dart';
|
||||||
|
|
||||||
|
import '../../src/common.dart';
|
||||||
import '../../src/context.dart';
|
import '../../src/context.dart';
|
||||||
import '../../src/fake_devices.dart';
|
import '../../src/fake_devices.dart';
|
||||||
import '../../src/test_flutter_command_runner.dart';
|
import '../../src/test_flutter_command_runner.dart';
|
||||||
@ -25,25 +29,54 @@ void main() {
|
|||||||
late Cache cache;
|
late Cache cache;
|
||||||
late Platform platform;
|
late Platform platform;
|
||||||
|
|
||||||
setUp(() {
|
group('ensure factory', () {
|
||||||
cache = Cache.test(processManager: FakeProcessManager.any());
|
late FakeBufferLogger fakeLogger;
|
||||||
platform = FakePlatform();
|
|
||||||
|
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 {
|
group('when Platform is not MacOS', () {
|
||||||
final DevicesCommand command = DevicesCommand();
|
setUp(() {
|
||||||
await createTestCommandRunner(command).run(<String>['devices']);
|
cache = Cache.test(processManager: FakeProcessManager.any());
|
||||||
}, overrides: <Type, Generator>{
|
platform = FakePlatform();
|
||||||
Cache: () => cache,
|
});
|
||||||
Artifacts: () => Artifacts.test(),
|
|
||||||
});
|
|
||||||
|
|
||||||
testUsingContext('no error when no connected devices', () async {
|
testUsingContext('returns 0 when called', () async {
|
||||||
final DevicesCommand command = DevicesCommand();
|
final DevicesCommand command = DevicesCommand();
|
||||||
await createTestCommandRunner(command).run(<String>['devices']);
|
await createTestCommandRunner(command).run(<String>['devices']);
|
||||||
expect(
|
}, overrides: <Type, Generator>{
|
||||||
testLogger.statusText,
|
Cache: () => cache,
|
||||||
equals('''
|
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.
|
No devices detected.
|
||||||
|
|
||||||
Run "flutter emulators" to list and start any available device emulators.
|
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.
|
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>{
|
}, overrides: <Type, Generator>{
|
||||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
AndroidSdk: () => null,
|
||||||
|
DeviceManager: () => NoDevicesManager(),
|
||||||
ProcessManager: () => FakeProcessManager.any(),
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
Cache: () => cache,
|
Cache: () => cache,
|
||||||
Artifacts: () => Artifacts.test(),
|
Artifacts: () => Artifacts.test(),
|
||||||
Platform: () => platform,
|
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();
|
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(
|
expect(
|
||||||
json.decode(testLogger.statusText),
|
testLogger.statusText,
|
||||||
<Map<String, Object>>[
|
equals('''
|
||||||
fakeDevices[0].json,
|
No devices found yet. Checking for wireless devices...
|
||||||
fakeDevices[1].json,
|
|
||||||
fakeDevices[2].json,
|
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>{
|
}, overrides: <Type, Generator>{
|
||||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
AndroidSdk: () => null,
|
||||||
|
DeviceManager: () => NoDevicesManager(),
|
||||||
ProcessManager: () => FakeProcessManager.any(),
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
Cache: () => cache,
|
Cache: () => cache,
|
||||||
Artifacts: () => Artifacts.test(),
|
Artifacts: () => Artifacts.test(),
|
||||||
Platform: () => platform,
|
Platform: () => platform,
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('available devices and diagnostics', () async {
|
group('when includes both attached and wireless devices', () {
|
||||||
final DevicesCommand command = DevicesCommand();
|
List<FakeDeviceJsonData>? deviceList;
|
||||||
await createTestCommandRunner(command).run(<String>['devices']);
|
setUp(() {
|
||||||
expect(testLogger.statusText, '''
|
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:
|
2 connected devices:
|
||||||
|
|
||||||
ephemeral (mobile) • ephemeral • android-arm • Test SDK (1.2.3) (emulator)
|
ephemeral (mobile) • ephemeral • android-arm • Test SDK (1.2.3) (emulator)
|
||||||
webby (mobile) • webby • web-javascript • Web SDK (1.2.4) (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 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
|
• Cannot connect to device ABC
|
||||||
''');
|
''');
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
||||||
ProcessManager: () => FakeProcessManager.any(),
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
Platform: () => platform,
|
Platform: () => platform,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
group('when includes only attached devices', () {
|
group('with ansi terminal', () {
|
||||||
List<FakeDeviceJsonData>? deviceList;
|
late FakeTerminal terminal;
|
||||||
setUp(() {
|
late FakeBufferLogger fakeLogger;
|
||||||
deviceList = <FakeDeviceJsonData>[
|
|
||||||
fakeDevices[0],
|
|
||||||
fakeDevices[1],
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
testUsingContext('available devices and diagnostics', () async {
|
setUp(() {
|
||||||
final DevicesCommand command = DevicesCommand();
|
terminal = FakeTerminal(supportsColor: true);
|
||||||
await createTestCommandRunner(command).run(<String>['devices']);
|
fakeLogger = FakeBufferLogger(terminal: terminal);
|
||||||
expect(testLogger.statusText, '''
|
fakeLogger.originalStatusText = '''
|
||||||
2 connected devices:
|
2 connected devices:
|
||||||
|
|
||||||
ephemeral (mobile) • ephemeral • android-arm • Test SDK (1.2.3) (emulator)
|
ephemeral (mobile) • ephemeral • android-arm • Test SDK (1.2.3) (emulator)
|
||||||
webby (mobile) • webby • web-javascript • Web SDK (1.2.4) (emulator)
|
webby (mobile) • webby • web-javascript • Web SDK (1.2.4) (emulator)
|
||||||
|
|
||||||
• Cannot connect to device ABC
|
Checking for wireless devices...
|
||||||
''');
|
''';
|
||||||
}, overrides: <Type, Generator>{
|
});
|
||||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
|
||||||
ProcessManager: () => FakeProcessManager.any(),
|
|
||||||
Platform: () => platform,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('when includes only wireless devices', () {
|
testUsingContext('available devices and diagnostics', () async {
|
||||||
List<FakeDeviceJsonData>? deviceList;
|
final DevicesCommand command = DevicesCommand();
|
||||||
setUp(() {
|
await createTestCommandRunner(command).run(<String>['devices']);
|
||||||
deviceList = <FakeDeviceJsonData>[
|
|
||||||
fakeDevices[2],
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
testUsingContext('available devices and diagnostics', () async {
|
expect(fakeLogger.statusText, '''
|
||||||
final DevicesCommand command = DevicesCommand();
|
2 connected devices:
|
||||||
await createTestCommandRunner(command).run(<String>['devices']);
|
|
||||||
expect(testLogger.statusText, '''
|
ephemeral (mobile) • ephemeral • android-arm • Test SDK (1.2.3) (emulator)
|
||||||
1 wirelessly connected device:
|
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 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
|
• Cannot connect to device ABC
|
||||||
''');
|
''');
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
|
DeviceManager: () =>
|
||||||
ProcessManager: () => FakeProcessManager.any(),
|
_FakeDeviceManager(devices: deviceList, logger: fakeLogger),
|
||||||
Platform: () => platform,
|
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 {
|
class _FakeDeviceManager extends DeviceManager {
|
||||||
_FakeDeviceManager({
|
_FakeDeviceManager({
|
||||||
List<FakeDeviceJsonData>? devices,
|
List<FakeDeviceJsonData>? devices,
|
||||||
|
FakeBufferLogger? logger,
|
||||||
}) : fakeDevices = devices ?? <FakeDeviceJsonData>[],
|
}) : fakeDevices = devices ?? <FakeDeviceJsonData>[],
|
||||||
super(logger: testLogger);
|
super(logger: logger ?? testLogger);
|
||||||
|
|
||||||
List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[];
|
List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[];
|
||||||
|
|
||||||
@ -203,6 +644,12 @@ class _FakeDeviceManager extends DeviceManager {
|
|||||||
DeviceDiscoveryFilter? filter,
|
DeviceDiscoveryFilter? filter,
|
||||||
}) => getAllDevices(filter: filter);
|
}) => getAllDevices(filter: filter);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Device>> refreshExtendedWirelessDeviceDiscoverers({
|
||||||
|
Duration? timeout,
|
||||||
|
DeviceDiscoveryFilter? filter,
|
||||||
|
}) => getAllDevices(filter: filter);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> getDeviceDiagnostics() => Future<List<String>>.value(
|
Future<List<String>> getDeviceDiagnostics() => Future<List<String>>.value(
|
||||||
<String>['Cannot connect to device ABC']
|
<String>['Cannot connect to device ABC']
|
||||||
@ -215,18 +662,66 @@ class _FakeDeviceManager extends DeviceManager {
|
|||||||
class NoDevicesManager extends DeviceManager {
|
class NoDevicesManager extends DeviceManager {
|
||||||
NoDevicesManager() : super(logger: testLogger);
|
NoDevicesManager() : super(logger: testLogger);
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Device>> getAllDevices({
|
|
||||||
DeviceDiscoveryFilter? filter,
|
|
||||||
}) async => <Device>[];
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Device>> refreshAllDevices({
|
|
||||||
Duration? timeout,
|
|
||||||
DeviceDiscoveryFilter? filter,
|
|
||||||
}) =>
|
|
||||||
getAllDevices();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
|
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,
|
DeviceDiscoveryFilter? filter,
|
||||||
}) async => devices;
|
}) async => devices;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Device>> refreshAllDevices({
|
||||||
|
Duration? timeout,
|
||||||
|
DeviceDiscoveryFilter? filter,
|
||||||
|
}) async => devices;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> getDeviceDiagnostics() async => diagnostics;
|
Future<List<String>> getDeviceDiagnostics() async => diagnostics;
|
||||||
}
|
}
|
||||||
|
@ -543,6 +543,9 @@ class ScreenshotDevice extends Fake implements Device {
|
|||||||
@override
|
@override
|
||||||
bool supportsScreenshot = true;
|
bool supportsScreenshot = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isConnected => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LaunchResult> startApp(
|
Future<LaunchResult> startApp(
|
||||||
ApplicationPackage? package, {
|
ApplicationPackage? package, {
|
||||||
|
@ -268,6 +268,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
|
|||||||
@override
|
@override
|
||||||
final bool ephemeral = false;
|
final bool ephemeral = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isConnected => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> get sdkNameAndVersion async => 'Android 12';
|
Future<String> get sdkNameAndVersion async => 'Android 12';
|
||||||
|
|
||||||
|
@ -1170,6 +1170,9 @@ class FakeDevice extends Fake implements Device {
|
|||||||
@override
|
@override
|
||||||
bool get supportsFastStart => false;
|
bool get supportsFastStart => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get ephemeral => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get isConnected => true;
|
bool get isConnected => true;
|
||||||
|
|
||||||
|
@ -480,7 +480,7 @@ void main() {
|
|||||||
expect(done, isTrue);
|
expect(done, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('AnonymousSpinnerStatus logs warning after timeout', () async {
|
testWithoutContext('AnonymousSpinnerStatus logs warning after timeout without color support', () async {
|
||||||
mockStopwatch = FakeStopwatch();
|
mockStopwatch = FakeStopwatch();
|
||||||
const String warningMessage = 'a warning message.';
|
const String warningMessage = 'a warning message.';
|
||||||
final bool done = FakeAsync().run<bool>((FakeAsync time) {
|
final bool done = FakeAsync().run<bool>((FakeAsync time) {
|
||||||
@ -489,6 +489,7 @@ void main() {
|
|||||||
stopwatch: mockStopwatch,
|
stopwatch: mockStopwatch,
|
||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
slowWarningCallback: () => warningMessage,
|
slowWarningCallback: () => warningMessage,
|
||||||
|
warningColor: TerminalColor.red,
|
||||||
timeout: const Duration(milliseconds: 100),
|
timeout: const Duration(milliseconds: 100),
|
||||||
)..start();
|
)..start();
|
||||||
// must be greater than the spinner timer duration
|
// must be greater than the spinner timer duration
|
||||||
@ -497,6 +498,7 @@ void main() {
|
|||||||
time.elapse(timeLapse);
|
time.elapse(timeLapse);
|
||||||
|
|
||||||
List<String> lines = outputStdout();
|
List<String> lines = outputStdout();
|
||||||
|
expect(lines.join().contains(RegExp(red)), isFalse);
|
||||||
expect(lines.join(), '⣽\ba warning message.⣻');
|
expect(lines.join(), '⣽\ba warning message.⣻');
|
||||||
|
|
||||||
spinner.stop();
|
spinner.stop();
|
||||||
@ -506,6 +508,35 @@ void main() {
|
|||||||
expect(done, isTrue);
|
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 {
|
testWithoutContext('Stdout startProgress on colored terminal', () async {
|
||||||
final Logger logger = StdoutLogger(
|
final Logger logger = StdoutLogger(
|
||||||
terminal: coloredTerminal,
|
terminal: coloredTerminal,
|
||||||
|
@ -34,7 +34,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('ANSI coloring and bold', () {
|
group('ANSI coloring, bold, and clearing', () {
|
||||||
late AnsiTerminal terminal;
|
late AnsiTerminal terminal;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
@ -103,6 +103,39 @@ void main() {
|
|||||||
equals('${AnsiTerminal.bold}bold output still bold${AnsiTerminal.resetBold}'),
|
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', () {
|
group('character input prompt', () {
|
||||||
|
@ -141,29 +141,92 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('getAllDevices caches', () async {
|
testWithoutContext('getAllDevices caches', () async {
|
||||||
final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f');
|
final FakePollingDeviceDiscovery notSupportedDiscoverer = FakePollingDeviceDiscovery();
|
||||||
final TestDeviceManager deviceManager = TestDeviceManager(
|
final FakePollingDeviceDiscovery supportedDiscoverer = FakePollingDeviceDiscovery(requiresExtendedWirelessDeviceDiscovery: true);
|
||||||
<Device>[device1],
|
|
||||||
logger: BufferLogger.test(),
|
|
||||||
);
|
|
||||||
expect(await deviceManager.getAllDevices(), <Device>[device1]);
|
|
||||||
|
|
||||||
final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
|
final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f');
|
||||||
deviceManager.resetDevices(<Device>[device2]);
|
final FakeDevice wirelessDevice = FakeDevice('Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
|
||||||
expect(await deviceManager.getAllDevices(), <Device>[device1]);
|
|
||||||
|
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 {
|
testWithoutContext('refreshAllDevices does not cache', () async {
|
||||||
final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f');
|
final FakePollingDeviceDiscovery notSupportedDiscoverer = FakePollingDeviceDiscovery();
|
||||||
final TestDeviceManager deviceManager = TestDeviceManager(
|
final FakePollingDeviceDiscovery supportedDiscoverer = FakePollingDeviceDiscovery(requiresExtendedWirelessDeviceDiscovery: true);
|
||||||
<Device>[device1],
|
|
||||||
logger: BufferLogger.test(),
|
|
||||||
);
|
|
||||||
expect(await deviceManager.refreshAllDevices(), <Device>[device1]);
|
|
||||||
|
|
||||||
final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e');
|
final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f');
|
||||||
deviceManager.resetDevices(<Device>[device2]);
|
final FakeDevice wirelessDevice = FakeDevice('Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
|
||||||
expect(await deviceManager.refreshAllDevices(), <Device>[device2]);
|
|
||||||
|
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 {
|
class TestDeviceDiscoverySupportFilter extends DeviceDiscoverySupportFilter {
|
||||||
TestDeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutterOrProject({
|
TestDeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutterOrProject({
|
||||||
required super.flutterProject,
|
required super.flutterProject,
|
||||||
|
@ -75,6 +75,7 @@ void main() {
|
|||||||
sdkVersion: '13.3',
|
sdkVersion: '13.3',
|
||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
expect(device.isSupported(), isTrue);
|
expect(device.isSupported(), isTrue);
|
||||||
});
|
});
|
||||||
@ -91,6 +92,7 @@ void main() {
|
|||||||
name: 'iPhone 1',
|
name: 'iPhone 1',
|
||||||
cpuArchitecture: DarwinArch.armv7,
|
cpuArchitecture: DarwinArch.armv7,
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
expect(device.isSupported(), isFalse);
|
expect(device.isSupported(), isFalse);
|
||||||
});
|
});
|
||||||
@ -108,6 +110,7 @@ void main() {
|
|||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
sdkVersion: '1.0.0',
|
sdkVersion: '1.0.0',
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
).majorSdkVersion, 1);
|
).majorSdkVersion, 1);
|
||||||
expect(IOSDevice(
|
expect(IOSDevice(
|
||||||
'device-123',
|
'device-123',
|
||||||
@ -121,6 +124,7 @@ void main() {
|
|||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
sdkVersion: '13.1.1',
|
sdkVersion: '13.1.1',
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
).majorSdkVersion, 13);
|
).majorSdkVersion, 13);
|
||||||
expect(IOSDevice(
|
expect(IOSDevice(
|
||||||
'device-123',
|
'device-123',
|
||||||
@ -134,6 +138,7 @@ void main() {
|
|||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
sdkVersion: '10',
|
sdkVersion: '10',
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
).majorSdkVersion, 10);
|
).majorSdkVersion, 10);
|
||||||
expect(IOSDevice(
|
expect(IOSDevice(
|
||||||
'device-123',
|
'device-123',
|
||||||
@ -147,6 +152,7 @@ void main() {
|
|||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
sdkVersion: '0',
|
sdkVersion: '0',
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
).majorSdkVersion, 0);
|
).majorSdkVersion, 0);
|
||||||
expect(IOSDevice(
|
expect(IOSDevice(
|
||||||
'device-123',
|
'device-123',
|
||||||
@ -160,6 +166,7 @@ void main() {
|
|||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
sdkVersion: 'bogus',
|
sdkVersion: 'bogus',
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
).majorSdkVersion, 0);
|
).majorSdkVersion, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -176,6 +183,7 @@ void main() {
|
|||||||
sdkVersion: '13.3 17C54',
|
sdkVersion: '13.3 17C54',
|
||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await device.sdkNameAndVersion,'iOS 13.3 17C54');
|
expect(await device.sdkNameAndVersion,'iOS 13.3 17C54');
|
||||||
@ -194,6 +202,7 @@ void main() {
|
|||||||
sdkVersion: '13.3',
|
sdkVersion: '13.3',
|
||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(device.supportsRuntimeMode(BuildMode.debug), true);
|
expect(device.supportsRuntimeMode(BuildMode.debug), true);
|
||||||
@ -218,6 +227,7 @@ void main() {
|
|||||||
sdkVersion: '13.3',
|
sdkVersion: '13.3',
|
||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
throwsAssertionError,
|
throwsAssertionError,
|
||||||
@ -308,6 +318,7 @@ void main() {
|
|||||||
sdkVersion: '13.3',
|
sdkVersion: '13.3',
|
||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
logReader1 = createLogReader(device, appPackage1, process1);
|
logReader1 = createLogReader(device, appPackage1, process1);
|
||||||
logReader2 = createLogReader(device, appPackage2, process2);
|
logReader2 = createLogReader(device, appPackage2, process2);
|
||||||
@ -369,6 +380,7 @@ void main() {
|
|||||||
platform: macPlatform,
|
platform: macPlatform,
|
||||||
fileSystem: MemoryFileSystem.test(),
|
fileSystem: MemoryFileSystem.test(),
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
device2 = IOSDevice(
|
device2 = IOSDevice(
|
||||||
@ -383,6 +395,7 @@ void main() {
|
|||||||
platform: macPlatform,
|
platform: macPlatform,
|
||||||
fileSystem: MemoryFileSystem.test(),
|
fileSystem: MemoryFileSystem.test(),
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -587,6 +600,120 @@ void main() {
|
|||||||
expect(diagnostics.first, 'Generic pairing error');
|
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 {
|
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<List<IOSDevice>> devices = <List<IOSDevice>>[];
|
||||||
final List<String> diagnostics = <String>[];
|
final List<String> diagnostics = <String>[];
|
||||||
StreamController<Map<XCDeviceEvent, String>> deviceEventController = StreamController<Map<XCDeviceEvent, String>>();
|
StreamController<Map<XCDeviceEvent, String>> deviceEventController = StreamController<Map<XCDeviceEvent, String>>();
|
||||||
|
XCDeviceEventNotification? waitForDeviceEvent;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isInstalled = true;
|
bool isInstalled = true;
|
||||||
@ -621,6 +749,16 @@ class FakeXcdevice extends Fake implements XCDevice {
|
|||||||
Future<List<IOSDevice>> getAvailableIOSDevices({Duration? timeout}) async {
|
Future<List<IOSDevice>> getAvailableIOSDevices({Duration? timeout}) async {
|
||||||
return devices[getAvailableIOSDevicesCount++];
|
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 {
|
class FakeProcess extends Fake implements Process {
|
||||||
|
@ -359,5 +359,6 @@ IOSDevice setUpIOSDevice({
|
|||||||
),
|
),
|
||||||
iProxy: IProxy.test(logger: logger, processManager: processManager),
|
iProxy: IProxy.test(logger: logger, processManager: processManager),
|
||||||
connectionInterface: interfaceType ?? DeviceConnectionInterface.attached,
|
connectionInterface: interfaceType ?? DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -100,5 +100,6 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
|
|||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
iProxy: IProxy.test(logger: logger, processManager: processManager),
|
iProxy: IProxy.test(logger: logger, processManager: processManager),
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -338,6 +338,7 @@ IOSDevice setUpIOSDevice({
|
|||||||
),
|
),
|
||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
connectionInterface: DeviceConnectionInterface.attached,
|
connectionInterface: DeviceConnectionInterface.attached,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,6 +599,7 @@ IOSDevice setUpIOSDevice({
|
|||||||
),
|
),
|
||||||
cpuArchitecture: DarwinArch.arm64,
|
cpuArchitecture: DarwinArch.arm64,
|
||||||
connectionInterface: interfaceType,
|
connectionInterface: interfaceType,
|
||||||
|
isConnected: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,6 +377,150 @@ void main() {
|
|||||||
await detach1.future;
|
await detach1.future;
|
||||||
expect(logger.traceText, contains('xcdevice observe error: Some error'));
|
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', () {
|
group('available devices', () {
|
||||||
@ -480,31 +624,41 @@ void main() {
|
|||||||
stdout: devicesOutput,
|
stdout: devicesOutput,
|
||||||
));
|
));
|
||||||
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
|
||||||
expect(devices, hasLength(4));
|
expect(devices, hasLength(5));
|
||||||
|
|
||||||
expect(devices[0].id, '00008027-00192736010F802E');
|
expect(devices[0].id, '00008027-00192736010F802E');
|
||||||
expect(devices[0].name, 'An iPhone (Space Gray)');
|
expect(devices[0].name, 'An iPhone (Space Gray)');
|
||||||
expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54');
|
expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54');
|
||||||
expect(devices[0].cpuArchitecture, DarwinArch.arm64);
|
expect(devices[0].cpuArchitecture, DarwinArch.arm64);
|
||||||
expect(devices[0].connectionInterface, DeviceConnectionInterface.attached);
|
expect(devices[0].connectionInterface, DeviceConnectionInterface.attached);
|
||||||
|
expect(devices[0].isConnected, true);
|
||||||
|
|
||||||
expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44');
|
expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44');
|
||||||
expect(devices[1].name, 'iPad 1');
|
expect(devices[1].name, 'iPad 1');
|
||||||
expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54');
|
expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54');
|
||||||
expect(devices[1].cpuArchitecture, DarwinArch.armv7);
|
expect(devices[1].cpuArchitecture, DarwinArch.armv7);
|
||||||
expect(devices[1].connectionInterface, DeviceConnectionInterface.attached);
|
expect(devices[1].connectionInterface, DeviceConnectionInterface.attached);
|
||||||
|
expect(devices[1].isConnected, true);
|
||||||
|
|
||||||
expect(devices[2].id, '234234234234234234345445687594e089dede3c44');
|
expect(devices[2].id, '234234234234234234345445687594e089dede3c44');
|
||||||
expect(devices[2].name, 'A networked iPad');
|
expect(devices[2].name, 'A networked iPad');
|
||||||
expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54');
|
expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54');
|
||||||
expect(devices[2].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
|
expect(devices[2].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
|
||||||
expect(devices[2].connectionInterface, DeviceConnectionInterface.wireless);
|
expect(devices[2].connectionInterface, DeviceConnectionInterface.wireless);
|
||||||
|
expect(devices[2].isConnected, true);
|
||||||
|
|
||||||
expect(devices[3].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4');
|
expect(devices[3].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4');
|
||||||
expect(devices[3].name, 'iPad 2');
|
expect(devices[3].name, 'iPad 2');
|
||||||
expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54');
|
expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54');
|
||||||
expect(devices[3].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
|
expect(devices[3].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
|
||||||
expect(devices[3].connectionInterface, DeviceConnectionInterface.attached);
|
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);
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -218,10 +218,17 @@ class FakeDeviceManager implements DeviceManager {
|
|||||||
DeviceDiscoveryFilter? filter,
|
DeviceDiscoveryFilter? filter,
|
||||||
}) async => filteredDevices(filter);
|
}) async => filteredDevices(filter);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Device>> refreshExtendedWirelessDeviceDiscoverers({
|
||||||
|
Duration? timeout,
|
||||||
|
DeviceDiscoveryFilter? filter,
|
||||||
|
}) async => filteredDevices(filter);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Device>> getDevicesById(
|
Future<List<Device>> getDevicesById(
|
||||||
String deviceId, {
|
String deviceId, {
|
||||||
DeviceDiscoveryFilter? filter,
|
DeviceDiscoveryFilter? filter,
|
||||||
|
bool waitForDeviceToConnect = false,
|
||||||
}) async {
|
}) async {
|
||||||
return filteredDevices(filter).where((Device device) {
|
return filteredDevices(filter).where((Device device) {
|
||||||
return device.id == deviceId || device.id.startsWith(deviceId);
|
return device.id == deviceId || device.id.startsWith(deviceId);
|
||||||
@ -231,6 +238,7 @@ class FakeDeviceManager implements DeviceManager {
|
|||||||
@override
|
@override
|
||||||
Future<List<Device>> getDevices({
|
Future<List<Device>> getDevices({
|
||||||
DeviceDiscoveryFilter? filter,
|
DeviceDiscoveryFilter? filter,
|
||||||
|
bool waitForDeviceToConnect = false,
|
||||||
}) {
|
}) {
|
||||||
return hasSpecifiedDeviceId
|
return hasSpecifiedDeviceId
|
||||||
? getDevicesById(specifiedDeviceId!, filter: filter)
|
? 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.
|
/// Fake device to test `devices` command.
|
||||||
@ -167,7 +194,9 @@ class FakeDeviceJsonData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FakePollingDeviceDiscovery extends PollingDeviceDiscovery {
|
class FakePollingDeviceDiscovery extends PollingDeviceDiscovery {
|
||||||
FakePollingDeviceDiscovery() : super('mock');
|
FakePollingDeviceDiscovery({
|
||||||
|
this.requiresExtendedWirelessDeviceDiscovery = false,
|
||||||
|
}) : super('mock');
|
||||||
|
|
||||||
final List<Device> _devices = <Device>[];
|
final List<Device> _devices = <Device>[];
|
||||||
final StreamController<Device> _onAddedController = StreamController<Device>.broadcast();
|
final StreamController<Device> _onAddedController = StreamController<Device>.broadcast();
|
||||||
@ -187,6 +216,9 @@ class FakePollingDeviceDiscovery extends PollingDeviceDiscovery {
|
|||||||
@override
|
@override
|
||||||
bool get canListAnything => true;
|
bool get canListAnything => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool requiresExtendedWirelessDeviceDiscovery;
|
||||||
|
|
||||||
void addDevice(Device device) {
|
void addDevice(Device device) {
|
||||||
_devices.add(device);
|
_devices.add(device);
|
||||||
_onAddedController.add(device);
|
_onAddedController.add(device);
|
||||||
|
Loading…
Reference in New Issue
Block a user