Update proxied devices to handle connection interface and diagnostics. (#145061)

Also improve the performance of daemon device discovery by parallelizing the calls.
This commit is contained in:
Lau Ching Jun 2024-03-13 15:24:25 -07:00 committed by GitHub
parent 19087442ce
commit 3dba3f2a0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 159 additions and 4 deletions

View file

@ -1008,6 +1008,7 @@ class DeviceDomain extends Domain {
registerHandler('startDartDevelopmentService', startDartDevelopmentService);
registerHandler('shutdownDartDevelopmentService', shutdownDartDevelopmentService);
registerHandler('setExternalDevToolsUriForDartDevelopmentService', setExternalDevToolsUriForDartDevelopmentService);
registerHandler('getDiagnostics', getDiagnostics);
// Use the device manager discovery so that client provided device types
// are usable via the daemon protocol.
@ -1059,12 +1060,23 @@ class DeviceDomain extends Domain {
}
/// Return a list of the current devices, discarding existing cache of devices.
Future<List<Map<String, Object?>>> discoverDevices([ Map<String, Object?>? args ]) async {
return <Map<String, Object?>>[
Future<List<Map<String, Object?>>> discoverDevices(Map<String, Object?> args) async {
final int? timeoutInMilliseconds = _getIntArg(args, 'timeoutInMilliseconds');
final Duration? timeout = timeoutInMilliseconds != null ? Duration(milliseconds: timeoutInMilliseconds) : null;
// Calling `discoverDevices()` and `_deviceToMap()` in parallel for better performance.
final List<List<Device>> devicesListList = await Future.wait(<Future<List<Device>>>[
for (final PollingDeviceDiscovery discoverer in _discoverers)
for (final Device device in await discoverer.discoverDevices())
await _deviceToMap(device),
discoverer.discoverDevices(timeout: timeout),
]);
final List<Device> devices = <Device>[
for (final List<Device> devicesList in devicesListList)
...devicesList,
];
return Future.wait(<Future<Map<String, Object?>>>[
for (final Device device in devices) _deviceToMap(device),
]);
}
/// Enable device events.
@ -1298,6 +1310,21 @@ class DeviceDomain extends Domain {
}
return null;
}
/// Gets a list of diagnostic messages pertaining to issues with any connected
/// devices.
Future<List<String>> getDiagnostics(Map<String, Object?> args) async {
// Call `getDiagnostics()` in parallel to improve performance.
final List<List<String>> diagnosticsLists = await Future.wait(<Future<List<String>>>[
for (final PollingDeviceDiscovery discoverer in _discoverers)
discoverer.getDiagnostics(),
]);
return <String>[
for (final List<String> diagnostics in diagnosticsLists)
...diagnostics,
];
}
}
class DevToolsDomain extends Domain {
@ -1333,6 +1360,8 @@ Future<Map<String, Object?>> _deviceToMap(Device device) async {
'ephemeral': device.ephemeral,
'emulatorId': await device.emulatorId,
'sdk': await device.sdkNameAndVersion,
'isConnected': device.isConnected,
'connectionInterface': getNameForDeviceConnectionInterface(device.connectionInterface),
'capabilities': <String, Object>{
'hotReload': device.supportsHotReload,
'hotRestart': device.supportsHotRestart,

View file

@ -586,6 +586,27 @@ enum DeviceConnectionInterface {
wireless,
}
/// Returns the `DeviceConnectionInterface` enum based on its string name.
DeviceConnectionInterface getDeviceConnectionInterfaceForName(String name) {
switch (name) {
case 'attached':
return DeviceConnectionInterface.attached;
case 'wireless':
return DeviceConnectionInterface.wireless;
}
throw Exception('Unsupported DeviceConnectionInterface name "$name"');
}
/// Returns a `DeviceConnectionInterface`'s string name.
String getNameForDeviceConnectionInterface(DeviceConnectionInterface connectionInterface) {
switch (connectionInterface) {
case DeviceConnectionInterface.attached:
return 'attached';
case DeviceConnectionInterface.wireless:
return 'wireless';
}
}
/// A device is a physical hardware that can run a Flutter application.
///
/// This may correspond to a connected iOS or Android device, or represent

View file

@ -102,6 +102,8 @@ class ProxiedDevices extends PollingDeviceDiscovery {
@visibleForTesting
ProxiedDevice deviceFromDaemonResult(Map<String, Object?> device) {
final Map<String, Object?> capabilities = _cast<Map<String, Object?>>(device['capabilities']);
final String? connectionInterfaceName = _cast<String?>(device['connectionInterface']);
final DeviceConnectionInterface? connectionInterface = connectionInterfaceName != null ? getDeviceConnectionInterfaceForName(connectionInterfaceName) : null;
return ProxiedDevice(
connection, _cast<String>(device['id']),
deltaFileTransfer: _deltaFileTransfer,
@ -110,6 +112,8 @@ class ProxiedDevices extends PollingDeviceDiscovery {
platformType: PlatformType.fromString(_cast<String>(device['platformType'])),
targetPlatform: getTargetPlatformForName(_cast<String>(device['platform'])),
ephemeral: _cast<bool>(device['ephemeral']),
isConnected: _cast<bool?>(device['isConnected']) ?? true,
connectionInterface: connectionInterface ?? DeviceConnectionInterface.attached,
name: 'Proxied ${device['name']}',
isLocalEmulator: _cast<bool>(device['emulator']),
emulatorId: _cast<String?>(device['emulatorId']),
@ -124,6 +128,21 @@ class ProxiedDevices extends PollingDeviceDiscovery {
fileTransfer: _fileTransfer,
);
}
@override
Future<List<String>> getDiagnostics() async {
try {
final List<String> diagnostics = _cast<List<dynamic>>(await connection.sendRequest('device.getDiagnostics')).cast<String>();
return diagnostics;
} on String catch (e) { // Daemon actually does throw string types.
if (e.contains('command not understood')) {
_logger.printTrace('The daemon is on an older version that does not support `device.getDiagnostics`.');
// Silently ignore.
return <String>[];
}
rethrow;
}
}
}
/// A [Device] that acts as a proxy to remotely connected device.
@ -143,6 +162,8 @@ class ProxiedDevice extends Device {
required PlatformType? platformType,
required TargetPlatform targetPlatform,
required bool ephemeral,
required this.isConnected,
required this.connectionInterface,
required this.name,
required bool isLocalEmulator,
required String? emulatorId,
@ -180,6 +201,12 @@ class ProxiedDevice extends Device {
final FileTransfer _fileTransfer;
@override
final bool isConnected;
@override
final DeviceConnectionInterface connectionInterface;
@override
final String name;

View file

@ -459,6 +459,8 @@ void main() {
'ephemeral': false,
'emulatorId': 'device',
'sdk': 'Android 12',
'isConnected': true,
'connectionInterface': 'attached',
'capabilities': <String, Object?>{
'hotReload': true,
'hotRestart': true,
@ -479,6 +481,8 @@ void main() {
'ephemeral': false,
'emulatorId': null,
'sdk': 'preview',
'isConnected': true,
'connectionInterface': 'attached',
'capabilities': <String, Object?>{
'hotReload': true,
'hotRestart': true,
@ -725,6 +729,31 @@ void main() {
expect(device.dds.shutdownCalled, true);
});
testUsingContext('device.getDiagnostics returns correct value', () async {
daemon = Daemon(
daemonConnection,
notifyingLogger: notifyingLogger,
);
final FakePollingDeviceDiscovery discoverer1 = FakePollingDeviceDiscovery();
discoverer1.diagnostics = <String>['fake diagnostic 1', 'fake diagnostic 2'];
final FakePollingDeviceDiscovery discoverer2 = FakePollingDeviceDiscovery();
discoverer2.diagnostics = <String>['fake diagnostic 3', 'fake diagnostic 4'];
daemon.deviceDomain.addDeviceDiscoverer(discoverer1);
daemon.deviceDomain.addDeviceDiscoverer(discoverer2);
daemonStreams.inputs.add(DaemonMessage(<String, Object?>{
'id': 0,
'method': 'device.getDiagnostics',
}));
final DaemonMessage response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
expect(response.data['id'], 0);
expect(response.data['result'], <String>[
'fake diagnostic 1',
'fake diagnostic 2',
'fake diagnostic 3',
'fake diagnostic 4',
]);
});
testUsingContext('emulator.launch without an emulatorId should report an error', () async {
daemon = Daemon(
daemonConnection,
@ -1128,6 +1157,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override
final bool isConnected = true;
@override
final DeviceConnectionInterface connectionInterface = DeviceConnectionInterface.attached;
@override
Future<String> get sdkNameAndVersion async => 'Android 12';

View file

@ -274,6 +274,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override
bool get isConnected => true;
@override
final DeviceConnectionInterface connectionInterface = DeviceConnectionInterface.attached;
@override
Future<String> get sdkNameAndVersion async => 'Android 12';

View file

@ -562,6 +562,44 @@ void main() {
expect(devicesAdded[0].id, fakeDevice['id']);
expect(devicesAdded[1].id, fakeDevice2['id']);
});
testWithoutContext('handles getDiagnostics', () async {
bufferLogger = BufferLogger.test();
final ProxiedDevices proxiedDevices = ProxiedDevices(
clientDaemonConnection,
logger: bufferLogger,
);
final Future<List<String>> resultFuture = proxiedDevices.getDiagnostics();
final DaemonMessage message = await serverDaemonConnection.incomingCommands.first;
expect(message.data['id'], isNotNull);
expect(message.data['method'], 'device.getDiagnostics');
serverDaemonConnection.sendResponse(message.data['id']!, <String>['1', '2']);
final List<String> result = await resultFuture;
expect(result, <String>['1', '2']);
});
testWithoutContext('returns empty result when daemon does not understand getDiagnostics', () async {
bufferLogger = BufferLogger.test();
final ProxiedDevices proxiedDevices = ProxiedDevices(
clientDaemonConnection,
logger: bufferLogger,
);
final Future<List<String>> resultFuture = proxiedDevices.getDiagnostics();
final DaemonMessage message = await serverDaemonConnection.incomingCommands.first;
expect(message.data['id'], isNotNull);
expect(message.data['method'], 'device.getDiagnostics');
serverDaemonConnection.sendErrorResponse(message.data['id']!, 'command not understood: device.getDiagnostics', StackTrace.current);
final List<String> result = await resultFuture;
expect(result, isEmpty);
});
});
group('ProxiedDartDevelopmentService', () {

View file

@ -269,6 +269,11 @@ class FakePollingDeviceDiscovery extends PollingDeviceDiscovery {
@override
List<String> wellKnownIds = <String>[];
List<String> diagnostics = <String>[];
@override
Future<List<String>> getDiagnostics() => Future<List<String>>.value(diagnostics);
}
/// A fake implementation of the [DeviceLogReader].