blob: f7e23f78ba50139e306e61e221c007dcf427f283 [file] [log] [blame]
// 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.
import 'dart:async';
import 'dart:convert';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/fuchsia/application_package.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';
import 'package:flutter_tools/src/fuchsia/pkgctl.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fakes.dart';
void main() {
group('Fuchsia app start and stop: ', () {
late MemoryFileSystem memoryFileSystem;
late FakeOperatingSystemUtils osUtils;
late FakeFuchsiaDeviceTools fuchsiaDeviceTools;
late FakeFuchsiaSdk fuchsiaSdk;
late Artifacts artifacts;
late FakeProcessManager fakeSuccessfulProcessManager;
late FakeProcessManager fakeFailedProcessManagerForHostAddress;
late File sshConfig;
setUp(() {
memoryFileSystem = MemoryFileSystem.test();
osUtils = FakeOperatingSystemUtils();
fuchsiaDeviceTools = FakeFuchsiaDeviceTools();
fuchsiaSdk = FakeFuchsiaSdk();
sshConfig = MemoryFileSystem.test().file('ssh_config')
..writeAsStringSync('\n');
artifacts = Artifacts.test();
for (final BuildMode mode in <BuildMode>[
BuildMode.debug,
BuildMode.release
]) {
memoryFileSystem
.file(
artifacts.getArtifactPath(Artifact.fuchsiaKernelCompiler,
platform: TargetPlatform.fuchsia_arm64, mode: mode),
)
.createSync();
memoryFileSystem
.file(
artifacts.getArtifactPath(Artifact.platformKernelDill,
platform: TargetPlatform.fuchsia_arm64, mode: mode),
)
.createSync();
memoryFileSystem
.file(
artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath,
platform: TargetPlatform.fuchsia_arm64, mode: mode),
)
.createSync();
memoryFileSystem
.file(
artifacts.getArtifactPath(Artifact.fuchsiaFlutterRunner,
platform: TargetPlatform.fuchsia_arm64, mode: mode),
)
.createSync();
}
fakeSuccessfulProcessManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: <String>[
'ssh',
'-F',
sshConfig.absolute.path,
'123',
r'echo $SSH_CONNECTION'
],
stdout:
'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
),
]);
fakeFailedProcessManagerForHostAddress =
FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: <String>[
'ssh',
'-F',
sshConfig.absolute.path,
'123',
r'echo $SSH_CONNECTION'
],
stdout:
'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
exitCode: 1,
),
]);
});
Future<LaunchResult> setupAndStartApp({
required bool prebuilt,
required BuildMode mode,
}) async {
const String appName = 'app_name';
final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123');
globals.fs.directory('fuchsia').createSync(recursive: true);
final File pubspecFile = globals.fs.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
FuchsiaApp? app;
if (prebuilt) {
final File far = globals.fs.file('app_name-0.far')..createSync();
app = FuchsiaApp.fromPrebuiltApp(far);
} else {
globals.fs.file(globals.fs.path.join('fuchsia', 'meta', '$appName.cm'))
..createSync(recursive: true)
..writeAsStringSync('{}');
globals.fs.file('.packages').createSync();
globals.fs
.file(globals.fs.path.join('lib', 'main.dart'))
.createSync(recursive: true);
app = BuildableFuchsiaApp(
project:
FlutterProject.fromDirectoryTest(globals.fs.currentDirectory)
.fuchsia);
}
final DebuggingOptions debuggingOptions = DebuggingOptions.disabled(
BuildInfo(mode, null, treeShakeIcons: false));
return device.startApp(
app!,
prebuiltApplication: prebuilt,
debuggingOptions: debuggingOptions,
);
}
testUsingContext(
'start prebuilt in release mode fails without session',
() async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.release);
expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start prebuilt in release mode with session', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.release);
expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils,
});
testUsingContext(
'start and stop prebuilt in release mode fails without session',
() async {
const String appName = 'app_name';
final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123');
globals.fs.directory('fuchsia').createSync(recursive: true);
final File pubspecFile = globals.fs.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
final File far = globals.fs.file('app_name-0.far')..createSync();
final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far)!;
final DebuggingOptions debuggingOptions = DebuggingOptions.disabled(
const BuildInfo(BuildMode.release, null, treeShakeIcons: false));
final LaunchResult launchResult = await device.startApp(app,
prebuiltApplication: true, debuggingOptions: debuggingOptions);
expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start and stop prebuilt in release mode with session',
() async {
const String appName = 'app_name';
final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123');
globals.fs.directory('fuchsia').createSync(recursive: true);
final File pubspecFile = globals.fs.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
final File far = globals.fs.file('app_name-0.far')..createSync();
final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far)!;
final DebuggingOptions debuggingOptions = DebuggingOptions.disabled(
const BuildInfo(BuildMode.release, null, treeShakeIcons: false));
final LaunchResult launchResult = await device.startApp(app,
prebuiltApplication: true, debuggingOptions: debuggingOptions);
expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isFalse);
expect(await device.stopApp(app), isTrue);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils,
});
testUsingContext(
'start prebuilt in debug mode fails without session',
() async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.debug);
expect(launchResult.started, isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start prebuilt in debug mode with session', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.debug);
expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isTrue);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils,
});
testUsingContext(
'start buildable in release mode fails without session',
() async {
expect(
() async => setupAndStartApp(prebuilt: false, mode: BuildMode.release),
throwsToolExit(
message: 'This tool does not currently build apps for fuchsia.\n'
'Build the app using a supported Fuchsia workflow.\n'
'Then use the --use-application-binary flag.'));
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>[
'Artifact.genSnapshot.TargetPlatform.fuchsia_arm64.release',
'--deterministic',
'--snapshot_kind=app-aot-elf',
'--elf=build/fuchsia/elf.aotsnapshot',
'build/fuchsia/app_name.dil',
],
),
FakeCommand(
command: <String>[
'ssh',
'-F',
sshConfig.absolute.path,
'123',
r'echo $SSH_CONNECTION'
],
stdout:
'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
),
]),
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext(
'start buildable in release mode with session fails, does not build apps yet',
() async {
expect(
() async => setupAndStartApp(prebuilt: false, mode: BuildMode.release),
throwsToolExit(
message: 'This tool does not currently build apps for fuchsia.\n'
'Build the app using a supported Fuchsia workflow.\n'
'Then use the --use-application-binary flag.'));
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>[
'Artifact.genSnapshot.TargetPlatform.fuchsia_arm64.release',
'--deterministic',
'--snapshot_kind=app-aot-elf',
'--elf=build/fuchsia/elf.aotsnapshot',
'build/fuchsia/app_name.dil',
],
),
FakeCommand(
command: <String>[
'ssh',
'-F',
sshConfig.absolute.path,
'123',
r'echo $SSH_CONNECTION'
],
stdout:
'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
),
]),
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils,
});
testUsingContext(
'start buildable in debug mode fails without session',
() async {
expect(
() async => setupAndStartApp(prebuilt: false, mode: BuildMode.debug),
throwsToolExit(
message: 'This tool does not currently build apps for fuchsia.\n'
'Build the app using a supported Fuchsia workflow.\n'
'Then use the --use-application-binary flag.'));
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext(
'start buildable in debug mode with session fails, does not build apps yet',
() async {
expect(
() async => setupAndStartApp(prebuilt: false, mode: BuildMode.debug),
throwsToolExit(
message: 'This tool does not currently build apps for fuchsia.\n'
'Build the app using a supported Fuchsia workflow.\n'
'Then use the --use-application-binary flag.'));
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils,
});
testUsingContext('fail when cant get ssh config', () async {
expect(
() async => setupAndStartApp(prebuilt: true, mode: BuildMode.release),
throwsToolExit(
message: 'Cannot interact with device. No ssh config.\n'
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.'));
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.any(),
FuchsiaArtifacts: () => FuchsiaArtifacts(),
FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils,
});
testUsingContext('fail when cant get host address', () async {
expect(() async => FuchsiaDeviceWithFakeDiscovery('123').hostAddress,
throwsToolExit(message: 'Failed to get local address, aborting.'));
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeFailedProcessManagerForHostAddress,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
OperatingSystemUtils: () => osUtils,
Platform: () => FakePlatform(),
});
testUsingContext('fail with correct LaunchResult when pm fails', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.release);
expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => FakeFuchsiaSdk(pm: FailingPM()),
OperatingSystemUtils: () => osUtils,
});
testUsingContext('fail with correct LaunchResult when pkgctl fails',
() async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.release);
expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => FakeFuchsiaDeviceTools(pkgctl: FailingPkgctl()),
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
});
}
Process _createFakeProcess({
int exitCode = 0,
String stdout = '',
String stderr = '',
bool persistent = false,
}) {
final Stream<List<int>> stdoutStream =
Stream<List<int>>.fromIterable(<List<int>>[
utf8.encode(stdout),
]);
final Stream<List<int>> stderrStream =
Stream<List<int>>.fromIterable(<List<int>>[
utf8.encode(stderr),
]);
final Completer<int> exitCodeCompleter = Completer<int>();
final Process process = FakeProcess(
stdout: stdoutStream,
stderr: stderrStream,
exitCode:
persistent ? exitCodeCompleter.future : Future<int>.value(exitCode),
);
return process;
}
class FuchsiaDeviceWithFakeDiscovery extends FuchsiaDevice {
FuchsiaDeviceWithFakeDiscovery(super.id, {super.name = ''});
@override
FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(
String isolateName) {
return FakeFuchsiaIsolateDiscoveryProtocol();
}
@override
Future<TargetPlatform> get targetPlatform async =>
TargetPlatform.fuchsia_arm64;
}
class FakeFuchsiaIsolateDiscoveryProtocol
implements FuchsiaIsolateDiscoveryProtocol {
@override
FutureOr<Uri> get uri => Uri.parse('http://[::1]:37');
@override
void dispose() {}
}
class FakeFuchsiaPkgctl implements FuchsiaPkgctl {
@override
Future<bool> addRepo(
FuchsiaDevice device, FuchsiaPackageServer server) async {
return true;
}
@override
Future<bool> resolve(
FuchsiaDevice device, String serverName, String packageName) async {
return true;
}
@override
Future<bool> rmRepo(FuchsiaDevice device, FuchsiaPackageServer server) async {
return true;
}
}
class FailingPkgctl implements FuchsiaPkgctl {
@override
Future<bool> addRepo(
FuchsiaDevice device, FuchsiaPackageServer server) async {
return false;
}
@override
Future<bool> resolve(
FuchsiaDevice device, String serverName, String packageName) async {
return false;
}
@override
Future<bool> rmRepo(FuchsiaDevice device, FuchsiaPackageServer server) async {
return false;
}
}
class FakeFuchsiaDeviceTools implements FuchsiaDeviceTools {
FakeFuchsiaDeviceTools({
FuchsiaPkgctl? pkgctl,
FuchsiaFfx? ffx,
}) : pkgctl = pkgctl ?? FakeFuchsiaPkgctl(),
ffx = ffx ?? FakeFuchsiaFfx();
@override
final FuchsiaPkgctl pkgctl;
@override
final FuchsiaFfx ffx;
}
class FakeFuchsiaPM implements FuchsiaPM {
String? _appName;
@override
Future<bool> init(String buildPath, String appName) async {
if (!globals.fs.directory(buildPath).existsSync()) {
return false;
}
globals.fs
.file(globals.fs.path.join(buildPath, 'meta', 'package'))
.createSync(recursive: true);
_appName = appName;
return true;
}
@override
Future<bool> build(String buildPath, String manifestPath) async {
if (!globals.fs
.file(globals.fs.path.join(buildPath, 'meta', 'package'))
.existsSync() ||
!globals.fs.file(manifestPath).existsSync()) {
return false;
}
globals.fs
.file(globals.fs.path.join(buildPath, 'meta.far'))
.createSync(recursive: true);
return true;
}
@override
Future<bool> archive(String buildPath, String manifestPath) async {
if (!globals.fs
.file(globals.fs.path.join(buildPath, 'meta', 'package'))
.existsSync() ||
!globals.fs.file(manifestPath).existsSync()) {
return false;
}
if (_appName == null) {
return false;
}
globals.fs
.file(globals.fs.path.join(buildPath, '$_appName-0.far'))
.createSync(recursive: true);
return true;
}
@override
Future<bool> newrepo(String repoPath) async {
if (!globals.fs.directory(repoPath).existsSync()) {
return false;
}
return true;
}
@override
Future<Process> serve(String repoPath, String host, int port) async {
return _createFakeProcess(persistent: true);
}
@override
Future<bool> publish(String repoPath, String packagePath) async {
if (!globals.fs.directory(repoPath).existsSync()) {
return false;
}
if (!globals.fs.file(packagePath).existsSync()) {
return false;
}
return true;
}
}
class FailingPM implements FuchsiaPM {
@override
Future<bool> init(String buildPath, String appName) async {
return false;
}
@override
Future<bool> build(String buildPath, String manifestPath) async {
return false;
}
@override
Future<bool> archive(String buildPath, String manifestPath) async {
return false;
}
@override
Future<bool> newrepo(String repoPath) async {
return false;
}
@override
Future<Process> serve(String repoPath, String host, int port) async {
return _createFakeProcess(exitCode: 6);
}
@override
Future<bool> publish(String repoPath, String packagePath) async {
return false;
}
}
class FakeFuchsiaKernelCompiler implements FuchsiaKernelCompiler {
@override
Future<void> build({
required FuchsiaProject fuchsiaProject,
required String target, // E.g., lib/main.dart
BuildInfo buildInfo = BuildInfo.debug,
}) async {
final String outDir = getFuchsiaBuildDirectory();
final String appName = fuchsiaProject.project.manifest.appName;
final String manifestPath =
globals.fs.path.join(outDir, '$appName.dilpmanifest');
globals.fs.file(manifestPath).createSync(recursive: true);
}
}
class FakeFuchsiaFfx implements FuchsiaFfx {
@override
Future<List<String>> list({Duration? timeout}) async {
return <String>['192.168.42.172 scare-cable-skip-ffx'];
}
@override
Future<String> resolve(String deviceName) async {
return '192.168.42.10';
}
@override
Future<String?> sessionShow() async {
return null;
}
@override
Future<bool> sessionAdd(String url) async {
return false;
}
}
class FakeFuchsiaFfxWithSession implements FuchsiaFfx {
@override
Future<List<String>> list({Duration? timeout}) async {
return <String>['192.168.42.172 scare-cable-skip-ffx'];
}
@override
Future<String> resolve(String deviceName) async {
return '192.168.42.10';
}
@override
Future<String> sessionShow() async {
return 'session info';
}
@override
Future<bool> sessionAdd(String url) async {
return true;
}
}
class FakeFuchsiaSdk extends Fake implements FuchsiaSdk {
FakeFuchsiaSdk({
FuchsiaPM? pm,
FuchsiaKernelCompiler? compiler,
FuchsiaFfx? ffx,
}) : fuchsiaPM = pm ?? FakeFuchsiaPM(),
fuchsiaKernelCompiler = compiler ?? FakeFuchsiaKernelCompiler(),
fuchsiaFfx = ffx ?? FakeFuchsiaFfx();
@override
final FuchsiaPM fuchsiaPM;
@override
final FuchsiaKernelCompiler fuchsiaKernelCompiler;
@override
final FuchsiaFfx fuchsiaFfx;
}