Unverified Commit f996033f authored by omerlevran46's avatar omerlevran46 Committed by GitHub

[fuchsia] - migrate devicer discovery workflows to use ffx (#76241)

parent 54455c70
......@@ -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
......
......@@ -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<List<String>> discoverDevices() async {
final List<String> output = (await eval(_devFinder, <String>['list', '-full']))
final List<String> output = (await eval(_ffx, <String>['target', 'list', '--format', 's']))
.trim()
.split('\n');
......@@ -396,16 +396,20 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery {
final Map<String, HealthCheckResult> results = <String, HealthCheckResult>{};
for (final String deviceId in await discoverDevices()) {
try {
StringBuffer stderr;
final int resolveResult = await exec(
_devFinder,
_ffx,
<String>[
'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');
......
......@@ -322,6 +322,7 @@ Future<int> exec(
List<String> arguments, {
Map<String, String> 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<int> exec(
arguments,
environment: environment,
canFail : canFail,
stderr: stderr,
workingDirectory: workingDirectory,
);
}
......
......@@ -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<void> main(List<String> 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<void> main(List<String> 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<void> main(List<String> args) async {
overrides: <Type, Generator>{
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,
......
......@@ -181,8 +181,11 @@ class FuchsiaDevices extends PollingDeviceDiscovery {
if (!_fuchsiaWorkflow.canListDevices) {
return <Device>[];
}
final List<String> text = (await _fuchsiaSdk.listDevices(timeout: timeout))
?.split('\n');
// TODO(omerlevran): Remove once soft transition is complete fxb/67602.
final List<String> text = (await _fuchsiaSdk.listDevices(
timeout: timeout,
useDeviceFinder: _fuchsiaWorkflow.shouldUseDeviceFinder,
))?.split('\n');
if (text == null || text.isEmpty) {
return <Device>[];
}
......@@ -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;
......
// 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 <config>] [-e <env>] [-t <target>] [-T <timeout>] [-v] [<command>] [<args>]
// 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<String>> list({Duration timeout}) async {
if (_fuchsiaArtifacts.ffx == null || !_fuchsiaArtifacts.ffx.existsSync()) {
throwToolExit('Fuchsia ffx tool not found.');
}
final List<String> command = <String>[
_fuchsiaArtifacts.ffx.path,
if (timeout != null)
...<String>['-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<String> resolve(String deviceName) async {
if (_fuchsiaArtifacts.ffx == null || !_fuchsiaArtifacts.ffx.existsSync()) {
throwToolExit('Fuchsia ffx tool not found.');
}
final List<String> command = <String>[
_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();
}
}
......@@ -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<Process> serve(String repoPath, String host, int port) async {
if (globals.fuchsiaArtifacts.pm == null) {
......
......@@ -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<String> 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<String> listDevices({Duration timeout, bool useDeviceFinder = false}) async {
List<String> 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<String> 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;
......
......@@ -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
......
// 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>[
FakeCommand(
command: <String>[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>[
FakeCommand(
command: <String>[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>[
FakeCommand(
command: <String>[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(), <String>['device1', 'device2']);
expect(logger.errorText, isEmpty);
});
testWithoutContext('timeout', () async {
ffx.createSync();
final ProcessManager processManager =
FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: <String>[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)),
<String>['device1']);
});
});
}
class FakeFuchsiaArtifacts extends Fake implements FuchsiaArtifacts {
@override
File ffx;
}
......@@ -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: <String, String>{}),
);
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: <String, String>{'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: <String, String>{}),
);
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: <String, String>{'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: <String, String>{}),
);
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: <String, String>{'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: <String, String>{}),
);
expect(fuchsiaWorkflow.canLaunchDevices, true);
......
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