Unverified Commit 0ecc7a4b authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] support multiple fuchsia devices (#55780)

Fixes #55765

We are currently only returning the first device from dev-finder, instead we need to look at the whole list.
parent c6a83af5
...@@ -17,6 +17,7 @@ import 'package:flutter_tools/src/commands/doctor.dart'; ...@@ -17,6 +17,7 @@ import 'package:flutter_tools/src/commands/doctor.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_device.dart'; import 'package:flutter_tools/src/fuchsia/fuchsia_device.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart'; import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart';
...@@ -126,7 +127,12 @@ Future<void> main(List<String> args) async { ...@@ -126,7 +127,12 @@ Future<void> main(List<String> args) async {
class _FuchsiaDeviceManager extends DeviceManager { class _FuchsiaDeviceManager extends DeviceManager {
@override @override
List<DeviceDiscovery> get deviceDiscoverers => List<DeviceDiscovery>.unmodifiable(<DeviceDiscovery>[ List<DeviceDiscovery> get deviceDiscoverers => List<DeviceDiscovery>.unmodifiable(<DeviceDiscovery>[
FuchsiaDevices(), FuchsiaDevices(
logger: globals.logger,
platform: globals.platform,
fuchsiaWorkflow: fuchsiaWorkflow,
fuchsiaSdk: fuchsiaSdk,
),
]); ]);
@override @override
......
...@@ -19,6 +19,8 @@ import 'base/utils.dart'; ...@@ -19,6 +19,8 @@ import 'base/utils.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'features.dart'; import 'features.dart';
import 'fuchsia/fuchsia_device.dart'; import 'fuchsia/fuchsia_device.dart';
import 'fuchsia/fuchsia_sdk.dart';
import 'fuchsia/fuchsia_workflow.dart';
import 'globals.dart' as globals; import 'globals.dart' as globals;
import 'ios/devices.dart'; import 'ios/devices.dart';
import 'ios/simulators.dart'; import 'ios/simulators.dart';
...@@ -82,7 +84,12 @@ class DeviceManager { ...@@ -82,7 +84,12 @@ class DeviceManager {
iosWorkflow: globals.iosWorkflow, iosWorkflow: globals.iosWorkflow,
), ),
IOSSimulators(iosSimulatorUtils: globals.iosSimulatorUtils), IOSSimulators(iosSimulatorUtils: globals.iosSimulatorUtils),
FuchsiaDevices(), FuchsiaDevices(
fuchsiaSdk: fuchsiaSdk,
logger: globals.logger,
fuchsiaWorkflow: fuchsiaWorkflow,
platform: globals.platform,
),
FlutterTesterDevices(), FlutterTesterDevices(),
MacOSDevices(), MacOSDevices(),
LinuxDevices( LinuxDevices(
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:vm_service/vm_service.dart' as vm_service; import 'package:vm_service/vm_service.dart' as vm_service;
import '../application_package.dart'; import '../application_package.dart';
...@@ -136,56 +137,75 @@ class _FuchsiaLogSink implements EventSink<String> { ...@@ -136,56 +137,75 @@ class _FuchsiaLogSink implements EventSink<String> {
} }
} }
/// Device discovery for Fuchsia devices.
class FuchsiaDevices extends PollingDeviceDiscovery { class FuchsiaDevices extends PollingDeviceDiscovery {
FuchsiaDevices() : super('Fuchsia devices'); FuchsiaDevices({
@required Platform platform,
@required FuchsiaWorkflow fuchsiaWorkflow,
@required FuchsiaSdk fuchsiaSdk,
@required Logger logger,
}) : _platform = platform,
_fuchsiaWorkflow = fuchsiaWorkflow,
_fuchsiaSdk = fuchsiaSdk,
_logger = logger,
super('Fuchsia devices');
final Platform _platform;
final FuchsiaWorkflow _fuchsiaWorkflow;
final FuchsiaSdk _fuchsiaSdk;
final Logger _logger;
@override @override
bool get supportsPlatform => isFuchsiaSupportedPlatform(); bool get supportsPlatform => isFuchsiaSupportedPlatform(_platform);
@override @override
bool get canListAnything => fuchsiaWorkflow.canListDevices; bool get canListAnything => _fuchsiaWorkflow.canListDevices;
@override @override
Future<List<Device>> pollingGetDevices({ Duration timeout }) async { Future<List<Device>> pollingGetDevices({ Duration timeout }) async {
if (!fuchsiaWorkflow.canListDevices) { if (!_fuchsiaWorkflow.canListDevices) {
return <Device>[]; return <Device>[];
} }
final String text = await fuchsiaSdk.listDevices(timeout: timeout); final List<String> text = (await _fuchsiaSdk.listDevices(timeout: timeout))
?.split('\n');
if (text == null || text.isEmpty) { if (text == null || text.isEmpty) {
return <Device>[]; return <Device>[];
} }
final List<FuchsiaDevice> devices = await parseListDevices(text); final List<FuchsiaDevice> devices = <FuchsiaDevice>[];
for (final String line in text) {
final FuchsiaDevice device = await _parseDevice(line);
if (device == null) {
continue;
}
devices.add(device);
}
return devices; return devices;
} }
@override @override
Future<List<String>> getDiagnostics() async => const <String>[]; Future<List<String>> getDiagnostics() async => const <String>[];
}
@visibleForTesting Future<FuchsiaDevice> _parseDevice(String text) async {
Future<List<FuchsiaDevice>> parseListDevices(String text) async { final String line = text.trim();
final List<FuchsiaDevice> devices = <FuchsiaDevice>[];
for (final String rawLine in text.trim().split('\n')) {
final String line = rawLine.trim();
// ['ip', 'device name'] // ['ip', 'device name']
final List<String> words = line.split(' '); final List<String> words = line.split(' ');
if (words.length < 2) { if (words.length < 2) {
continue; return null;
} }
final String name = words[1]; final String name = words[1];
final String resolvedHost = await fuchsiaSdk.fuchsiaDevFinder.resolve( final String resolvedHost = await _fuchsiaSdk.fuchsiaDevFinder.resolve(
name, name,
local: false, local: false,
); );
if (resolvedHost == null) { if (resolvedHost == null) {
globals.printError('Failed to resolve host for Fuchsia device `$name`'); _logger.printError('Failed to resolve host for Fuchsia device `$name`');
continue; return null;
} }
devices.add(FuchsiaDevice(resolvedHost, name: name)); return FuchsiaDevice(resolvedHost, name: name);
} }
return devices;
} }
class FuchsiaDevice extends Device { class FuchsiaDevice extends Device {
FuchsiaDevice(String id, {this.name}) : super( FuchsiaDevice(String id, {this.name}) : super(
id, id,
...@@ -451,7 +471,7 @@ class FuchsiaDevice extends Device { ...@@ -451,7 +471,7 @@ class FuchsiaDevice extends Device {
} }
@override @override
bool get supportsScreenshot => isFuchsiaSupportedPlatform(); bool get supportsScreenshot => isFuchsiaSupportedPlatform(globals.platform);
@override @override
Future<void> takeScreenshot(File outputFile) async { Future<void> takeScreenshot(File outputFile) async {
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
import 'dart:async'; import 'dart:async';
import 'package:platform/platform.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
...@@ -18,8 +20,8 @@ import 'fuchsia_pm.dart'; ...@@ -18,8 +20,8 @@ import 'fuchsia_pm.dart';
FuchsiaSdk get fuchsiaSdk => context.get<FuchsiaSdk>(); FuchsiaSdk get fuchsiaSdk => context.get<FuchsiaSdk>();
/// Returns [true] if the current platform supports Fuchsia targets. /// Returns [true] if the current platform supports Fuchsia targets.
bool isFuchsiaSupportedPlatform() { bool isFuchsiaSupportedPlatform(Platform platform) {
return globals.platform.isLinux || globals.platform.isMacOS; return platform.isLinux || platform.isMacOS;
} }
/// The Fuchsia SDK shell commands. /// The Fuchsia SDK shell commands.
...@@ -45,6 +47,8 @@ class FuchsiaSdk { ...@@ -45,6 +47,8 @@ class FuchsiaSdk {
FuchsiaKernelCompiler get fuchsiaKernelCompiler => FuchsiaKernelCompiler get fuchsiaKernelCompiler =>
_fuchsiaKernelCompiler ??= FuchsiaKernelCompiler(); _fuchsiaKernelCompiler ??= FuchsiaKernelCompiler();
/// Returns any attached devices is a newline-denominated String.
///
/// Example output: /// Example output:
/// $ device-finder list -full /// $ device-finder list -full
/// > 192.168.42.56 paper-pulp-bush-angel /// > 192.168.42.56 paper-pulp-bush-angel
...@@ -57,7 +61,7 @@ class FuchsiaSdk { ...@@ -57,7 +61,7 @@ class FuchsiaSdk {
if (devices == null) { if (devices == null) {
return null; return null;
} }
return devices.isNotEmpty ? devices[0] : null; return devices.isNotEmpty ? devices.join('\n') : null;
} }
/// Returns the fuchsia system logs for an attached device where /// Returns the fuchsia system logs for an attached device where
...@@ -116,7 +120,7 @@ class FuchsiaArtifacts { ...@@ -116,7 +120,7 @@ class FuchsiaArtifacts {
/// FUCHSIA_SSH_CONFIG) to find the ssh configuration needed to talk to /// FUCHSIA_SSH_CONFIG) to find the ssh configuration needed to talk to
/// a device. /// a device.
factory FuchsiaArtifacts.find() { factory FuchsiaArtifacts.find() {
if (!isFuchsiaSupportedPlatform()) { if (!isFuchsiaSupportedPlatform(globals.platform)) {
// Don't try to find the artifacts on platforms that are not supported. // Don't try to find the artifacts on platforms that are not supported.
return FuchsiaArtifacts(); return FuchsiaArtifacts();
} }
......
...@@ -6,6 +6,8 @@ import 'dart:async'; ...@@ -6,6 +6,8 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
import 'package:vm_service/vm_service.dart' as vm_service; import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
...@@ -66,32 +68,89 @@ void main() { ...@@ -66,32 +68,89 @@ void main() {
when(sshConfig.absolute).thenReturn(sshConfig); when(sshConfig.absolute).thenReturn(sshConfig);
}); });
testUsingContext('stores the requested id and name', () { testWithoutContext('stores the requested id and name', () {
const String deviceId = 'e80::0000:a00a:f00f:2002/3'; const String deviceId = 'e80::0000:a00a:f00f:2002/3';
const String name = 'halfbaked'; const String name = 'halfbaked';
final FuchsiaDevice device = FuchsiaDevice(deviceId, name: name); final FuchsiaDevice device = FuchsiaDevice(deviceId, name: name);
expect(device.id, deviceId); expect(device.id, deviceId);
expect(device.name, name); expect(device.name, name);
}); });
testUsingContext('parse device-finder output', () async { testWithoutContext('lists nothing when workflow cannot list devices', () async {
const String example = '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel'; final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow();
final List<FuchsiaDevice> names = await parseListDevices(example); final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
platform: FakePlatform(operatingSystem: 'linux'),
fuchsiaSdk: null,
fuchsiaWorkflow: fuchsiaWorkflow,
logger: BufferLogger.test(),
);
when(fuchsiaWorkflow.canListDevices).thenReturn(false);
expect(names.length, 1); expect(fuchsiaDevices.canListAnything, false);
expect(names.first.name, 'paper-pulp-bush-angel'); expect(await fuchsiaDevices.pollingGetDevices(), isEmpty);
expect(names.first.id, '192.168.42.10');
}, overrides: <Type, Generator>{
FuchsiaSdk: () => MockFuchsiaSdk(),
}); });
testUsingContext('parse junk device-finder output', () async { testWithoutContext('can parse device-finder output for single device', () async {
const String example = 'junk'; final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow();
final List<FuchsiaDevice> names = await parseListDevices(example); final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk();
final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
platform: FakePlatform(operatingSystem: 'linux'),
fuchsiaSdk: fuchsiaSdk,
fuchsiaWorkflow: fuchsiaWorkflow,
logger: BufferLogger.test(),
);
when(fuchsiaWorkflow.canListDevices).thenReturn(true);
when(fuchsiaSdk.listDevices()).thenAnswer((Invocation invocation) async {
return '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel';
});
expect(names.length, 0); final Device device = (await fuchsiaDevices.pollingGetDevices()).single;
}, overrides: <Type, Generator>{
FuchsiaSdk: () => MockFuchsiaSdk(), expect(device.name, 'paper-pulp-bush-angel');
expect(device.id, '192.168.42.10');
});
testWithoutContext('can parse device-finder output for multiple devices', () async {
final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow();
final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk();
final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
platform: FakePlatform(operatingSystem: 'linux'),
fuchsiaSdk: fuchsiaSdk,
fuchsiaWorkflow: fuchsiaWorkflow,
logger: BufferLogger.test(),
);
when(fuchsiaWorkflow.canListDevices).thenReturn(true);
when(fuchsiaSdk.listDevices()).thenAnswer((Invocation invocation) async {
return '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel\n'
'2001:0db8:85a3:0000:0000:8a2e:0370:7335 foo-bar-fiz-buzz';
});
final List<Device> devices = await fuchsiaDevices.pollingGetDevices();
expect(devices.first.name, 'paper-pulp-bush-angel');
expect(devices.first.id, '192.168.42.10');
expect(devices.last.name, 'foo-bar-fiz-buzz');
expect(devices.last.id, '192.168.42.10');
});
testWithoutContext('can parse junk output from the dev-finder', () async {
final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow();
final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk();
final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
platform: FakePlatform(operatingSystem: 'linux'),
fuchsiaSdk: fuchsiaSdk,
fuchsiaWorkflow: fuchsiaWorkflow,
logger: BufferLogger.test(),
);
when(fuchsiaWorkflow.canListDevices).thenReturn(true);
when(fuchsiaSdk.listDevices()).thenAnswer((Invocation invocation) async {
return 'junk';
});
final List<Device> devices = await fuchsiaDevices.pollingGetDevices();
expect(devices, isEmpty);
}); });
testUsingContext('disposing device disposes the portForwarder', () async { testUsingContext('disposing device disposes the portForwarder', () async {
...@@ -118,6 +177,7 @@ void main() { ...@@ -118,6 +177,7 @@ void main() {
test('is ephemeral', () { test('is ephemeral', () {
final FuchsiaDevice device = FuchsiaDevice('123'); final FuchsiaDevice device = FuchsiaDevice('123');
expect(device.ephemeral, true); expect(device.ephemeral, true);
}); });
...@@ -125,6 +185,7 @@ void main() { ...@@ -125,6 +185,7 @@ void main() {
final FuchsiaDevice device = FuchsiaDevice('123'); final FuchsiaDevice device = FuchsiaDevice('123');
globals.fs.directory('fuchsia').createSync(recursive: true); globals.fs.directory('fuchsia').createSync(recursive: true);
globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('pubspec.yaml').createSync();
expect(device.isSupportedForProject(FlutterProject.current()), true); expect(device.isSupportedForProject(FlutterProject.current()), true);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
...@@ -134,6 +195,7 @@ void main() { ...@@ -134,6 +195,7 @@ void main() {
testUsingContext('not supported for project', () async { testUsingContext('not supported for project', () async {
final FuchsiaDevice device = FuchsiaDevice('123'); final FuchsiaDevice device = FuchsiaDevice('123');
globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('pubspec.yaml').createSync();
expect(device.isSupportedForProject(FlutterProject.current()), false); expect(device.isSupportedForProject(FlutterProject.current()), false);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
...@@ -142,6 +204,7 @@ void main() { ...@@ -142,6 +204,7 @@ void main() {
testUsingContext('targetPlatform does not throw when sshConfig is missing', () async { testUsingContext('targetPlatform does not throw when sshConfig is missing', () async {
final FuchsiaDevice device = FuchsiaDevice('123'); final FuchsiaDevice device = FuchsiaDevice('123');
expect(await device.targetPlatform, TargetPlatform.fuchsia_arm64); expect(await device.targetPlatform, TargetPlatform.fuchsia_arm64);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: null), FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: null),
...@@ -1475,3 +1538,5 @@ class MockFuchsiaSdk extends Mock implements FuchsiaSdk { ...@@ -1475,3 +1538,5 @@ class MockFuchsiaSdk extends Mock implements FuchsiaSdk {
@override @override
final FuchsiaDevFinder fuchsiaDevFinder; final FuchsiaDevFinder fuchsiaDevFinder;
} }
class MockFuchsiaWorkflow extends Mock implements FuchsiaWorkflow {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment