diff --git a/dev/bots/run_fuchsia_tests.sh b/dev/bots/run_fuchsia_tests.sh index 063178ab76..120c0e0c4c 100755 --- a/dev/bots/run_fuchsia_tests.sh +++ b/dev/bots/run_fuchsia_tests.sh @@ -13,7 +13,7 @@ # The first and only parameter should be the path to the Fuchsia system image # tarball, e.g. `./run_fuchsia_tests.sh generic-x64.tgz`. # -# This script expects `pm`, `device-finder`, and `fuchsia_ctl` to all be in the +# This script expects `pm`, `ffx`, and `fuchsia_ctl` to all be in the # same directory as the script. # # This script also expects a private key available at: @@ -44,7 +44,7 @@ fi # Wrapper function to pass common args to fuchsia_ctl. fuchsia_ctl() { $script_dir/fuchsia_ctl -d $device_name \ - --device-finder-path $script_dir/device-finder "$@" + --ffx-path $script_dir/ffx "$@" } reboot() { @@ -115,6 +115,8 @@ EOF export FUCHSIA_SSH_CONFIG=$script_dir/fuchsia_ssh_config +export FUCHSIA_ANALYTICS_DISABLED="1" + # Run the driver test echo "$(date) START:DRIVER_TEST -------------------------------------" flutter_dir=$script_dir/flutter diff --git a/dev/devicelab/lib/framework/adb.dart b/dev/devicelab/lib/framework/adb.dart index 4a60234f51..9f48a65842 100644 --- a/dev/devicelab/lib/framework/adb.dart +++ b/dev/devicelab/lib/framework/adb.dart @@ -327,12 +327,12 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery { FuchsiaDevice _workingDevice; - String get _devFinder { - final String devFinder = path.join(getArtifactPath(), 'fuchsia', 'tools', 'device-finder'); - if (!File(devFinder).existsSync()) { - throw FileSystemException("Couldn't find device-finder at location $devFinder"); + String get _ffx { + final String ffx = path.join(getArtifactPath(), 'fuchsia', 'tools','x64', 'ffx'); + if (!File(ffx).existsSync()) { + throw FileSystemException("Couldn't find ffx at location $ffx"); } - return devFinder; + return ffx; } @override @@ -378,7 +378,7 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery { @override Future> discoverDevices() async { - final List output = (await eval(_devFinder, ['list', '-full'])) + final List output = (await eval(_ffx, ['target', 'list', '--format', 's'])) .trim() .split('\n'); @@ -396,16 +396,20 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery { final Map results = {}; for (final String deviceId in await discoverDevices()) { try { + StringBuffer stderr; final int resolveResult = await exec( - _devFinder, + _ffx, [ - 'resolve', - '-device-limit', - '1', + 'target', + 'list', + '--format', + 'a', deviceId, - ] + ], + stderr: stderr ); - if (resolveResult == 0) { + final String stderrOutput = stderr.toString().trim(); + if (resolveResult == 0 && ! stderrOutput.contains('No devices found')) { results['fuchsia-device-$deviceId'] = HealthCheckResult.success(); } else { results['fuchsia-device-$deviceId'] = HealthCheckResult.failure('Cannot resolve device $deviceId'); diff --git a/dev/devicelab/lib/framework/utils.dart b/dev/devicelab/lib/framework/utils.dart index d00f033bd7..5319724f56 100644 --- a/dev/devicelab/lib/framework/utils.dart +++ b/dev/devicelab/lib/framework/utils.dart @@ -322,6 +322,7 @@ Future exec( List arguments, { Map environment, bool canFail = false, // as in, whether failures are ok. False means that they are fatal. + StringBuffer stderr, // if not null, the stderr will be written here String workingDirectory, }) async { return _execute( @@ -329,6 +330,7 @@ Future exec( arguments, environment: environment, canFail : canFail, + stderr: stderr, workingDirectory: workingDirectory, ); } diff --git a/packages/flutter_tools/bin/fuchsia_attach.dart b/packages/flutter_tools/bin/fuchsia_attach.dart index 0d08cb0872..b3e3fa72a5 100644 --- a/packages/flutter_tools/bin/fuchsia_attach.dart +++ b/packages/flutter_tools/bin/fuchsia_attach.dart @@ -30,6 +30,7 @@ final ArgParser parser = ArgParser() ..addOption('entrypoint', defaultsTo: 'main.dart', help: 'The filename of the main method. Defaults to main.dart') ..addOption('device', help: 'The device id to attach to') ..addOption('dev-finder', help: 'The location of the device-finder binary') + ..addOption('ffx', help: 'The location of the ffx binary') ..addFlag('verbose', negatable: true); // Track the original working directory so that the tool can find the @@ -48,6 +49,7 @@ Future main(List args) async { final File frontendServer = globals.fs.file('$buildDirectory/host_x64/gen/third_party/flutter/frontend_server/frontend_server_tool.snapshot'); final File sshConfig = globals.fs.file('$buildDirectory/ssh-keys/ssh_config'); final File devFinder = globals.fs.file(argResults['dev-finder']); + final File ffx = globals.fs.file(argResults['ffx']); final File platformKernelDill = globals.fs.file('$buildDirectory/flutter_runner_patched_sdk/platform_strong.dill'); final File flutterPatchedSdk = globals.fs.file('$buildDirectory/flutter_runner_patched_sdk'); final String packages = '$buildDirectory/dartlang/gen/$path/${name}_dart_library.packages'; @@ -62,6 +64,10 @@ Future main(List args) async { print('Error: device-finder not found at ${devFinder.path}.'); return 1; } + if (!ffx.existsSync()) { + print('Error: ffx not found at ${ffx.path}.'); + return 1; + } if (!frontendServer.existsSync()) { print( 'Error: frontend_server not found at ${frontendServer.path}. This ' @@ -107,7 +113,8 @@ Future main(List args) async { overrides: { FeatureFlags: () => const _FuchsiaFeatureFlags(), DeviceManager: () => _FuchsiaDeviceManager(), - FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig, devFinder: devFinder), + FuchsiaArtifacts: () => FuchsiaArtifacts( + sshConfig: sshConfig, devFinder: devFinder, ffx: ffx), Artifacts: () => OverrideArtifacts( parent: CachedArtifacts( fileSystem: globals.fs, diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart index e852113f0c..37674476c5 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart @@ -181,8 +181,11 @@ class FuchsiaDevices extends PollingDeviceDiscovery { if (!_fuchsiaWorkflow.canListDevices) { return []; } - final List text = (await _fuchsiaSdk.listDevices(timeout: timeout)) - ?.split('\n'); + // TODO(omerlevran): Remove once soft transition is complete fxb/67602. + final List text = (await _fuchsiaSdk.listDevices( + timeout: timeout, + useDeviceFinder: _fuchsiaWorkflow.shouldUseDeviceFinder, + ))?.split('\n'); if (text == null || text.isEmpty) { return []; } @@ -208,9 +211,18 @@ class FuchsiaDevices extends PollingDeviceDiscovery { return null; } final String name = words[1]; - final String resolvedHost = await _fuchsiaSdk.fuchsiaDevFinder.resolve( - name, - ); + String resolvedHost; + + // TODO(omerlevran): Remove once soft transition is complete fxb/67602. + if (_fuchsiaWorkflow.shouldUseDeviceFinder) { + // TODO(omerlevran): Add support for resolve on the FuchsiaSdk Object. + resolvedHost = await _fuchsiaSdk.fuchsiaDevFinder.resolve( + name, + ); + } else { + // TODO(omerlevran): Add support for resolve on the FuchsiaSdk Object. + resolvedHost = await _fuchsiaSdk.fuchsiaFfx.resolve(name); + } if (resolvedHost == null) { _logger.printError('Failed to resolve host for Fuchsia device `$name`'); return null; diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_ffx.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_ffx.dart new file mode 100644 index 0000000000..26c4739f37 --- /dev/null +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_ffx.dart @@ -0,0 +1,101 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:meta/meta.dart'; +import 'package:process/process.dart'; + +import '../base/common.dart'; +import '../base/logger.dart'; +import '../base/process.dart'; +import 'fuchsia_sdk.dart'; + +// Usage: ffx [-c ] [-e ] [-t ] [-T ] [-v] [] [] + +// Fuchsia's developer tool + +// Options: +// -c, --config override default configuration +// -e, --env override default environment settings +// -t, --target apply operations across single or multiple targets +// -T, --timeout override default proxy timeout +// -v, --verbose use verbose output +// --help display usage information + +// Commands: +// config View and switch default and user configurations +// daemon Interact with/control the ffx daemon +// target Interact with a target device or emulator + +/// A simple wrapper for the Fuchsia SDK's 'ffx' tool. +class FuchsiaFfx { + FuchsiaFfx({ + @required FuchsiaArtifacts fuchsiaArtifacts, + @required Logger logger, + @required ProcessManager processManager, + }) : _fuchsiaArtifacts = fuchsiaArtifacts, + _logger = logger, + _processUtils = + ProcessUtils(logger: logger, processManager: processManager); + + final FuchsiaArtifacts _fuchsiaArtifacts; + final Logger _logger; + final ProcessUtils _processUtils; + + /// Returns a list of attached devices as a list of strings with entries + /// formatted as follows: + /// + /// abcd::abcd:abc:abcd:abcd%qemu scare-cable-skip-joy + Future> list({Duration timeout}) async { + if (_fuchsiaArtifacts.ffx == null || !_fuchsiaArtifacts.ffx.existsSync()) { + throwToolExit('Fuchsia ffx tool not found.'); + } + final List command = [ + _fuchsiaArtifacts.ffx.path, + if (timeout != null) + ...['-T', '${timeout.inSeconds}'], + 'target', + 'list', + '--format', + 's' + ]; + final RunResult result = await _processUtils.run(command); + if (result.exitCode != 0) { + _logger.printError('ffx failed: ${result.stderr}'); + return null; + } + if (result.stderr.contains('No devices found')) { + return null; + } + return result.stdout.split('\n'); + } + + /// Returns the address of the named device. + /// + /// The string [deviceName] should be the name of the device from the + /// 'list' command, e.g. 'scare-cable-skip-joy'. + Future resolve(String deviceName) async { + if (_fuchsiaArtifacts.ffx == null || !_fuchsiaArtifacts.ffx.existsSync()) { + throwToolExit('Fuchsia ffx tool not found.'); + } + final List command = [ + _fuchsiaArtifacts.ffx.path, + 'target', + 'list', + '--format', + 'a', + deviceName, + ]; + final RunResult result = await _processUtils.run(command); + if (result.exitCode != 0) { + _logger.printError('ffx failed: ${result.stderr}'); + return null; + } + if (result.stderr.contains('No devices found')) { + return null; + } + return result.stdout.trim(); + } +} diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart index c2f8e40580..10ed29152a 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart @@ -106,7 +106,7 @@ class FuchsiaPM { /// /// The argument [repoPath] should have previously been an argument to /// [newrepo]. The [host] should be the host reported by - /// [FuchsiaDevFinder.resolve], and [port] should be an unused port for the + /// [FuchsiaDevFinder.resolve] or [FuchsiaFfx.resolve] and [port] should be an unused port for the /// http server to bind. Future serve(String repoPath, String host, int port) async { if (globals.fuchsiaArtifacts.pm == null) { diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart index 80e3c45d34..ea1559edae 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart @@ -14,6 +14,7 @@ import '../convert.dart'; import '../globals.dart' as globals; import 'fuchsia_dev_finder.dart'; +import 'fuchsia_ffx.dart'; import 'fuchsia_kernel_compiler.dart'; import 'fuchsia_pm.dart'; @@ -48,18 +49,32 @@ class FuchsiaSdk { FuchsiaKernelCompiler get fuchsiaKernelCompiler => _fuchsiaKernelCompiler ??= FuchsiaKernelCompiler(); + /// Interface to the 'ffx' tool. + FuchsiaFfx _fuchsiaFfx; + FuchsiaFfx get fuchsiaFfx => _fuchsiaFfx ??= FuchsiaFfx( + fuchsiaArtifacts: globals.fuchsiaArtifacts, + logger: globals.logger, + processManager: globals.processManager, + ); + /// Returns any attached devices is a newline-denominated String. /// - /// Example output: - /// - /// $ device-finder list -full - /// > 192.168.42.56 paper-pulp-bush-angel - Future listDevices({ Duration timeout }) async { - if (globals.fuchsiaArtifacts.devFinder == null || - !globals.fuchsiaArtifacts.devFinder.existsSync()) { - return null; + /// Example output: abcd::abcd:abc:abcd:abcd%qemu scare-cable-skip-joy + Future listDevices({Duration timeout, bool useDeviceFinder = false}) async { + List devices; + if (useDeviceFinder) { + if (globals.fuchsiaArtifacts.devFinder == null || + !globals.fuchsiaArtifacts.devFinder.existsSync()) { + return null; + } + devices = await fuchsiaDevFinder.list(timeout: timeout); + } else { + if (globals.fuchsiaArtifacts.ffx == null || + !globals.fuchsiaArtifacts.ffx.existsSync()) { + return null; + } + devices = await fuchsiaFfx.list(timeout: timeout); } - final List devices = await fuchsiaDevFinder.list(timeout: timeout); if (devices == null) { return null; } @@ -112,6 +127,7 @@ class FuchsiaArtifacts { FuchsiaArtifacts({ this.sshConfig, this.devFinder, + this.ffx, this.pm, }); @@ -140,11 +156,13 @@ class FuchsiaArtifacts { final String fuchsia = globals.cache.getArtifactDirectory('fuchsia').path; final String tools = globals.fs.path.join(fuchsia, 'tools'); final File devFinder = globals.fs.file(globals.fs.path.join(tools, 'device-finder')); + final File ffx = globals.fs.file(globals.fs.path.join(tools, 'x64/ffx')); final File pm = globals.fs.file(globals.fs.path.join(tools, 'pm')); return FuchsiaArtifacts( sshConfig: sshConfig, devFinder: devFinder.existsSync() ? devFinder : null, + ffx: ffx.existsSync() ? ffx : null, pm: pm.existsSync() ? pm : null, ); } @@ -160,6 +178,10 @@ class FuchsiaArtifacts { /// Fuchsia devices. final File devFinder; + /// The location of the ffx tool used to locate connected + /// Fuchsia devices. + final File ffx; + /// The pm tool. final File pm; diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_workflow.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_workflow.dart index 23ab33dfe6..0e1c04d2c6 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_workflow.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_workflow.dart @@ -35,14 +35,29 @@ class FuchsiaWorkflow implements Workflow { @override bool get appliesToHostPlatform => _featureFlags.isFuchsiaEnabled && (_platform.isLinux || _platform.isMacOS); + bool get shouldUseDeviceFinder { + final String useDeviceFinder = _platform.environment.containsKey('FUCHSIA_DISABLED_ffx_discovery') + ? _platform.environment['FUCHSIA_DISABLED_ffx_discovery'] : '0'; + if (useDeviceFinder == '1') { + return true; + } + return false; + } + @override bool get canListDevices { - return _fuchsiaArtifacts.devFinder != null; + if (shouldUseDeviceFinder) { + return _fuchsiaArtifacts.devFinder != null; + } + return _fuchsiaArtifacts.ffx != null; } @override bool get canLaunchDevices { - return _fuchsiaArtifacts.devFinder != null && _fuchsiaArtifacts.sshConfig != null; + if (shouldUseDeviceFinder) { + return _fuchsiaArtifacts.devFinder != null && _fuchsiaArtifacts.sshConfig != null; + } + return _fuchsiaArtifacts.ffx != null && _fuchsiaArtifacts.sshConfig != null; } @override diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart index 2a5108f287..66aa3d93f9 100644 --- a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart +++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart @@ -25,6 +25,7 @@ import 'package:flutter_tools/src/fuchsia/amber_ctl.dart'; import 'package:flutter_tools/src/fuchsia/application_package.dart'; import 'package:flutter_tools/src/fuchsia/fuchsia_dev_finder.dart'; import 'package:flutter_tools/src/fuchsia/fuchsia_device.dart'; +import 'package:flutter_tools/src/fuchsia/fuchsia_ffx.dart'; import 'package:flutter_tools/src/fuchsia/fuchsia_kernel_compiler.dart'; import 'package:flutter_tools/src/fuchsia/fuchsia_pm.dart'; import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart'; @@ -103,17 +104,18 @@ void main() { expect(await fuchsiaDevices.pollingGetDevices(), isEmpty); }); - testWithoutContext('can parse device-finder output for single device', () async { + testWithoutContext('can parse ffx output for single device', () async { final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk(); final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( - platform: FakePlatform(operatingSystem: 'linux'), + platform: FakePlatform(operatingSystem: 'linux', environment: {},), fuchsiaSdk: fuchsiaSdk, fuchsiaWorkflow: fuchsiaWorkflow, logger: BufferLogger.test(), ); + when(fuchsiaWorkflow.shouldUseDeviceFinder).thenReturn(false); when(fuchsiaWorkflow.canListDevices).thenReturn(true); - when(fuchsiaSdk.listDevices()).thenAnswer((Invocation invocation) async { + when(fuchsiaSdk.listDevices(useDeviceFinder: false)).thenAnswer((Invocation invocation) async { return '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel'; }); @@ -123,7 +125,28 @@ void main() { expect(device.id, '192.168.42.10'); }); - testWithoutContext('can parse device-finder output for multiple devices', () async { + testWithoutContext('can parse device-finder output for single device', () async { + final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); + final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk(); + final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( + platform: FakePlatform(operatingSystem: 'linux', environment: {'FUCHSIA_DISABLED_ffx_discovery': '1'},), + fuchsiaSdk: fuchsiaSdk, + fuchsiaWorkflow: fuchsiaWorkflow, + logger: BufferLogger.test(), + ); + when(fuchsiaWorkflow.shouldUseDeviceFinder).thenReturn(true); + when(fuchsiaWorkflow.canListDevices).thenReturn(true); + when(fuchsiaSdk.listDevices(useDeviceFinder: true)).thenAnswer((Invocation invocation) async { + return '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel'; + }); + + final Device device = (await fuchsiaDevices.pollingGetDevices()).single; + + expect(device.name, 'paper-pulp-bush-angel'); + expect(device.id, '192.168.11.999'); + }); + + testWithoutContext('can parse ffx output for multiple devices', () async { final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk(); final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( @@ -132,8 +155,9 @@ void main() { fuchsiaWorkflow: fuchsiaWorkflow, logger: BufferLogger.test(), ); + when(fuchsiaWorkflow.shouldUseDeviceFinder).thenReturn(false); when(fuchsiaWorkflow.canListDevices).thenReturn(true); - when(fuchsiaSdk.listDevices()).thenAnswer((Invocation invocation) async { + when(fuchsiaSdk.listDevices(useDeviceFinder: false)).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'; }); @@ -146,7 +170,7 @@ void main() { expect(devices.last.id, '192.168.42.10'); }); - testWithoutContext('can parse junk output from the dev-finder', () async { + testWithoutContext('can parse device-finder output for multiple devices', () async { final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk(); final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( @@ -155,6 +179,31 @@ void main() { fuchsiaWorkflow: fuchsiaWorkflow, logger: BufferLogger.test(), ); + when(fuchsiaWorkflow.shouldUseDeviceFinder).thenReturn(true); + when(fuchsiaWorkflow.canListDevices).thenReturn(true); + when(fuchsiaSdk.listDevices(useDeviceFinder: true)).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 devices = await fuchsiaDevices.pollingGetDevices(); + + expect(devices.first.name, 'paper-pulp-bush-angel'); + expect(devices.first.id, '192.168.11.999'); + expect(devices.last.name, 'foo-bar-fiz-buzz'); + expect(devices.last.id, '192.168.11.999'); + }); + + testWithoutContext('can parse junk output from ffx', () 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.shouldUseDeviceFinder).thenReturn(false); when(fuchsiaWorkflow.canListDevices).thenReturn(true); when(fuchsiaSdk.listDevices()).thenAnswer((Invocation invocation) async { return 'junk'; @@ -165,6 +214,26 @@ void main() { expect(devices, isEmpty); }); + testWithoutContext('can parse junk output from device-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.shouldUseDeviceFinder).thenReturn(true); + when(fuchsiaWorkflow.canListDevices).thenReturn(true); + when(fuchsiaSdk.listDevices(useDeviceFinder: true)).thenAnswer((Invocation invocation) async { + return 'junk'; + }); + + final List devices = await fuchsiaDevices.pollingGetDevices(); + + expect(devices, isEmpty); + }); + testUsingContext('disposing device disposes the portForwarder', () async { final MockPortForwarder mockPortForwarder = MockPortForwarder(); final FuchsiaDevice device = FuchsiaDevice('123'); @@ -335,6 +404,7 @@ void main() { FuchsiaArtifacts: () => FuchsiaArtifacts( sshConfig: artifactFile, devFinder: artifactFile, + ffx: artifactFile, ), FuchsiaSdk: () => MockFuchsiaSdk(), }); @@ -355,6 +425,7 @@ void main() { StreamController> stdout; StreamController> stderr; File devFinder; + File ffx; File sshConfig; setUp(() { @@ -370,6 +441,7 @@ void main() { when(mockProcess.stderr).thenAnswer((Invocation _) => stderr.stream); final FileSystem memoryFileSystem = MemoryFileSystem.test(); devFinder = memoryFileSystem.file('device-finder')..writeAsStringSync('\n'); + ffx = memoryFileSystem.file('ffx')..writeAsStringSync('\n'); sshConfig = memoryFileSystem.file('ssh_config')..writeAsStringSync('\n'); }); @@ -403,7 +475,7 @@ void main() { ProcessManager: () => mockProcessManager, SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)), FuchsiaArtifacts: () => - FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig), + FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: ffx), }); testUsingContext('cuts off prior logs', () async { @@ -429,7 +501,7 @@ void main() { ProcessManager: () => mockProcessManager, SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 29, 45)), FuchsiaArtifacts: () => - FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig), + FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: ffx), }); testUsingContext('can be parsed for all apps', () async { @@ -458,7 +530,7 @@ void main() { ProcessManager: () => mockProcessManager, SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)), FuchsiaArtifacts: () => - FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig), + FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: ffx), }); }); }); @@ -1577,7 +1649,19 @@ class FailingKernelCompiler implements FuchsiaKernelCompiler { class FakeFuchsiaDevFinder implements FuchsiaDevFinder { @override Future> list({ Duration timeout }) async { - return ['192.168.42.172 scare-cable-skip-joy']; + return ['192.168.11.999 scare-cable-device-finder']; + } + + @override + Future resolve(String deviceName) async { + return '192.168.11.999'; + } +} + +class FakeFuchsiaFfx implements FuchsiaFfx { + @override + Future> list({Duration timeout}) async { + return ['192.168.42.172 scare-cable-skip-ffx']; } @override @@ -1591,9 +1675,11 @@ class MockFuchsiaSdk extends Mock implements FuchsiaSdk { FuchsiaPM pm, FuchsiaKernelCompiler compiler, FuchsiaDevFinder devFinder, + FuchsiaFfx ffx, }) : fuchsiaPM = pm ?? FakeFuchsiaPM(), fuchsiaKernelCompiler = compiler ?? FakeFuchsiaKernelCompiler(), - fuchsiaDevFinder = devFinder ?? FakeFuchsiaDevFinder(); + fuchsiaDevFinder = devFinder ?? FakeFuchsiaDevFinder(), + fuchsiaFfx = ffx ?? FakeFuchsiaFfx(); @override final FuchsiaPM fuchsiaPM; @@ -1603,6 +1689,9 @@ class MockFuchsiaSdk extends Mock implements FuchsiaSdk { @override final FuchsiaDevFinder fuchsiaDevFinder; + + @override + final FuchsiaFfx fuchsiaFfx; } class MockDartDevelopmentService extends Mock implements DartDevelopmentService {} diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_ffx_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_ffx_test.dart new file mode 100644 index 0000000000..bd347f45e9 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_ffx_test.dart @@ -0,0 +1,138 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/fuchsia/fuchsia_ffx.dart'; +import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart'; +import 'package:process/process.dart'; +import 'package:test/fake.dart'; + +import '../../src/common.dart'; +import '../../src/context.dart'; + +void main() { + FakeFuchsiaArtifacts fakeFuchsiaArtifacts; + BufferLogger logger; + MemoryFileSystem memoryFileSystem; + File ffx; + + setUp(() { + fakeFuchsiaArtifacts = FakeFuchsiaArtifacts(); + memoryFileSystem = MemoryFileSystem.test(); + logger = BufferLogger.test(); + ffx = memoryFileSystem.file('ffx'); + fakeFuchsiaArtifacts.ffx = ffx; + }); + + group('ffx list', () { + testWithoutContext('ffx not found', () { + final FuchsiaFfx fuchsiaFfx = FuchsiaFfx( + fuchsiaArtifacts: fakeFuchsiaArtifacts, + logger: logger, + processManager: FakeProcessManager.any(), + ); + + expect(() async => await fuchsiaFfx.list(), + throwsToolExit(message: 'Fuchsia ffx tool not found.')); + }); + + testWithoutContext('no device found', () async { + ffx.createSync(); + + final ProcessManager processManager = + FakeProcessManager.list([ + FakeCommand( + command: [ffx.path, 'target', 'list', '--format', 's'], + exitCode: 0, + stderr: 'No devices found.', + ), + ]); + + final FuchsiaFfx fuchsiaFfx = FuchsiaFfx( + fuchsiaArtifacts: fakeFuchsiaArtifacts, + logger: logger, + processManager: processManager, + ); + + expect(await fuchsiaFfx.list(), isNull); + expect(logger.errorText, isEmpty); + }); + + testWithoutContext('error', () async { + ffx.createSync(); + + final ProcessManager processManager = + FakeProcessManager.list([ + FakeCommand( + command: [ffx.path, 'target', 'list', '--format', 's'], + exitCode: 1, + stderr: 'unexpected error', + ), + ]); + + final FuchsiaFfx fuchsiaFfx = FuchsiaFfx( + fuchsiaArtifacts: fakeFuchsiaArtifacts, + logger: logger, + processManager: processManager, + ); + + expect(await fuchsiaFfx.list(), isNull); + expect(logger.errorText, contains('unexpected error')); + }); + + testWithoutContext('devices found', () async { + ffx.createSync(); + + final ProcessManager processManager = + FakeProcessManager.list([ + FakeCommand( + command: [ffx.path, 'target', 'list', '--format', 's'], + exitCode: 0, + stdout: 'device1\ndevice2', + ), + ]); + + final FuchsiaFfx fuchsiaFfx = FuchsiaFfx( + fuchsiaArtifacts: fakeFuchsiaArtifacts, + logger: logger, + processManager: processManager, + ); + + expect(await fuchsiaFfx.list(), ['device1', 'device2']); + expect(logger.errorText, isEmpty); + }); + + testWithoutContext('timeout', () async { + ffx.createSync(); + + final ProcessManager processManager = + FakeProcessManager.list([ + FakeCommand( + command: [ffx.path, '-T', '2', 'target', 'list', '--format', 's'], + exitCode: 0, + stdout: 'device1', + ), + ]); + + final FuchsiaFfx fuchsiaFfx = FuchsiaFfx( + fuchsiaArtifacts: fakeFuchsiaArtifacts, + logger: logger, + processManager: processManager, + ); + + expect(await fuchsiaFfx.list(timeout: const Duration(seconds: 2)), + ['device1']); + }); + }); +} + +class FakeFuchsiaArtifacts extends Fake implements FuchsiaArtifacts { + @override + File ffx; +} diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_workflow_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_workflow_test.dart index 2e1fb94660..a2cd36a987 100644 --- a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_workflow_test.dart +++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_workflow_test.dart @@ -17,6 +17,7 @@ void main() { final FileSystem fileSystem = MemoryFileSystem.test(); final File devFinder = fileSystem.file('dev_finder'); final File sshConfig = fileSystem.file('ssh_config'); + final File ffx = fileSystem.file('ffx'); testWithoutContext('Fuchsia workflow does not apply to host platform if feature is disabled', () { final FuchsiaWorkflow fuchsiaWorkflow = FuchsiaWorkflow( @@ -38,11 +39,11 @@ void main() { expect(fuchsiaWorkflow.appliesToHostPlatform, false); }); - testWithoutContext('Fuchsia workflow can not list and launch devices if there is no ssh config and dev finder', () { + testWithoutContext('Fuchsia workflow can not list and launch devices if there is no ffx when using default workflow', () { final FuchsiaWorkflow fuchsiaWorkflow = FuchsiaWorkflow( featureFlags: TestFeatureFlags(), - fuchsiaArtifacts: FuchsiaArtifacts(devFinder: null, sshConfig: null), - platform: FakePlatform(operatingSystem: 'linux'), + fuchsiaArtifacts: FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: null), + platform: FakePlatform(operatingSystem: 'linux', environment: {}), ); expect(fuchsiaWorkflow.canLaunchDevices, false); @@ -50,11 +51,23 @@ void main() { expect(fuchsiaWorkflow.canListEmulators, false); }); - testWithoutContext('Fuchsia workflow can not list and launch devices if there is no ssh config and dev finder', () { + testWithoutContext('Fuchsia workflow can not list and launch devices if there is no dev finder when ffx is disabled', () { final FuchsiaWorkflow fuchsiaWorkflow = FuchsiaWorkflow( featureFlags: TestFeatureFlags(), - fuchsiaArtifacts: FuchsiaArtifacts(devFinder: devFinder, sshConfig: null), - platform: FakePlatform(operatingSystem: 'linux'), + fuchsiaArtifacts: FuchsiaArtifacts(devFinder: null, sshConfig: sshConfig, ffx: ffx), + platform: FakePlatform(operatingSystem: 'linux', environment: {'FUCHSIA_DISABLED_ffx_discovery': '1'}), + ); + + expect(fuchsiaWorkflow.canLaunchDevices, false); + expect(fuchsiaWorkflow.canListDevices, false); + expect(fuchsiaWorkflow.canListEmulators, false); + }); + + testWithoutContext('Fuchsia workflow can not launch devices if there is no ssh config when using default workflow', () { + final FuchsiaWorkflow fuchsiaWorkflow = FuchsiaWorkflow( + featureFlags: TestFeatureFlags(), + fuchsiaArtifacts: FuchsiaArtifacts(sshConfig: null, ffx: ffx), + platform: FakePlatform(operatingSystem: 'linux', environment: {}), ); expect(fuchsiaWorkflow.canLaunchDevices, false); @@ -62,11 +75,35 @@ void main() { expect(fuchsiaWorkflow.canListEmulators, false); }); - testWithoutContext('Fuchsia workflow can list and launch devices supported with sufficient SDK artifacts', () { + testWithoutContext('Fuchsia workflow can not launch devices if there is no ssh config when ffx is disabled', () { final FuchsiaWorkflow fuchsiaWorkflow = FuchsiaWorkflow( featureFlags: TestFeatureFlags(), - fuchsiaArtifacts: FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig), - platform: FakePlatform(operatingSystem: 'linux'), + fuchsiaArtifacts: FuchsiaArtifacts(sshConfig: null, devFinder: devFinder), + platform: FakePlatform(operatingSystem: 'linux', environment: {'FUCHSIA_DISABLED_ffx_discovery': '1'}), + ); + + expect(fuchsiaWorkflow.canLaunchDevices, false); + expect(fuchsiaWorkflow.canListDevices, true); + expect(fuchsiaWorkflow.canListEmulators, false); + }); + + testWithoutContext('Fuchsia workflow can list and launch devices supported with sufficient SDK artifacts when using default workflow', () { + final FuchsiaWorkflow fuchsiaWorkflow = FuchsiaWorkflow( + featureFlags: TestFeatureFlags(), + fuchsiaArtifacts: FuchsiaArtifacts(devFinder: null, sshConfig: sshConfig, ffx: ffx), + platform: FakePlatform(operatingSystem: 'linux', environment: {}), + ); + + expect(fuchsiaWorkflow.canLaunchDevices, true); + expect(fuchsiaWorkflow.canListDevices, true); + expect(fuchsiaWorkflow.canListEmulators, false); + }); + + testWithoutContext('Fuchsia workflow can list and launch devices supported with sufficient SDK artifacts when ffx is disabled', () { + final FuchsiaWorkflow fuchsiaWorkflow = FuchsiaWorkflow( + featureFlags: TestFeatureFlags(), + fuchsiaArtifacts: FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: null), + platform: FakePlatform(operatingSystem: 'linux', environment: {'FUCHSIA_DISABLED_ffx_discovery': '1'}), ); expect(fuchsiaWorkflow.canLaunchDevices, true); @@ -77,8 +114,8 @@ void main() { testWithoutContext('Fuchsia workflow can list and launch devices supported with sufficient SDK artifacts on macOS', () { final FuchsiaWorkflow fuchsiaWorkflow = FuchsiaWorkflow( featureFlags: TestFeatureFlags(), - fuchsiaArtifacts: FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig), - platform: FakePlatform(operatingSystem: 'macOS'), + fuchsiaArtifacts: FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: ffx), + platform: FakePlatform(operatingSystem: 'macOS', environment: {}), ); expect(fuchsiaWorkflow.canLaunchDevices, true);