Unverified Commit 549f70c9 authored by Rich Kadel's avatar Rich Kadel Committed by GitHub

Remove outdated Fuchsia concepts (#107335)

Fuchsia will soon remove all support for Component Framework version 1
components (recognized by component manifests ending in `.cmx`).
Notably, some of the `flutter` tool commands for Fuchsia devices--
notably, but not limited to, those related to CFv1--are outdated, and
either do not work today or soon won't work.

This PR removes the outdated components and commands, replacing some
with the newer version, or simply removing the non-working features,
in some cases.
parent a3b531dd
{
"program": {
"data": "data/complex_layout"
},
"sandbox": {
"services": [
"fuchsia.cobalt.LoggerFactory",
"fuchsia.fonts.Provider",
"fuchsia.logger.LogSink",
"fuchsia.modular.Clipboard",
"fuchsia.modular.ContextWriter",
"fuchsia.modular.DeviceMap",
"fuchsia.modular.ModuleContext",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.testing.runner.TestRunner",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.policy.Presenter",
"fuchsia.ui.scenic.Scenic"
]
}
}
{
"program": {
"data": "data/stocks"
},
"sandbox": {
"services": [
"fuchsia.cobalt.LoggerFactory",
"fuchsia.fonts.Provider",
"fuchsia.logger.LogSink",
"fuchsia.modular.Clipboard",
"fuchsia.modular.ContextWriter",
"fuchsia.modular.DeviceMap",
"fuchsia.modular.ModuleContext",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.testing.runner.TestRunner",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.policy.Presenter",
"fuchsia.ui.scenic.Scenic"
]
}
}
{
"program": {
"data": "data/flutter_gallery"
},
"sandbox": {
"services": [
"fuchsia.cobalt.LoggerFactory",
"fuchsia.fonts.Provider",
"fuchsia.logger.LogSink",
"fuchsia.modular.Clipboard",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.policy.Presenter",
"fuchsia.ui.scenic.Scenic"
]
}
}
{
"program": {
"data": "data/flutter_gallery"
},
"sandbox": {
"services": [
"fuchsia.cobalt.LoggerFactory",
"fuchsia.fonts.Provider",
"fuchsia.logger.LogSink",
"fuchsia.modular.Clipboard",
"fuchsia.modular.ContextWriter",
"fuchsia.modular.DeviceMap",
"fuchsia.modular.ModuleContext",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.testing.runner.TestRunner",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.policy.Presenter",
"fuchsia.ui.scenic.Scenic"
]
}
}
{
"program": {
"data": "data/integration_ui"
},
"sandbox": {
"services": [
"fuchsia.cobalt.LoggerFactory",
"fuchsia.fonts.Provider",
"fuchsia.logger.LogSink",
"fuchsia.modular.Clipboard",
"fuchsia.modular.ContextWriter",
"fuchsia.modular.DeviceMap",
"fuchsia.modular.ModuleContext",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.testing.runner.TestRunner",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.policy.Presenter",
"fuchsia.ui.scenic.Scenic"
]
}
}
{
"program": {
"data": "data/hello_world"
},
"sandbox": {
"services": [
"fuchsia.cobalt.LoggerFactory",
"fuchsia.fonts.Provider",
"fuchsia.logger.LogSink",
"fuchsia.modular.Clipboard",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.policy.Presenter",
"fuchsia.ui.scenic.Scenic"
]
}
}
...@@ -14,7 +14,6 @@ import 'build_aar.dart'; ...@@ -14,7 +14,6 @@ import 'build_aar.dart';
import 'build_apk.dart'; import 'build_apk.dart';
import 'build_appbundle.dart'; import 'build_appbundle.dart';
import 'build_bundle.dart'; import 'build_bundle.dart';
import 'build_fuchsia.dart';
import 'build_ios.dart'; import 'build_ios.dart';
import 'build_ios_framework.dart'; import 'build_ios_framework.dart';
import 'build_macos_framework.dart'; import 'build_macos_framework.dart';
...@@ -43,7 +42,6 @@ class BuildCommand extends FlutterCommand { ...@@ -43,7 +42,6 @@ class BuildCommand extends FlutterCommand {
verboseHelp: verboseHelp verboseHelp: verboseHelp
)); ));
_addSubcommand(BuildWindowsCommand(verboseHelp: verboseHelp)); _addSubcommand(BuildWindowsCommand(verboseHelp: verboseHelp));
_addSubcommand(BuildFuchsiaCommand(verboseHelp: verboseHelp));
} }
void _addSubcommand(BuildSubCommand command) { void _addSubcommand(BuildSubCommand command) {
...@@ -83,8 +81,11 @@ abstract class BuildSubCommand extends FlutterCommand { ...@@ -83,8 +81,11 @@ abstract class BuildSubCommand extends FlutterCommand {
@protected @protected
void displayNullSafetyMode(BuildInfo buildInfo) { void displayNullSafetyMode(BuildInfo buildInfo) {
globals.printStatus(''); globals.printStatus('');
if (buildInfo.nullSafetyMode == NullSafetyMode.sound) { if (buildInfo.nullSafetyMode == NullSafetyMode.sound) {
globals.printStatus('💪 Building with sound null safety 💪', emphasis: true); globals.printStatus(
'💪 Building with sound null safety 💪',
emphasis: true,
);
} else { } else {
globals.printStatus( globals.printStatus(
'Building without sound null safety', 'Building without sound null safety',
......
// 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 '../base/common.dart';
import '../build_info.dart';
import '../cache.dart';
import '../features.dart';
import '../fuchsia/fuchsia_build.dart';
import '../fuchsia/fuchsia_pm.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../runner/flutter_command.dart' show FlutterCommandResult;
import 'build.dart';
/// A command to build a Fuchsia target.
class BuildFuchsiaCommand extends BuildSubCommand {
BuildFuchsiaCommand({
required bool verboseHelp,
}) : super(verboseHelp: verboseHelp) {
addTreeShakeIconsFlag();
usesTargetOption();
usesDartDefineOption();
addBuildModeFlags(verboseHelp: verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp);
addEnableExperimentation(hide: !verboseHelp);
argParser.addOption(
'runner-source',
help: 'The package source to use for the flutter_runner. '
'"${FuchsiaPackageServer.deviceHost}" implies using a runner already on the device. '
'"${FuchsiaPackageServer.toolHost}" implies using a runner distributed with Flutter.',
allowed: <String>[
FuchsiaPackageServer.deviceHost,
FuchsiaPackageServer.toolHost,
],
defaultsTo: FuchsiaPackageServer.toolHost,
);
argParser.addOption('target-platform',
defaultsTo: 'fuchsia-x64',
allowed: <String>['fuchsia-arm64', 'fuchsia-x64'],
help: 'The target platform for which the app is compiled.',
);
}
@override
final String name = 'fuchsia';
@override
bool hidden = true;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => <DevelopmentArtifact>{
DevelopmentArtifact.fuchsia,
};
@override
String get description => 'Build the Fuchsia target (Experimental).';
@override
bool get supported => globals.platform.isLinux || globals.platform.isMacOS;
@override
Future<FlutterCommandResult> runCommand() async {
if (!featureFlags.isFuchsiaEnabled) {
throwToolExit(
'"build fuchsia" is currently disabled. See "flutter config" for more '
'information.'
);
}
final BuildInfo buildInfo = await getBuildInfo();
final FlutterProject flutterProject = FlutterProject.current();
if (!supported) {
throwToolExit('"build fuchsia" is only supported on Linux and MacOS hosts.');
}
if (!flutterProject.fuchsia.existsSync()) {
throwToolExit('No Fuchsia project is configured.');
}
displayNullSafetyMode(buildInfo);
await buildFuchsia(
fuchsiaProject: flutterProject.fuchsia,
target: targetFile,
targetPlatform: getTargetPlatformForName(stringArgDeprecated('target-platform')!),
buildInfo: buildInfo,
runnerPackageSource: stringArgDeprecated('runner-source')!,
);
return FlutterCommandResult.success();
}
}
// 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 '../artifacts.dart';
import '../asset.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../bundle_builder.dart';
import '../convert.dart';
import '../devfs.dart';
import '../globals.dart' as globals;
import '../project.dart';
import 'fuchsia_pm.dart';
Future<void> _timedBuildStep(String name, Future<void> Function() action) async {
final Stopwatch sw = Stopwatch()..start();
await action();
globals.printTrace('$name: ${sw.elapsedMilliseconds} ms.');
globals.flutterUsage.sendTiming('build', name, Duration(milliseconds: sw.elapsedMilliseconds));
}
Future<void> _validateCmxFile(FuchsiaProject fuchsiaProject) async {
final String appName = fuchsiaProject.project.manifest.appName;
final String cmxPath = globals.fs.path.join(fuchsiaProject.meta.path, '$appName.cmx');
final File cmxFile = globals.fs.file(cmxPath);
if (!await cmxFile.exists()) {
throwToolExit('The Fuchsia build requires a .cmx file at $cmxPath for the app: $appName.');
}
}
// Building a Fuchsia package has a few steps:
// 1. Do the custom kernel compile using the kernel compiler from the Fuchsia
// SDK. This produces .dilp files (among others) and a manifest file.
// 2. Create a manifest file for assets.
// 3. Using these manifests, use the Fuchsia SDK 'pm' tool to create the
// Fuchsia package.
Future<void> buildFuchsia({
required FuchsiaProject fuchsiaProject,
required TargetPlatform targetPlatform,
String? target, // E.g., lib/main.dart
BuildInfo buildInfo = BuildInfo.debug,
String runnerPackageSource = FuchsiaPackageServer.toolHost,
}) async {
final String targetPath = target ??= 'lib/main.dart';
await _validateCmxFile(fuchsiaProject);
final Directory outDir = globals.fs.directory(getFuchsiaBuildDirectory());
if (!outDir.existsSync()) {
outDir.createSync(recursive: true);
}
await _timedBuildStep('fuchsia-kernel-compile',
() => globals.fuchsiaSdk!.fuchsiaKernelCompiler.build(
fuchsiaProject: fuchsiaProject, target: targetPath, buildInfo: buildInfo));
if (buildInfo.usesAot) {
await _timedBuildStep('fuchsia-gen-snapshot',
() => _genSnapshot(fuchsiaProject, targetPath, buildInfo, targetPlatform));
}
await _timedBuildStep('fuchsia-build-assets',
() => _buildAssets(fuchsiaProject, targetPath, buildInfo));
await _timedBuildStep('fuchsia-build-package',
() => _buildPackage(fuchsiaProject, targetPath, buildInfo, runnerPackageSource));
}
Future<void> _genSnapshot(
FuchsiaProject fuchsiaProject,
String target, // lib/main.dart
BuildInfo buildInfo,
TargetPlatform targetPlatform,
) async {
final String outDir = getFuchsiaBuildDirectory();
final String appName = fuchsiaProject.project.manifest.appName;
final String dilPath = globals.fs.path.join(outDir, '$appName.dil');
final String elf = globals.fs.path.join(outDir, 'elf.aotsnapshot');
final String genSnapshot = globals.artifacts!.getArtifactPath(
Artifact.genSnapshot,
platform: targetPlatform,
mode: buildInfo.mode,
);
final List<String> command = <String>[
genSnapshot,
'--deterministic',
'--snapshot_kind=app-aot-elf',
'--elf=$elf',
if (buildInfo.isDebug) '--enable-asserts',
dilPath,
];
int result;
final Status status = globals.logger.startProgress(
'Compiling Fuchsia application to native code...',
);
try {
result = await globals.processUtils.stream(command, trace: true);
} finally {
status.cancel();
}
if (result != 0) {
throwToolExit('Build process failed');
}
}
Future<void> _buildAssets(
FuchsiaProject fuchsiaProject,
String target, // lib/main.dart
BuildInfo buildInfo,
) async {
final String assetDir = getAssetBuildDirectory();
final AssetBundle? assets = await buildAssets(
manifestPath: fuchsiaProject.project.pubspecFile.path,
packagesPath: fuchsiaProject.project.packagesFile.path,
assetDirPath: assetDir,
);
if (assets == null) {
throwToolExit('Unable to find assets.', exitCode: 1);
}
final Map<String, DevFSContent> assetEntries = Map<String, DevFSContent>.of(
assets.entries,
);
await writeBundle(globals.fs.directory(assetDir), assetEntries, assets.entryKinds);
final String appName = fuchsiaProject.project.manifest.appName;
final String outDir = getFuchsiaBuildDirectory();
final String assetManifest = globals.fs.path.join(outDir, '${appName}_pkgassets');
final File destFile = globals.fs.file(assetManifest);
await destFile.create(recursive: true);
final IOSink outFile = destFile.openWrite();
for (final String path in assets.entries.keys) {
outFile.write('data/$appName/$path=$assetDir/$path\n');
}
await outFile.flush();
await outFile.close();
}
void _rewriteCmx(BuildMode mode, String runnerPackageSource, File src, File dst) {
final Map<String, Object?> cmx = castStringKeyedMap(json.decode(src.readAsStringSync())) ?? <String, Object?>{};
// If the app author has already specified the runner in the cmx file, then
// do not override it with something else.
if (cmx.containsKey('runner')) {
dst.writeAsStringSync(json.encode(cmx));
return;
}
String runner;
switch (mode) {
case BuildMode.debug:
runner = 'flutter_jit_runner';
break;
case BuildMode.profile:
runner = 'flutter_aot_runner';
break;
case BuildMode.jitRelease:
runner = 'flutter_jit_product_runner';
break;
case BuildMode.release:
runner = 'flutter_aot_product_runner';
break;
default:
throwToolExit('Fuchsia does not support build mode "$mode"');
}
cmx['runner'] = 'fuchsia-pkg://$runnerPackageSource/$runner#meta/$runner.cmx';
dst.writeAsStringSync(json.encode(cmx));
}
// TODO(zanderso): Allow supplying a signing key.
Future<void> _buildPackage(
FuchsiaProject fuchsiaProject,
String target, // lib/main.dart
BuildInfo buildInfo,
String runnerPackageSource,
) async {
final String outDir = getFuchsiaBuildDirectory();
final String pkgDir = globals.fs.path.join(outDir, 'pkg');
final String appName = fuchsiaProject.project.manifest.appName;
final String pkgassets = globals.fs.path.join(outDir, '${appName}_pkgassets');
final String packageManifest = globals.fs.path.join(pkgDir, 'package_manifest');
final Directory pkg = globals.fs.directory(pkgDir);
if (!pkg.existsSync()) {
pkg.createSync(recursive: true);
}
final File srcCmx =
globals.fs.file(globals.fs.path.join(fuchsiaProject.meta.path, '$appName.cmx'));
final File dstCmx = globals.fs.file(globals.fs.path.join(outDir, '$appName.cmx'));
_rewriteCmx(buildInfo.mode, runnerPackageSource, srcCmx, dstCmx);
final File manifestFile = globals.fs.file(packageManifest);
if (buildInfo.usesAot) {
final String elf = globals.fs.path.join(outDir, 'elf.aotsnapshot');
manifestFile.writeAsStringSync(
'data/$appName/app_aot_snapshot.so=$elf\n');
} else {
final String dilpmanifest = globals.fs.path.join(outDir, '$appName.dilpmanifest');
manifestFile.writeAsStringSync(globals.fs.file(dilpmanifest).readAsStringSync());
}
manifestFile.writeAsStringSync(globals.fs.file(pkgassets).readAsStringSync(),
mode: FileMode.append);
manifestFile.writeAsStringSync('meta/$appName.cmx=${dstCmx.path}\n',
mode: FileMode.append);
manifestFile.writeAsStringSync('meta/package=$pkgDir/meta/package\n',
mode: FileMode.append);
final FuchsiaPM? fuchsiaPM = globals.fuchsiaSdk?.fuchsiaPM;
if (fuchsiaPM == null) {
return;
}
if (!await fuchsiaPM.init(pkgDir, appName)) {
return;
}
if (!await fuchsiaPM.build(pkgDir, packageManifest)) {
return;
}
if (!await fuchsiaPM.archive(pkgDir, packageManifest)) {
return;
}
}
...@@ -23,16 +23,15 @@ import '../device.dart'; ...@@ -23,16 +23,15 @@ import '../device.dart';
import '../device_port_forwarder.dart'; import '../device_port_forwarder.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../project.dart'; import '../project.dart';
import '../runner/flutter_command.dart';
import '../vmservice.dart'; import '../vmservice.dart';
import 'application_package.dart'; import 'application_package.dart';
import 'fuchsia_build.dart'; import 'fuchsia_ffx.dart';
import 'fuchsia_pm.dart'; import 'fuchsia_pm.dart';
import 'fuchsia_sdk.dart'; import 'fuchsia_sdk.dart';
import 'fuchsia_workflow.dart'; import 'fuchsia_workflow.dart';
import 'pkgctl.dart'; import 'pkgctl.dart';
import 'session_control.dart';
import 'tiles_ctl.dart';
/// The [FuchsiaDeviceTools] instance. /// The [FuchsiaDeviceTools] instance.
FuchsiaDeviceTools get fuchsiaDeviceTools => context.get<FuchsiaDeviceTools>()!; FuchsiaDeviceTools get fuchsiaDeviceTools => context.get<FuchsiaDeviceTools>()!;
...@@ -40,8 +39,7 @@ FuchsiaDeviceTools get fuchsiaDeviceTools => context.get<FuchsiaDeviceTools>()!; ...@@ -40,8 +39,7 @@ FuchsiaDeviceTools get fuchsiaDeviceTools => context.get<FuchsiaDeviceTools>()!;
/// Fuchsia device-side tools. /// Fuchsia device-side tools.
class FuchsiaDeviceTools { class FuchsiaDeviceTools {
late final FuchsiaPkgctl pkgctl = FuchsiaPkgctl(); late final FuchsiaPkgctl pkgctl = FuchsiaPkgctl();
late final FuchsiaTilesCtl tilesCtl = FuchsiaTilesCtl(); late final FuchsiaFfx ffx = FuchsiaFfx();
late final FuchsiaSessionControl sessionControl = FuchsiaSessionControl();
} }
final String _ipv4Loopback = InternetAddress.loopbackIPv4.address; final String _ipv4Loopback = InternetAddress.loopbackIPv4.address;
...@@ -100,7 +98,7 @@ class _FuchsiaLogReader extends DeviceLogReader { ...@@ -100,7 +98,7 @@ class _FuchsiaLogReader extends DeviceLogReader {
final ApplicationPackage? app = _app; final ApplicationPackage? app = _app;
final RegExp matchRegExp = app == null final RegExp matchRegExp = app == null
? _flutterLogOutput ? _flutterLogOutput
: RegExp('INFO: ${app.name}(\\.cmx)?\\(flutter\\): '); : RegExp('INFO: ${app.name}(\\.cm)?\\(flutter\\): ');
return Stream<String>.eventTransformed( return Stream<String>.eventTransformed(
lines, lines,
(EventSink<String> output) => _FuchsiaLogSink(output, matchRegExp, startTime), (EventSink<String> output) => _FuchsiaLogSink(output, matchRegExp, startTime),
...@@ -212,7 +210,7 @@ class FuchsiaDevices extends PollingDeviceDiscovery { ...@@ -212,7 +210,7 @@ class FuchsiaDevices extends PollingDeviceDiscovery {
} }
final String name = words[1]; final String name = words[1];
// TODO(omerlevran): Add support for resolve on the FuchsiaSdk Object. // TODO(omerlevran): Add support for resolve on the FuchsiaSdk Object.
final String? resolvedHost = await _fuchsiaSdk.fuchsiaFfx.resolve(name); final String? resolvedHost = await _fuchsiaSdk.fuchsiaFfx.resolve(name);
if (resolvedHost == null) { if (resolvedHost == null) {
_logger.printError('Failed to resolve host for Fuchsia device `$name`'); _logger.printError('Failed to resolve host for Fuchsia device `$name`');
...@@ -225,13 +223,13 @@ class FuchsiaDevices extends PollingDeviceDiscovery { ...@@ -225,13 +223,13 @@ class FuchsiaDevices extends PollingDeviceDiscovery {
List<String> get wellKnownIds => const <String>[]; List<String> get wellKnownIds => const <String>[];
} }
class FuchsiaDevice extends Device { class FuchsiaDevice extends Device {
FuchsiaDevice(super.id, {required this.name}) : super( FuchsiaDevice(super.id, {required this.name})
platformType: PlatformType.fuchsia, : super(
category: null, platformType: PlatformType.fuchsia,
ephemeral: true, category: null,
); ephemeral: true,
);
@override @override
bool get supportsHotReload => true; bool get supportsHotReload => true;
...@@ -258,14 +256,11 @@ class FuchsiaDevice extends Device { ...@@ -258,14 +256,11 @@ class FuchsiaDevice extends Device {
/// Determine if the Fuchsia device is running a session based build. /// Determine if the Fuchsia device is running a session based build.
/// ///
/// If the device is running a session based build, `session_control` should be /// If the device is running a session based build, `ffx session` should be
/// used to launch apps, otherwise `tiles_ctl` should be used. /// used to launch apps. Fuchsia flutter apps cannot currently be launched
/// without a session.
Future<bool> _initIsSession() async { Future<bool> _initIsSession() async {
final RunResult result = await shell('which session_control'); return await globals.fuchsiaSdk?.fuchsiaFfx.sessionShow() != null;
if (result.exitCode != 0) {
return false;
}
return true;
} }
@override @override
...@@ -293,7 +288,8 @@ class FuchsiaDevice extends Device { ...@@ -293,7 +288,8 @@ class FuchsiaDevice extends Device {
bool isSupported() => true; bool isSupported() => true;
@override @override
bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease; bool supportsRuntimeMode(BuildMode buildMode) =>
buildMode != BuildMode.jitRelease;
@override @override
Future<LaunchResult> startApp( Future<LaunchResult> startApp(
...@@ -313,10 +309,11 @@ class FuchsiaDevice extends Device { ...@@ -313,10 +309,11 @@ class FuchsiaDevice extends Device {
} }
if (!prebuiltApplication) { if (!prebuiltApplication) {
await buildFuchsia(fuchsiaProject: FlutterProject.current().fuchsia, throwToolExit(
targetPlatform: await targetPlatform, 'This tool does not currently build apps for fuchsia.\n'
target: mainPath, 'Build the app using a supported Fuchsia workflow.\n'
buildInfo: debuggingOptions.buildInfo); 'Then use the --${FlutterOptions.kUseApplicationBinary} flag.'
);
} }
// Stop the app if it's currently running. // Stop the app if it's currently running.
await stopApp(package); await stopApp(package);
...@@ -330,8 +327,8 @@ class FuchsiaDevice extends Device { ...@@ -330,8 +327,8 @@ class FuchsiaDevice extends Device {
// Try Start with a fresh package repo in case one was left over from a // Try Start with a fresh package repo in case one was left over from a
// previous run. // previous run.
final Directory packageRepo = final Directory packageRepo = globals.fs.directory(
globals.fs.directory(globals.fs.path.join(getFuchsiaBuildDirectory(), '.pkg-repo')); globals.fs.path.join(getFuchsiaBuildDirectory(), '.pkg-repo'));
try { try {
if (packageRepo.existsSync()) { if (packageRepo.existsSync()) {
packageRepo.deleteSync(recursive: true); packageRepo.deleteSync(recursive: true);
...@@ -339,7 +336,7 @@ class FuchsiaDevice extends Device { ...@@ -339,7 +336,7 @@ class FuchsiaDevice extends Device {
packageRepo.createSync(recursive: true); packageRepo.createSync(recursive: true);
} on Exception catch (e) { } on Exception catch (e) {
globals.printError('Failed to create Fuchsia package repo directory ' globals.printError('Failed to create Fuchsia package repo directory '
'at ${packageRepo.path}: $e'); 'at ${packageRepo.path}: $e');
return LaunchResult.failed(); return LaunchResult.failed();
} }
...@@ -419,29 +416,20 @@ class FuchsiaDevice extends Device { ...@@ -419,29 +416,20 @@ class FuchsiaDevice extends Device {
return LaunchResult.failed(); return LaunchResult.failed();
} }
fuchsiaUrl = fuchsiaUrl = 'fuchsia-pkg://$packageServerName/$appName#meta/$appName.cm';
'fuchsia-pkg://$packageServerName/$appName#meta/$appName.cmx';
if (await isSession) { if (await isSession) {
// Instruct session_control to start the app // Instruct ffx session to start the app
if (!await fuchsiaDeviceTools.sessionControl.add(this, fuchsiaUrl)) { final bool addedApp =
globals.printError('Failed to add the app to session_control'); await globals.fuchsiaSdk?.fuchsiaFfx.sessionAdd(fuchsiaUrl) ?? false;
if (!addedApp) {
globals.printError('Failed to add the app via `ffx session add`');
return LaunchResult.failed(); return LaunchResult.failed();
} }
} else { } else {
// Ensure tiles_ctl is started, and start the app. globals.printError(
if (!await FuchsiaTilesCtl.ensureStarted(this)) { 'Fuchsia flutter apps can only be launched within a session');
globals.printError( return LaunchResult.failed();
'Failed to ensure that tiles is started on the device');
return LaunchResult.failed();
}
// Instruct tiles_ctl to start the app.
if (!await fuchsiaDeviceTools.tilesCtl
.add(this, fuchsiaUrl, <String>[])) {
globals.printError('Failed to add the app to tiles');
return LaunchResult.failed();
}
} }
} finally { } finally {
// Try to un-teach the package controller about the package server if // Try to un-teach the package controller about the package server if
...@@ -491,13 +479,8 @@ class FuchsiaDevice extends Device { ...@@ -491,13 +479,8 @@ class FuchsiaDevice extends Device {
// using the session framework afaik. So this is a no-op. // using the session framework afaik. So this is a no-op.
return true; return true;
} }
final int appKey = await FuchsiaTilesCtl.findAppKey(this, app.id); // Fuchsia flutter apps currently require a session, but if that changes,
if (appKey != -1) { // add the relevant "stopApp" code here.
if (!await fuchsiaDeviceTools.tilesCtl.remove(this, appKey)) {
globals.printError('tiles_ctl remove on ${app.id} failed.');
return false;
}
}
return true; return true;
} }
...@@ -537,12 +520,15 @@ class FuchsiaDevice extends Device { ...@@ -537,12 +520,15 @@ class FuchsiaDevice extends Device {
if (outputFile.basename.split('.').last != 'ppm') { if (outputFile.basename.split('.').last != 'ppm') {
throw Exception('${outputFile.path} must be a .ppm file'); throw Exception('${outputFile.path} must be a .ppm file');
} }
final RunResult screencapResult = await shell('screencap > /tmp/screenshot.ppm'); final RunResult screencapResult =
await shell('screencap > /tmp/screenshot.ppm');
if (screencapResult.exitCode != 0) { if (screencapResult.exitCode != 0) {
throw Exception('Could not take a screenshot on device $name:\n$screencapResult'); throw Exception(
'Could not take a screenshot on device $name:\n$screencapResult');
} }
try { try {
final RunResult scpResult = await scp('/tmp/screenshot.ppm', outputFile.path); final RunResult scpResult =
await scp('/tmp/screenshot.ppm', outputFile.path);
if (scpResult.exitCode != 0) { if (scpResult.exitCode != 0) {
throw Exception('Failed to copy screenshot from device:\n$scpResult'); throw Exception('Failed to copy screenshot from device:\n$scpResult');
} }
...@@ -551,13 +537,11 @@ class FuchsiaDevice extends Device { ...@@ -551,13 +537,11 @@ class FuchsiaDevice extends Device {
final RunResult deleteResult = await shell('rm /tmp/screenshot.ppm'); final RunResult deleteResult = await shell('rm /tmp/screenshot.ppm');
if (deleteResult.exitCode != 0) { if (deleteResult.exitCode != 0) {
globals.printError( globals.printError(
'Failed to delete screenshot.ppm from the device:\n$deleteResult' 'Failed to delete screenshot.ppm from the device:\n$deleteResult');
);
} }
} on Exception catch (e) { } on Exception catch (e) {
globals.printError( globals
'Failed to delete screenshot.ppm from the device: $e' .printError('Failed to delete screenshot.ppm from the device: $e');
);
} }
} }
} }
...@@ -570,7 +554,7 @@ class FuchsiaDevice extends Device { ...@@ -570,7 +554,7 @@ class FuchsiaDevice extends Device {
const String defaultName = 'Fuchsia'; const String defaultName = 'Fuchsia';
if (!globals.fuchsiaArtifacts!.hasSshConfig) { if (!globals.fuchsiaArtifacts!.hasSshConfig) {
globals.printTrace('Could not determine Fuchsia sdk name or version ' globals.printTrace('Could not determine Fuchsia sdk name or version '
'because Fuchsia ssh configuration is missing.'); 'because Fuchsia ssh configuration is missing.');
return defaultName; return defaultName;
} }
const String versionPath = '/pkgfs/packages/build-info/0/data/version'; const String versionPath = '/pkgfs/packages/build-info/0/data/version';
...@@ -595,6 +579,7 @@ class FuchsiaDevice extends Device { ...@@ -595,6 +579,7 @@ class FuchsiaDevice extends Device {
assert(!includePastLogs, 'Past log reading not supported on Fuchsia.'); assert(!includePastLogs, 'Past log reading not supported on Fuchsia.');
return _logReader ??= _FuchsiaLogReader(this, globals.systemClock, app); return _logReader ??= _FuchsiaLogReader(this, globals.systemClock, app);
} }
_FuchsiaLogReader? _logReader; _FuchsiaLogReader? _logReader;
@override @override
...@@ -620,6 +605,7 @@ class FuchsiaDevice extends Device { ...@@ -620,6 +605,7 @@ class FuchsiaDevice extends Device {
void fail() { void fail() {
throwToolExit('Failed to get local address, aborting.\n$result'); throwToolExit('Failed to get local address, aborting.\n$result');
} }
if (result.exitCode != 0) { if (result.exitCode != 0) {
fail(); fail();
} }
...@@ -639,7 +625,8 @@ class FuchsiaDevice extends Device { ...@@ -639,7 +625,8 @@ class FuchsiaDevice extends Device {
const String findCommand = 'find /hub -name vmservice-port'; const String findCommand = 'find /hub -name vmservice-port';
final RunResult findResult = await shell(findCommand); final RunResult findResult = await shell(findCommand);
if (findResult.exitCode != 0) { if (findResult.exitCode != 0) {
throwToolExit("'$findCommand' on device $name failed. stderr: '${findResult.stderr}'"); throwToolExit(
"'$findCommand' on device $name failed. stderr: '${findResult.stderr}'");
} }
final String findOutput = findResult.stdout; final String findOutput = findResult.stdout;
if (findOutput.trim() == '') { if (findOutput.trim() == '') {
...@@ -675,7 +662,7 @@ class FuchsiaDevice extends Device { ...@@ -675,7 +662,7 @@ class FuchsiaDevice extends Device {
final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig; final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
if (sshConfig == null) { if (sshConfig == null) {
throwToolExit('Cannot interact with device. No ssh config.\n' throwToolExit('Cannot interact with device. No ssh config.\n'
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.'); 'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
} }
return globals.processUtils.run(<String>[ return globals.processUtils.run(<String>[
'ssh', 'ssh',
...@@ -691,7 +678,7 @@ class FuchsiaDevice extends Device { ...@@ -691,7 +678,7 @@ class FuchsiaDevice extends Device {
final File? sshConfig = globals.fuchsiaArtifacts!.sshConfig; final File? sshConfig = globals.fuchsiaArtifacts!.sshConfig;
if (sshConfig == null) { if (sshConfig == null) {
throwToolExit('Cannot interact with device. No ssh config.\n' throwToolExit('Cannot interact with device. No ssh config.\n'
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.'); 'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
} }
return globals.processUtils.run(<String>[ return globals.processUtils.run(<String>[
'scp', 'scp',
...@@ -714,15 +701,18 @@ class FuchsiaDevice extends Device { ...@@ -714,15 +701,18 @@ class FuchsiaDevice extends Device {
// netstat shows that the local port is actually being used on the IPv6 // netstat shows that the local port is actually being used on the IPv6
// loopback (::1). // loopback (::1).
final Uri uri = Uri.parse('http://[$_ipv6Loopback]:$port'); final Uri uri = Uri.parse('http://[$_ipv6Loopback]:$port');
final FlutterVmService vmService = await connectToVmService(uri, logger: globals.logger); final FlutterVmService vmService =
final List<FlutterView> flutterViews = await vmService.getFlutterViews(); await connectToVmService(uri, logger: globals.logger);
final List<FlutterView> flutterViews =
await vmService.getFlutterViews();
for (final FlutterView flutterView in flutterViews) { for (final FlutterView flutterView in flutterViews) {
final vm_service.IsolateRef? uiIsolate = flutterView.uiIsolate; final vm_service.IsolateRef? uiIsolate = flutterView.uiIsolate;
if (uiIsolate == null) { if (uiIsolate == null) {
continue; continue;
} }
final int? port = vmService.httpAddress?.port; final int? port = vmService.httpAddress?.port;
if (port != null && (uiIsolate.name?.contains(isolateName) ?? false)) { if (port != null &&
(uiIsolate.name?.contains(isolateName) ?? false)) {
return port; return port;
} }
} }
...@@ -733,7 +723,8 @@ class FuchsiaDevice extends Device { ...@@ -733,7 +723,8 @@ class FuchsiaDevice extends Device {
throwToolExit('No ports found running $isolateName'); throwToolExit('No ports found running $isolateName');
} }
FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(String isolateName) { FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(
String isolateName) {
return FuchsiaIsolateDiscoveryProtocol(this, isolateName); return FuchsiaIsolateDiscoveryProtocol(this, isolateName);
} }
...@@ -776,7 +767,7 @@ class FuchsiaIsolateDiscoveryProtocol { ...@@ -776,7 +767,7 @@ class FuchsiaIsolateDiscoveryProtocol {
_status ??= globals.logger.startProgress( _status ??= globals.logger.startProgress(
'Waiting for a connection from $_isolateName on ${_device.name}...', 'Waiting for a connection from $_isolateName on ${_device.name}...',
); );
unawaited(_findIsolate()); // Completes the _foundUri Future. unawaited(_findIsolate()); // Completes the _foundUri Future.
return _foundUri.future.then((Uri uri) { return _foundUri.future.then((Uri uri) {
_uri = uri; _uri = uri;
return uri; return uri;
...@@ -813,7 +804,8 @@ class FuchsiaIsolateDiscoveryProtocol { ...@@ -813,7 +804,8 @@ class FuchsiaIsolateDiscoveryProtocol {
continue; continue;
} }
} }
final List<FlutterView> flutterViews = await service?.getFlutterViews() ?? <FlutterView>[]; final List<FlutterView> flutterViews =
await service?.getFlutterViews() ?? <FlutterView>[];
for (final FlutterView flutterView in flutterViews) { for (final FlutterView flutterView in flutterViews) {
final vm_service.IsolateRef? uiIsolate = flutterView.uiIsolate; final vm_service.IsolateRef? uiIsolate = flutterView.uiIsolate;
if (uiIsolate == null) { if (uiIsolate == null) {
...@@ -848,7 +840,8 @@ class _FuchsiaPortForwarder extends DevicePortForwarder { ...@@ -848,7 +840,8 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
Future<int> forward(int devicePort, {int? hostPort}) async { Future<int> forward(int devicePort, {int? hostPort}) async {
hostPort ??= await globals.os.findFreePort(); hostPort ??= await globals.os.findFreePort();
if (hostPort == 0) { if (hostPort == 0) {
throwToolExit('Failed to forward port $devicePort. No free host-side ports'); throwToolExit(
'Failed to forward port $devicePort. No free host-side ports');
} }
final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig; final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
if (sshConfig == null) { if (sshConfig == null) {
...@@ -911,7 +904,7 @@ class _FuchsiaPortForwarder extends DevicePortForwarder { ...@@ -911,7 +904,7 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
throwToolExit( throwToolExit(
'Unforward command failed:\n' 'Unforward command failed:\n'
'stdout: ${result.stdout}\n' 'stdout: ${result.stdout}\n'
'stderr: ${result.stderr}' 'stderr: ${result.stderr}',
); );
} }
} }
...@@ -919,7 +912,7 @@ class _FuchsiaPortForwarder extends DevicePortForwarder { ...@@ -919,7 +912,7 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
@override @override
Future<void> dispose() async { Future<void> dispose() async {
final List<ForwardedPort> forwardedPortsCopy = final List<ForwardedPort> forwardedPortsCopy =
List<ForwardedPort>.of(forwardedPorts); List<ForwardedPort>.of(forwardedPorts);
for (final ForwardedPort port in forwardedPortsCopy) { for (final ForwardedPort port in forwardedPortsCopy) {
await unforward(port); await unforward(port);
} }
......
...@@ -8,6 +8,7 @@ import 'package:process/process.dart'; ...@@ -8,6 +8,7 @@ import 'package:process/process.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../globals.dart' as globals;
import 'fuchsia_sdk.dart'; import 'fuchsia_sdk.dart';
// Usage: ffx [-c <config>] [-e <env>] [-t <target>] [-T <timeout>] [-v] [<command>] [<args>] // Usage: ffx [-c <config>] [-e <env>] [-t <target>] [-T <timeout>] [-v] [<command>] [<args>]
...@@ -26,17 +27,21 @@ import 'fuchsia_sdk.dart'; ...@@ -26,17 +27,21 @@ import 'fuchsia_sdk.dart';
// config View and switch default and user configurations // config View and switch default and user configurations
// daemon Interact with/control the ffx daemon // daemon Interact with/control the ffx daemon
// target Interact with a target device or emulator // target Interact with a target device or emulator
// session Control the current session. See
// https://fuchsia.dev/fuchsia-src/concepts/session/introduction
// for details.
/// A simple wrapper for the Fuchsia SDK's 'ffx' tool. /// A simple wrapper for the Fuchsia SDK's 'ffx' tool.
class FuchsiaFfx { class FuchsiaFfx {
FuchsiaFfx({ FuchsiaFfx({
required FuchsiaArtifacts? fuchsiaArtifacts, FuchsiaArtifacts? fuchsiaArtifacts,
required Logger logger, Logger? logger,
required ProcessManager processManager, ProcessManager? processManager,
}) : _fuchsiaArtifacts = fuchsiaArtifacts, }) : _fuchsiaArtifacts = fuchsiaArtifacts ?? globals.fuchsiaArtifacts,
_logger = logger, _logger = logger ?? globals.logger,
_processUtils = _processUtils = ProcessUtils(
ProcessUtils(logger: logger, processManager: processManager); logger: logger ?? globals.logger,
processManager: processManager ?? globals.processManager);
final FuchsiaArtifacts? _fuchsiaArtifacts; final FuchsiaArtifacts? _fuchsiaArtifacts;
final Logger _logger; final Logger _logger;
...@@ -53,8 +58,7 @@ class FuchsiaFfx { ...@@ -53,8 +58,7 @@ class FuchsiaFfx {
} }
final List<String> command = <String>[ final List<String> command = <String>[
ffx.path, ffx.path,
if (timeout != null) if (timeout != null) ...<String>['-T', '${timeout.inSeconds}'],
...<String>['-T', '${timeout.inSeconds}'],
'target', 'target',
'list', 'list',
// TODO(akbiggs): Revert -f back to --format once we've verified that // TODO(akbiggs): Revert -f back to --format once we've verified that
...@@ -97,4 +101,50 @@ class FuchsiaFfx { ...@@ -97,4 +101,50 @@ class FuchsiaFfx {
} }
return result.stdout.trim(); return result.stdout.trim();
} }
/// Show information about the current session
///
/// Returns `null` if the command failed, which can be interpreted as there is
/// no usable session.
Future<String?> sessionShow() async {
final File? ffx = _fuchsiaArtifacts?.ffx;
if (ffx == null || !ffx.existsSync()) {
throwToolExit('Fuchsia ffx tool not found.');
}
final List<String> command = <String>[
ffx.path,
'session',
'show',
];
final RunResult result = await _processUtils.run(command);
if (result.exitCode != 0) {
_logger.printError('ffx failed: ${result.stderr}');
return null;
}
return result.stdout;
}
/// Add an element to the current session
///
/// [url] should be formatted as a Fuchsia-style package URL, e.g.:
/// fuchsia-pkg://fuchsia.com/flutter_gallery#meta/flutter_gallery.cmx
/// Returns true on success and false on failure.
Future<bool> sessionAdd(String url) async {
final File? ffx = _fuchsiaArtifacts?.ffx;
if (ffx == null || !ffx.existsSync()) {
throwToolExit('Fuchsia ffx tool not found.');
}
final List<String> command = <String>[
ffx.path,
'session',
'add',
url,
];
final RunResult result = await _processUtils.run(command);
if (result.exitCode != 0) {
_logger.printError('ffx failed: ${result.stderr}');
return false;
}
return true;
}
} }
...@@ -41,7 +41,7 @@ class FuchsiaPM { ...@@ -41,7 +41,7 @@ class FuchsiaPM {
/// which describe the contents of the Fuchsia package. It must also contain /// which describe the contents of the Fuchsia package. It must also contain
/// two other entries: /// two other entries:
/// ///
/// meta/$APPNAME.cmx=/path/to/cmx/on/the/host/$APPNAME.cmx /// meta/$APPNAME.cm=/path/to/cm/on/the/host/$APPNAME.cm
/// meta/package=/path/to/package/file/from/init/package /// meta/package=/path/to/package/file/from/init/package
/// ///
/// where $APPNAME is the same [appName] passed to [init], and meta/package /// where $APPNAME is the same [appName] passed to [init], and meta/package
...@@ -232,12 +232,14 @@ class FuchsiaPackageServer { ...@@ -232,12 +232,14 @@ class FuchsiaPackageServer {
if (_process == null) { if (_process == null) {
return false; return false;
} }
return (await globals.fuchsiaSdk?.fuchsiaPM.publish(_repo, package.path)) ?? false; return (await globals.fuchsiaSdk?.fuchsiaPM.publish(_repo, package.path)) ??
false;
} }
@override @override
String toString() { String toString() {
final String p = (_process == null) ? 'stopped' : 'running ${_process?.pid}'; final String p =
(_process == null) ? 'stopped' : 'running ${_process?.pid}';
return 'FuchsiaPackageServer at $_host:$_port ($p)'; return 'FuchsiaPackageServer at $_host:$_port ($p)';
} }
} }
...@@ -31,11 +31,7 @@ class FuchsiaSdk { ...@@ -31,11 +31,7 @@ class FuchsiaSdk {
late final FuchsiaKernelCompiler fuchsiaKernelCompiler = FuchsiaKernelCompiler(); late final FuchsiaKernelCompiler fuchsiaKernelCompiler = FuchsiaKernelCompiler();
/// Interface to the 'ffx' tool. /// Interface to the 'ffx' tool.
late final FuchsiaFfx fuchsiaFfx = FuchsiaFfx( late final FuchsiaFfx fuchsiaFfx = FuchsiaFfx();
fuchsiaArtifacts: globals.fuchsiaArtifacts,
logger: globals.logger,
processManager: globals.processManager,
);
/// Returns any attached devices is a newline-denominated String. /// Returns any attached devices is a newline-denominated String.
/// ///
......
// 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 '../base/process.dart';
import 'fuchsia_device.dart';
// Usage: session_control <command> [<args>]
//
// Various operations to control sessions.
//
// Options:
// --help display usage information
//
// Commands:
// launch Launch a new session.
// restart Restart the current session.
// add Add an element to the current session.
//
// Usage: session_control launch <session_url>
//
// Launch a new session.
//
// Options:
// --help display usage information
//
// Usage: session_control restart
//
// Restart the current session.
//
// Options:
// --help display usage information
//
//
// Usage: session_control add <element_url>
//
// Add an element to the current session.
//
// Options:
// --help display usage information
/// A simple wrapper around the 'session_control' tool running on the Fuchsia device.
class FuchsiaSessionControl {
/// Instructs session_control on the device to add the app at [url] as an element.
///
/// [url] should be formatted as a Fuchsia-style package URL, e.g.:
/// fuchsia-pkg://fuchsia.com/flutter_gallery#meta/flutter_gallery.cmx
/// Returns true on success and false on failure.
Future<bool> add(FuchsiaDevice device, String url) async {
final RunResult result = await device.shell('session_control add $url');
return result.exitCode == 0;
}
}
// 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 '../base/process.dart';
import '../globals.dart' as globals;
import 'fuchsia_device.dart';
// Usage: tiles_ctl <command>
// Supported commands:
// start
// add [--disable-focus] <url> [<args>...]
// remove <key>
// list
// quit
/// A simple wrapper around the 'tiles_ctl' tool running on the Fuchsia device.
class FuchsiaTilesCtl {
/// Finds the key for the app called [appName], or returns -1 if it can't be
/// found.
static Future<int> findAppKey(FuchsiaDevice device, String appName) async {
final FuchsiaTilesCtl tilesCtl = fuchsiaDeviceTools.tilesCtl;
final Map<int, String>? runningApps = await tilesCtl.list(device);
if (runningApps == null) {
globals.printTrace('tiles_ctl is not running');
return -1;
}
for (final MapEntry<int, String> entry in runningApps.entries) {
if (entry.value.contains('$appName#meta')) {
return entry.key;
}
}
return -1;
}
/// Ensures that tiles is running on the device.
static Future<bool> ensureStarted(FuchsiaDevice device) async {
final FuchsiaTilesCtl tilesCtl = fuchsiaDeviceTools.tilesCtl;
final Map<int, String>? runningApps = await tilesCtl.list(device);
if (runningApps == null) {
return tilesCtl.start(device);
}
return true;
}
/// Instructs 'tiles' to start on the device.
///
/// Returns true on success and false on failure.
Future<bool> start(FuchsiaDevice device) async {
final RunResult result = await device.shell('tiles_ctl start');
return result.exitCode == 0;
}
/// Returns a mapping of tile keys to app URLs.
///
/// Returns an empty mapping if tiles_ctl is running but no apps are running.
/// Returns null if tiles_ctl is not running.
Future<Map<int, String>?> list(FuchsiaDevice device) async {
// Output of tiles_ctl list has the format:
// Found 1 tiles:
// Tile key 1 url fuchsia-pkg://fuchsia.com/stocks#meta/stocks.cmx ...
final Map<int, String> tiles = <int, String>{};
final RunResult result = await device.shell('tiles_ctl list');
if (result.exitCode != 0) {
return null;
}
// Look for evidence that tiles_ctl is not running.
if (result.stdout.contains("Couldn't find tiles component in realm")) {
return null;
}
// Find lines beginning with 'Tile'
for (final String line in result.stdout.split('\n')) {
final List<String> words = line.split(' ');
if (words.isNotEmpty && words[0] == 'Tile') {
final int? key = int.tryParse(words[2]);
if (key != null) {
final String url = words[4];
tiles[key] = url;
}
}
}
return tiles;
}
/// Instructs tiles on the device to begin running the app at [url] in a new
/// tile.
///
/// The app is passed the arguments in [args]. Flutter apps receive these
/// arguments as arguments to `main()`. [url] should be formatted as a
/// Fuchsia-style package URL, e.g.:
/// fuchsia-pkg://fuchsia.com/flutter_gallery#meta/flutter_gallery.cmx
/// Returns true on success and false on failure.
Future<bool> add(FuchsiaDevice device, String url, List<String> args) async {
final RunResult result = await device.shell(
'tiles_ctl add $url ${args.join(" ")}');
return result.exitCode == 0;
}
/// Instructs tiles on the device to remove the app with key [key].
///
/// Returns true on success and false on failure.
Future<bool> remove(FuchsiaDevice device, int key) async {
final RunResult result = await device.shell('tiles_ctl remove $key');
return result.exitCode == 0;
}
/// Instructs tiles on the device to quit.
///
/// Returns true on success and false on failure.
Future<bool> quit(FuchsiaDevice device) async {
final RunResult result = await device.shell('tiles_ctl quit');
return result.exitCode == 0;
}
}
// 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:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/commands/build_fuchsia.dart';
import 'package:flutter_tools/src/features.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/project.dart';
import 'package:meta/meta.dart';
import 'package:test/fake.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fakes.dart';
import '../../src/test_flutter_command_runner.dart';
// Defined globally for fakes to use.
FileSystem fileSystem;
void main() {
Cache.disableLocking();
final Platform linuxPlatform = FakePlatform(
environment: const <String, String>{
'FLUTTER_ROOT': '/',
},
);
final Platform windowsPlatform = FakePlatform(
operatingSystem: 'windows',
environment: const <String, String>{
'FLUTTER_ROOT': '/',
},
);
FakeFuchsiaSdk fuchsiaSdk;
setUp(() {
fuchsiaSdk = FakeFuchsiaSdk();
fileSystem = MemoryFileSystem.test();
});
group('Fuchsia build fails gracefully when', () {
testUsingContext('The feature is disabled', () async {
final BuildCommand command = BuildCommand();
fileSystem.directory('fuchsia').createSync(recursive: true);
fileSystem.file('.packages').createSync();
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('lib/main.dart').createSync(recursive: true);
expect(
createTestCommandRunner(command).run(const <String>['build', 'fuchsia']),
throwsToolExit(message: '"build fuchsia" is currently disabled'),
);
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(),
});
testUsingContext('there is no Fuchsia project', () async {
final BuildCommand command = BuildCommand();
expect(
createTestCommandRunner(command).run(const <String>['build', 'fuchsia']),
throwsToolExit(),
);
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
});
testUsingContext('there is no cmx file', () async {
final BuildCommand command = BuildCommand();
fileSystem.directory('fuchsia').createSync(recursive: true);
fileSystem.file('.packages').createSync();
fileSystem.file('pubspec.yaml').createSync();
expect(
createTestCommandRunner(command).run(const <String>['build', 'fuchsia']),
throwsToolExit(),
);
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
});
testUsingContext('on Windows platform', () async {
final BuildCommand command = BuildCommand();
const String appName = 'app_name';
fileSystem
.file(fileSystem.path.join('fuchsia', 'meta', '$appName.cmx'))
..createSync(recursive: true)
..writeAsStringSync('{}');
fileSystem.file('.packages').createSync();
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
final bool supported = BuildFuchsiaCommand(verboseHelp: false).supported;
expect(
createTestCommandRunner(command).run(const <String>['build', 'fuchsia']),
supported ? throwsToolExit() : throwsA(isA<UsageException>()),
);
}, overrides: <Type, Generator>{
Platform: () => windowsPlatform,
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
});
testUsingContext('there is no Fuchsia kernel compiler', () async {
final BuildCommand command = BuildCommand();
const String appName = 'app_name';
fileSystem
.file(fileSystem.path.join('fuchsia', 'meta', '$appName.cmx'))
..createSync(recursive: true)
..writeAsStringSync('{}');
fileSystem.file('.packages').createSync();
fileSystem.file(fileSystem.path.join('lib', 'main.dart')).createSync(recursive: true);
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
expect(
createTestCommandRunner(command).run(const <String>['build', 'fuchsia']),
throwsToolExit(),
);
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
});
});
testUsingContext('Fuchsia build parts fit together right', () async {
final BuildCommand command = BuildCommand();
const String appName = 'app_name';
fileSystem
.file(fileSystem.path.join('fuchsia', 'meta', '$appName.cmx'))
..createSync(recursive: true)
..writeAsStringSync('{}');
fileSystem.file('.packages').createSync();
fileSystem.file(fileSystem.path.join('lib', 'main.dart')).createSync(recursive: true);
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
await createTestCommandRunner(command)
.run(const <String>['build', 'fuchsia']);
final String farPath = fileSystem.path.join(
getFuchsiaBuildDirectory(), 'pkg', 'app_name-0.far',
);
expect(fileSystem.file(farPath), exists);
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
FuchsiaSdk: () => fuchsiaSdk,
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
});
}
class FakeFuchsiaPM extends Fake implements FuchsiaPM {
String _appName;
@override
Future<bool> init(String buildPath, String appName) async {
if (!fileSystem.directory(buildPath).existsSync()) {
return false;
}
fileSystem
.file(fileSystem.path.join(buildPath, 'meta', 'package'))
.createSync(recursive: true);
_appName = appName;
return true;
}
@override
Future<bool> build(String buildPath, String manifestPath) async {
if (!fileSystem.file(fileSystem.path.join(buildPath, 'meta', 'package')).existsSync() ||
!fileSystem.file(manifestPath).existsSync()) {
return false;
}
fileSystem.file(fileSystem.path.join(buildPath, 'meta.far')).createSync(recursive: true);
return true;
}
@override
Future<bool> archive(String buildPath, String manifestPath) async {
if (!fileSystem.file(fileSystem.path.join(buildPath, 'meta', 'package')).existsSync() ||
!fileSystem.file(manifestPath).existsSync()) {
return false;
}
if (_appName == null) {
return false;
}
fileSystem
.file(fileSystem.path.join(buildPath, '$_appName-0.far'))
.createSync(recursive: true);
return true;
}
}
class FakeFuchsiaKernelCompiler extends Fake 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 = fileSystem.path.join(outDir, '$appName.dilpmanifest');
fileSystem.file(manifestPath).createSync(recursive: true);
}
}
class FakeFuchsiaSdk extends Fake implements FuchsiaSdk {
@override
final FuchsiaPM fuchsiaPM = FakeFuchsiaPM();
@override
final FuchsiaKernelCompiler fuchsiaKernelCompiler =
FakeFuchsiaKernelCompiler();
}
...@@ -12,7 +12,6 @@ import 'package:flutter_tools/src/commands/build.dart'; ...@@ -12,7 +12,6 @@ import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/commands/build_aar.dart'; import 'package:flutter_tools/src/commands/build_aar.dart';
import 'package:flutter_tools/src/commands/build_apk.dart'; import 'package:flutter_tools/src/commands/build_apk.dart';
import 'package:flutter_tools/src/commands/build_appbundle.dart'; import 'package:flutter_tools/src/commands/build_appbundle.dart';
import 'package:flutter_tools/src/commands/build_fuchsia.dart';
import 'package:flutter_tools/src/commands/build_ios.dart'; import 'package:flutter_tools/src/commands/build_ios.dart';
import 'package:flutter_tools/src/commands/build_ios_framework.dart'; import 'package:flutter_tools/src/commands/build_ios_framework.dart';
import 'package:flutter_tools/src/commands/build_linux.dart'; import 'package:flutter_tools/src/commands/build_linux.dart';
...@@ -36,9 +35,11 @@ void main() { ...@@ -36,9 +35,11 @@ void main() {
BuildIOSCommand(verboseHelp: false), BuildIOSCommand(verboseHelp: false),
BuildIOSArchiveCommand(verboseHelp: false), BuildIOSArchiveCommand(verboseHelp: false),
BuildAppBundleCommand(), BuildAppBundleCommand(),
BuildFuchsiaCommand(verboseHelp: false),
BuildAarCommand(verboseHelp: false), BuildAarCommand(verboseHelp: false),
BuildIOSFrameworkCommand(verboseHelp: false, buildSystem: globals.buildSystem), BuildIOSFrameworkCommand(
verboseHelp: false,
buildSystem: globals.buildSystem,
),
AttachCommand(), AttachCommand(),
]; ];
...@@ -53,7 +54,8 @@ void main() { ...@@ -53,7 +54,8 @@ void main() {
} }
}); });
testUsingContext('BuildSubCommand displays current null safety mode', () async { testUsingContext('BuildSubCommand displays current null safety mode',
() async {
const BuildInfo unsound = BuildInfo( const BuildInfo unsound = BuildInfo(
BuildMode.debug, BuildMode.debug,
'', '',
...@@ -67,11 +69,13 @@ void main() { ...@@ -67,11 +69,13 @@ void main() {
); );
FakeBuildSubCommand().test(unsound); FakeBuildSubCommand().test(unsound);
expect(testLogger.statusText, contains('Building without sound null safety')); expect(
testLogger.statusText, contains('Building without sound null safety'));
testLogger.clear(); testLogger.clear();
FakeBuildSubCommand().test(sound); FakeBuildSubCommand().test(sound);
expect(testLogger.statusText, contains('💪 Building with sound null safety 💪')); expect(testLogger.statusText,
contains('💪 Building with sound null safety 💪'));
}); });
testUsingContext('Include only supported sub commands', () { testUsingContext('Include only supported sub commands', () {
......
...@@ -22,8 +22,6 @@ import 'package:flutter_tools/src/fuchsia/fuchsia_kernel_compiler.dart'; ...@@ -22,8 +22,6 @@ 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_pm.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/pkgctl.dart'; import 'package:flutter_tools/src/fuchsia/pkgctl.dart';
import 'package:flutter_tools/src/fuchsia/session_control.dart';
import 'package:flutter_tools/src/fuchsia/tiles_ctl.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:meta/meta.dart'; import 'package:meta/meta.dart';
...@@ -42,7 +40,6 @@ void main() { ...@@ -42,7 +40,6 @@ void main() {
Artifacts artifacts; Artifacts artifacts;
FakeProcessManager fakeSuccessfulProcessManager; FakeProcessManager fakeSuccessfulProcessManager;
FakeProcessManager fakeFailedProcessManagerForHostAddress; FakeProcessManager fakeFailedProcessManagerForHostAddress;
FakeProcessManager fakeSuccessfulProcessManagerWithSession;
File sshConfig; File sshConfig;
setUp(() { setUp(() {
...@@ -50,56 +47,69 @@ void main() { ...@@ -50,56 +47,69 @@ void main() {
osUtils = FakeOperatingSystemUtils(); osUtils = FakeOperatingSystemUtils();
fuchsiaDeviceTools = FakeFuchsiaDeviceTools(); fuchsiaDeviceTools = FakeFuchsiaDeviceTools();
fuchsiaSdk = FakeFuchsiaSdk(); fuchsiaSdk = FakeFuchsiaSdk();
sshConfig = MemoryFileSystem.test().file('ssh_config')..writeAsStringSync('\n'); sshConfig = MemoryFileSystem.test().file('ssh_config')
..writeAsStringSync('\n');
artifacts = Artifacts.test(); artifacts = Artifacts.test();
for (final BuildMode mode in <BuildMode>[BuildMode.debug, BuildMode.release]) { for (final BuildMode mode in <BuildMode>[
memoryFileSystem.file( BuildMode.debug,
artifacts.getArtifactPath(Artifact.fuchsiaKernelCompiler, BuildMode.release
platform: TargetPlatform.fuchsia_arm64, mode: mode), ]) {
).createSync(); memoryFileSystem
.file(
memoryFileSystem.file( artifacts.getArtifactPath(Artifact.fuchsiaKernelCompiler,
artifacts.getArtifactPath(Artifact.platformKernelDill, platform: TargetPlatform.fuchsia_arm64, mode: mode),
platform: TargetPlatform.fuchsia_arm64, mode: mode), )
).createSync(); .createSync();
memoryFileSystem.file( memoryFileSystem
artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, .file(
platform: TargetPlatform.fuchsia_arm64, mode: mode), artifacts.getArtifactPath(Artifact.platformKernelDill,
).createSync(); platform: TargetPlatform.fuchsia_arm64, mode: mode),
)
memoryFileSystem.file( .createSync();
artifacts.getArtifactPath(Artifact.fuchsiaFlutterRunner,
platform: TargetPlatform.fuchsia_arm64, mode: mode), memoryFileSystem
).createSync(); .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>[ fakeSuccessfulProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', '123', 'which session_control'],
exitCode: 1,
),
FakeCommand( FakeCommand(
command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'], command: <String>[
stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22', '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>[ fakeFailedProcessManagerForHostAddress =
FakeProcessManager.list(<FakeCommand>[
FakeCommand( FakeCommand(
command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'], command: <String>[
stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22', '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, exitCode: 1,
), ),
]); ]);
fakeSuccessfulProcessManagerWithSession = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', '123', 'which session_control'],
stdout: '/bin/session_control',
),
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',
),
]);
}); });
Future<LaunchResult> setupAndStartApp({ Future<LaunchResult> setupAndStartApp({
...@@ -117,15 +127,21 @@ void main() { ...@@ -117,15 +127,21 @@ void main() {
final File far = globals.fs.file('app_name-0.far')..createSync(); final File far = globals.fs.file('app_name-0.far')..createSync();
app = FuchsiaApp.fromPrebuiltApp(far); app = FuchsiaApp.fromPrebuiltApp(far);
} else { } else {
globals.fs.file(globals.fs.path.join('fuchsia', 'meta', '$appName.cmx')) globals.fs.file(globals.fs.path.join('fuchsia', 'meta', '$appName.cm'))
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('{}'); ..writeAsStringSync('{}');
globals.fs.file('.packages').createSync(); globals.fs.file('.packages').createSync();
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs
app = BuildableFuchsiaApp(project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory).fuchsia); .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)); final DebuggingOptions debuggingOptions = DebuggingOptions.disabled(
BuildInfo(mode, null, treeShakeIcons: false));
return device.startApp( return device.startApp(
app, app,
prebuiltApplication: prebuilt, prebuiltApplication: prebuilt,
...@@ -133,10 +149,12 @@ void main() { ...@@ -133,10 +149,12 @@ void main() {
); );
} }
testUsingContext('start prebuilt in release mode', () async { testUsingContext(
'start prebuilt in release mode fails without session',
() async {
final LaunchResult launchResult = final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.release); await setupAndStartApp(prebuilt: true, mode: BuildMode.release);
expect(launchResult.started, isTrue); expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isFalse); expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
...@@ -150,20 +168,22 @@ void main() { ...@@ -150,20 +168,22 @@ void main() {
testUsingContext('start prebuilt in release mode with session', () async { testUsingContext('start prebuilt in release mode with session', () async {
final LaunchResult launchResult = final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.release); await setupAndStartApp(prebuilt: true, mode: BuildMode.release);
expect(launchResult.started, isTrue); expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isFalse); expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManagerWithSession, ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk, FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils, OperatingSystemUtils: () => osUtils,
}); });
testUsingContext('start and stop prebuilt in release mode', () async { testUsingContext(
'start and stop prebuilt in release mode fails without session',
() async {
const String appName = 'app_name'; const String appName = 'app_name';
final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123'); final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123');
globals.fs.directory('fuchsia').createSync(recursive: true); globals.fs.directory('fuchsia').createSync(recursive: true);
...@@ -172,14 +192,12 @@ void main() { ...@@ -172,14 +192,12 @@ void main() {
final File far = globals.fs.file('app_name-0.far')..createSync(); final File far = globals.fs.file('app_name-0.far')..createSync();
final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far); final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far);
final DebuggingOptions debuggingOptions = final DebuggingOptions debuggingOptions = DebuggingOptions.disabled(
DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)); const BuildInfo(BuildMode.release, null, treeShakeIcons: false));
final LaunchResult launchResult = await device.startApp(app, final LaunchResult launchResult = await device.startApp(app,
prebuiltApplication: true, prebuiltApplication: true, debuggingOptions: debuggingOptions);
debuggingOptions: debuggingOptions); expect(launchResult.started, isFalse);
expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isFalse); expect(launchResult.hasObservatory, isFalse);
expect(await device.stopApp(app), isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
...@@ -190,7 +208,8 @@ void main() { ...@@ -190,7 +208,8 @@ void main() {
OperatingSystemUtils: () => osUtils, OperatingSystemUtils: () => osUtils,
}); });
testUsingContext('start and stop prebuilt in release mode with session', () async { testUsingContext('start and stop prebuilt in release mode with session',
() async {
const String appName = 'app_name'; const String appName = 'app_name';
final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123'); final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123');
globals.fs.directory('fuchsia').createSync(recursive: true); globals.fs.directory('fuchsia').createSync(recursive: true);
...@@ -199,29 +218,29 @@ void main() { ...@@ -199,29 +218,29 @@ void main() {
final File far = globals.fs.file('app_name-0.far')..createSync(); final File far = globals.fs.file('app_name-0.far')..createSync();
final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far); final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far);
final DebuggingOptions debuggingOptions = final DebuggingOptions debuggingOptions = DebuggingOptions.disabled(
DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)); const BuildInfo(BuildMode.release, null, treeShakeIcons: false));
final LaunchResult launchResult = await device.startApp(app, final LaunchResult launchResult = await device.startApp(app,
prebuiltApplication: true, prebuiltApplication: true, debuggingOptions: debuggingOptions);
debuggingOptions: debuggingOptions);
expect(launchResult.started, isTrue); expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isFalse); expect(launchResult.hasObservatory, isFalse);
expect(await device.stopApp(app), isTrue); expect(await device.stopApp(app), isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManagerWithSession, ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk, FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils, OperatingSystemUtils: () => osUtils,
}); });
testUsingContext('start prebuilt in debug mode', () async { testUsingContext(
'start prebuilt in debug mode fails without session',
() async {
final LaunchResult launchResult = final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.debug); await setupAndStartApp(prebuilt: true, mode: BuildMode.debug);
expect(launchResult.started, isTrue); expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
...@@ -234,32 +253,32 @@ void main() { ...@@ -234,32 +253,32 @@ void main() {
testUsingContext('start prebuilt in debug mode with session', () async { testUsingContext('start prebuilt in debug mode with session', () async {
final LaunchResult launchResult = final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.debug); await setupAndStartApp(prebuilt: true, mode: BuildMode.debug);
expect(launchResult.started, isTrue); expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isTrue); expect(launchResult.hasObservatory, isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManagerWithSession, ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk, FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils, OperatingSystemUtils: () => osUtils,
}); });
testUsingContext('start buildable in release mode', () async { testUsingContext(
final LaunchResult launchResult = 'start buildable in release mode fails without session',
await setupAndStartApp(prebuilt: false, mode: BuildMode.release); () async {
expect(launchResult.started, isTrue); expect(
expect(launchResult.hasObservatory, isFalse); () 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>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[ ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', '123', 'which session_control'],
exitCode: 1,
),
const FakeCommand( const FakeCommand(
command: <String>[ command: <String>[
'Artifact.genSnapshot.TargetPlatform.fuchsia_arm64.release', 'Artifact.genSnapshot.TargetPlatform.fuchsia_arm64.release',
...@@ -270,8 +289,15 @@ void main() { ...@@ -270,8 +289,15 @@ void main() {
], ],
), ),
FakeCommand( FakeCommand(
command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'], command: <String>[
stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22', '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, FuchsiaDeviceTools: () => fuchsiaDeviceTools,
...@@ -280,44 +306,55 @@ void main() { ...@@ -280,44 +306,55 @@ void main() {
OperatingSystemUtils: () => osUtils, OperatingSystemUtils: () => osUtils,
}); });
testUsingContext('start buildable in release mode with session', () async { testUsingContext(
final LaunchResult launchResult = 'start buildable in release mode with session fails, does not build apps yet',
await setupAndStartApp(prebuilt: false, mode: BuildMode.release); () async {
expect(launchResult.started, isTrue); expect(
expect(launchResult.hasObservatory, isFalse); () 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>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[ ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
const FakeCommand( const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', '123', 'which session_control'], command: <String>[
stdout: '/bin/session_control', 'Artifact.genSnapshot.TargetPlatform.fuchsia_arm64.release',
), '--deterministic',
const FakeCommand( '--snapshot_kind=app-aot-elf',
command: <String>[ '--elf=build/fuchsia/elf.aotsnapshot',
'Artifact.genSnapshot.TargetPlatform.fuchsia_arm64.release', 'build/fuchsia/app_name.dil',
'--deterministic', ],
'--snapshot_kind=app-aot-elf', ),
'--elf=build/fuchsia/elf.aotsnapshot', FakeCommand(
'build/fuchsia/app_name.dil', command: <String>[
], 'ssh',
), '-F',
FakeCommand( sshConfig.absolute.path,
command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'], '123',
stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22', r'echo $SSH_CONNECTION'
), ],
]), stdout:
'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
),
]),
FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk, FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils, OperatingSystemUtils: () => osUtils,
}); });
testUsingContext('start buildable in debug mode', () async { testUsingContext(
final LaunchResult launchResult = 'start buildable in debug mode fails without session',
await setupAndStartApp(prebuilt: false, mode: BuildMode.debug); () async {
expect(launchResult.started, isTrue); expect(
expect(launchResult.hasObservatory, isTrue); () 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>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
...@@ -328,37 +365,42 @@ void main() { ...@@ -328,37 +365,42 @@ void main() {
OperatingSystemUtils: () => osUtils, OperatingSystemUtils: () => osUtils,
}); });
testUsingContext('start buildable in debug mode with session', () async { testUsingContext(
final LaunchResult launchResult = 'start buildable in debug mode with session fails, does not build apps yet',
await setupAndStartApp(prebuilt: false, mode: BuildMode.debug); () async {
expect(launchResult.started, isTrue); expect(
expect(launchResult.hasObservatory, isTrue); () 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>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManagerWithSession, ProcessManager: () => fakeSuccessfulProcessManager,
FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk, FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils, OperatingSystemUtils: () => osUtils,
}); });
testUsingContext('fail when cant get ssh config', () async { testUsingContext('fail when cant get ssh config', () async {
expect(() async => expect(
setupAndStartApp(prebuilt: true, mode: BuildMode.release), () async => setupAndStartApp(prebuilt: true, mode: BuildMode.release),
throwsToolExit(message: 'Cannot interact with device. No ssh config.\n' throwsToolExit(
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.')); message: 'Cannot interact with device. No ssh config.\n'
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
FuchsiaArtifacts: () => FuchsiaArtifacts(), FuchsiaArtifacts: () => FuchsiaArtifacts(),
FuchsiaSdk: () => FakeFuchsiaSdk(ffx: FakeFuchsiaFfxWithSession()),
OperatingSystemUtils: () => osUtils, OperatingSystemUtils: () => osUtils,
}); });
testUsingContext('fail when cant get host address', () async { testUsingContext('fail when cant get host address', () async {
expect(() async => expect(() async => FuchsiaDeviceWithFakeDiscovery('123').hostAddress,
FuchsiaDeviceWithFakeDiscovery('123').hostAddress,
throwsToolExit(message: 'Failed to get local address, aborting.')); throwsToolExit(message: 'Failed to get local address, aborting.'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => artifacts, Artifacts: () => artifacts,
...@@ -400,37 +442,6 @@ void main() { ...@@ -400,37 +442,6 @@ void main() {
FuchsiaSdk: () => fuchsiaSdk, FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils, OperatingSystemUtils: () => osUtils,
}); });
testUsingContext('fail with correct LaunchResult when tiles 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(tiles: FailingTilesCtl()),
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext('fail with correct LaunchResult when tiles fails with 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: () => fakeSuccessfulProcessManagerWithSession,
FuchsiaDeviceTools: () => FakeFuchsiaDeviceTools(sessionControl: FailingFuchsiaSessionControl()),
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
}); });
} }
...@@ -440,34 +451,41 @@ Process _createFakeProcess({ ...@@ -440,34 +451,41 @@ Process _createFakeProcess({
String stderr = '', String stderr = '',
bool persistent = false, bool persistent = false,
}) { }) {
final Stream<List<int>> stdoutStream = Stream<List<int>>.fromIterable(<List<int>>[ final Stream<List<int>> stdoutStream =
Stream<List<int>>.fromIterable(<List<int>>[
utf8.encode(stdout), utf8.encode(stdout),
]); ]);
final Stream<List<int>> stderrStream = Stream<List<int>>.fromIterable(<List<int>>[ final Stream<List<int>> stderrStream =
Stream<List<int>>.fromIterable(<List<int>>[
utf8.encode(stderr), utf8.encode(stderr),
]); ]);
final Completer<int> exitCodeCompleter = Completer<int>(); final Completer<int> exitCodeCompleter = Completer<int>();
final Process process = FakeProcess( final Process process = FakeProcess(
stdout: stdoutStream, stdout: stdoutStream,
stderr: stderrStream, stderr: stderrStream,
exitCode: persistent ? exitCodeCompleter.future : Future<int>.value(exitCode), exitCode:
persistent ? exitCodeCompleter.future : Future<int>.value(exitCode),
); );
return process; return process;
} }
class FuchsiaDeviceWithFakeDiscovery extends FuchsiaDevice { class FuchsiaDeviceWithFakeDiscovery extends FuchsiaDevice {
FuchsiaDeviceWithFakeDiscovery(String id, {String name}) : super(id, name: name); FuchsiaDeviceWithFakeDiscovery(String id, {String name})
: super(id, name: name);
@override @override
FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(String isolateName) { FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(
String isolateName) {
return FakeFuchsiaIsolateDiscoveryProtocol(); return FakeFuchsiaIsolateDiscoveryProtocol();
} }
@override @override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.fuchsia_arm64; Future<TargetPlatform> get targetPlatform async =>
TargetPlatform.fuchsia_arm64;
} }
class FakeFuchsiaIsolateDiscoveryProtocol implements FuchsiaIsolateDiscoveryProtocol { class FakeFuchsiaIsolateDiscoveryProtocol
implements FuchsiaIsolateDiscoveryProtocol {
@override @override
FutureOr<Uri> get uri => Uri.parse('http://[::1]:37'); FutureOr<Uri> get uri => Uri.parse('http://[::1]:37');
...@@ -513,112 +531,18 @@ class FailingPkgctl implements FuchsiaPkgctl { ...@@ -513,112 +531,18 @@ class FailingPkgctl implements FuchsiaPkgctl {
} }
} }
class FakeFuchsiaTilesCtl implements FuchsiaTilesCtl {
final Map<int, String> _runningApps = <int, String>{};
bool _started = false;
int _nextAppId = 1;
@override
Future<bool> start(FuchsiaDevice device) async {
_started = true;
return true;
}
@override
Future<Map<int, String>> list(FuchsiaDevice device) async {
if (!_started) {
return null;
}
return _runningApps;
}
@override
Future<bool> add(FuchsiaDevice device, String url, List<String> args) async {
if (!_started) {
return false;
}
_runningApps[_nextAppId] = url;
_nextAppId++;
return true;
}
@override
Future<bool> remove(FuchsiaDevice device, int key) async {
if (!_started) {
return false;
}
_runningApps.remove(key);
return true;
}
@override
Future<bool> quit(FuchsiaDevice device) async {
if (!_started) {
return false;
}
_started = false;
return true;
}
}
class FailingTilesCtl implements FuchsiaTilesCtl {
@override
Future<bool> start(FuchsiaDevice device) async {
return false;
}
@override
Future<Map<int, String>> list(FuchsiaDevice device) async {
return null;
}
@override
Future<bool> add(FuchsiaDevice device, String url, List<String> args) async {
return false;
}
@override
Future<bool> remove(FuchsiaDevice device, int key) async {
return false;
}
@override
Future<bool> quit(FuchsiaDevice device) async {
return false;
}
}
class FakeFuchsiaSessionControl implements FuchsiaSessionControl {
@override
Future<bool> add(FuchsiaDevice device, String url) async {
return true;
}
}
class FailingFuchsiaSessionControl implements FuchsiaSessionControl {
@override
Future<bool> add(FuchsiaDevice device, String url) async {
return false;
}
}
class FakeFuchsiaDeviceTools implements FuchsiaDeviceTools { class FakeFuchsiaDeviceTools implements FuchsiaDeviceTools {
FakeFuchsiaDeviceTools({ FakeFuchsiaDeviceTools({
FuchsiaPkgctl pkgctl, FuchsiaPkgctl pkgctl,
FuchsiaTilesCtl tiles, FuchsiaFfx ffx,
FuchsiaSessionControl sessionControl,
}) : pkgctl = pkgctl ?? FakeFuchsiaPkgctl(), }) : pkgctl = pkgctl ?? FakeFuchsiaPkgctl(),
tilesCtl = tiles ?? FakeFuchsiaTilesCtl(), ffx = ffx ?? FakeFuchsiaFfx();
sessionControl = sessionControl ?? FakeFuchsiaSessionControl();
@override @override
final FuchsiaPkgctl pkgctl; final FuchsiaPkgctl pkgctl;
@override @override
final FuchsiaTilesCtl tilesCtl; final FuchsiaFfx ffx;
@override
final FuchsiaSessionControl sessionControl;
} }
class FakeFuchsiaPM implements FuchsiaPM { class FakeFuchsiaPM implements FuchsiaPM {
...@@ -638,17 +562,23 @@ class FakeFuchsiaPM implements FuchsiaPM { ...@@ -638,17 +562,23 @@ class FakeFuchsiaPM implements FuchsiaPM {
@override @override
Future<bool> build(String buildPath, String manifestPath) async { Future<bool> build(String buildPath, String manifestPath) async {
if (!globals.fs.file(globals.fs.path.join(buildPath, 'meta', 'package')).existsSync() || if (!globals.fs
.file(globals.fs.path.join(buildPath, 'meta', 'package'))
.existsSync() ||
!globals.fs.file(manifestPath).existsSync()) { !globals.fs.file(manifestPath).existsSync()) {
return false; return false;
} }
globals.fs.file(globals.fs.path.join(buildPath, 'meta.far')).createSync(recursive: true); globals.fs
.file(globals.fs.path.join(buildPath, 'meta.far'))
.createSync(recursive: true);
return true; return true;
} }
@override @override
Future<bool> archive(String buildPath, String manifestPath) async { Future<bool> archive(String buildPath, String manifestPath) async {
if (!globals.fs.file(globals.fs.path.join(buildPath, 'meta', 'package')).existsSync() || if (!globals.fs
.file(globals.fs.path.join(buildPath, 'meta', 'package'))
.existsSync() ||
!globals.fs.file(manifestPath).existsSync()) { !globals.fs.file(manifestPath).existsSync()) {
return false; return false;
} }
...@@ -692,7 +622,6 @@ class FailingPM implements FuchsiaPM { ...@@ -692,7 +622,6 @@ class FailingPM implements FuchsiaPM {
return false; return false;
} }
@override @override
Future<bool> build(String buildPath, String manifestPath) async { Future<bool> build(String buildPath, String manifestPath) async {
return false; return false;
...@@ -728,7 +657,8 @@ class FakeFuchsiaKernelCompiler implements FuchsiaKernelCompiler { ...@@ -728,7 +657,8 @@ class FakeFuchsiaKernelCompiler implements FuchsiaKernelCompiler {
}) async { }) async {
final String outDir = getFuchsiaBuildDirectory(); final String outDir = getFuchsiaBuildDirectory();
final String appName = fuchsiaProject.project.manifest.appName; final String appName = fuchsiaProject.project.manifest.appName;
final String manifestPath = globals.fs.path.join(outDir, '$appName.dilpmanifest'); final String manifestPath =
globals.fs.path.join(outDir, '$appName.dilpmanifest');
globals.fs.file(manifestPath).createSync(recursive: true); globals.fs.file(manifestPath).createSync(recursive: true);
} }
} }
...@@ -743,6 +673,38 @@ class FakeFuchsiaFfx implements FuchsiaFfx { ...@@ -743,6 +673,38 @@ class FakeFuchsiaFfx implements FuchsiaFfx {
Future<String> resolve(String deviceName) async { Future<String> resolve(String deviceName) async {
return '192.168.42.10'; 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 { class FakeFuchsiaSdk extends Fake implements FuchsiaSdk {
...@@ -750,9 +712,9 @@ class FakeFuchsiaSdk extends Fake implements FuchsiaSdk { ...@@ -750,9 +712,9 @@ class FakeFuchsiaSdk extends Fake implements FuchsiaSdk {
FuchsiaPM pm, FuchsiaPM pm,
FuchsiaKernelCompiler compiler, FuchsiaKernelCompiler compiler,
FuchsiaFfx ffx, FuchsiaFfx ffx,
}) : fuchsiaPM = pm ?? FakeFuchsiaPM(), }) : fuchsiaPM = pm ?? FakeFuchsiaPM(),
fuchsiaKernelCompiler = compiler ?? FakeFuchsiaKernelCompiler(), fuchsiaKernelCompiler = compiler ?? FakeFuchsiaKernelCompiler(),
fuchsiaFfx = ffx ?? FakeFuchsiaFfx(); fuchsiaFfx = ffx ?? FakeFuchsiaFfx();
@override @override
final FuchsiaPM fuchsiaPM; final FuchsiaPM fuchsiaPM;
......
...@@ -40,7 +40,7 @@ final vm_service.Isolate fakeIsolate = vm_service.Isolate( ...@@ -40,7 +40,7 @@ final vm_service.Isolate fakeIsolate = vm_service.Isolate(
id: '1', id: '1',
pauseEvent: vm_service.Event( pauseEvent: vm_service.Event(
kind: vm_service.EventKind.kResume, kind: vm_service.EventKind.kResume,
timestamp: 0 timestamp: 0,
), ),
breakpoints: <vm_service.Breakpoint>[], breakpoints: <vm_service.Breakpoint>[],
exceptionPauseMode: null, exceptionPauseMode: null,
...@@ -87,8 +87,10 @@ void main() { ...@@ -87,8 +87,10 @@ void main() {
expect(device.supportsRuntimeMode(BuildMode.jitRelease), false); expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
}); });
testWithoutContext('lists nothing when workflow cannot list devices', () async { testWithoutContext('lists nothing when workflow cannot list devices',
final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(canListDevices: false); () async {
final FakeFuchsiaWorkflow fuchsiaWorkflow =
FakeFuchsiaWorkflow(canListDevices: false);
final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
platform: FakePlatform(), platform: FakePlatform(),
fuchsiaSdk: FakeFuchsiaSdk(devices: 'ignored'), fuchsiaSdk: FakeFuchsiaSdk(devices: 'ignored'),
...@@ -102,7 +104,9 @@ void main() { ...@@ -102,7 +104,9 @@ void main() {
testWithoutContext('can parse ffx output for single device', () async { testWithoutContext('can parse ffx output for single device', () async {
final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(); final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow();
final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devices: '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel'); final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(
devices:
'2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel');
final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
platform: FakePlatform(environment: <String, String>{}), platform: FakePlatform(environment: <String, String>{}),
fuchsiaSdk: fuchsiaSdk, fuchsiaSdk: fuchsiaSdk,
...@@ -118,10 +122,10 @@ void main() { ...@@ -118,10 +122,10 @@ void main() {
testWithoutContext('can parse ffx output for multiple devices', () async { testWithoutContext('can parse ffx output for multiple devices', () async {
final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(); final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow();
final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devices: final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(
'2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel\n' devices:
'2001:0db8:85a3:0000:0000:8a2e:0370:7335 foo-bar-fiz-buzz' '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 FuchsiaDevices fuchsiaDevices = FuchsiaDevices( final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
platform: FakePlatform(), platform: FakePlatform(),
fuchsiaSdk: fuchsiaSdk, fuchsiaSdk: fuchsiaSdk,
...@@ -138,7 +142,8 @@ void main() { ...@@ -138,7 +142,8 @@ void main() {
}); });
testWithoutContext('can parse junk output from ffx', () async { testWithoutContext('can parse junk output from ffx', () async {
final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow(canListDevices: false); final FakeFuchsiaWorkflow fuchsiaWorkflow =
FakeFuchsiaWorkflow(canListDevices: false);
final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devices: 'junk'); final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devices: 'junk');
final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
platform: FakePlatform(), platform: FakePlatform(),
...@@ -163,7 +168,8 @@ void main() { ...@@ -163,7 +168,8 @@ void main() {
testWithoutContext('default capabilities', () async { testWithoutContext('default capabilities', () async {
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device'); final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
final FlutterProject project = FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory); final FlutterProject project =
FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory);
memoryFileSystem.directory('fuchsia').createSync(recursive: true); memoryFileSystem.directory('fuchsia').createSync(recursive: true);
memoryFileSystem.file('pubspec.yaml').createSync(); memoryFileSystem.file('pubspec.yaml').createSync();
...@@ -181,7 +187,8 @@ void main() { ...@@ -181,7 +187,8 @@ void main() {
testWithoutContext('supported for project', () async { testWithoutContext('supported for project', () async {
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device'); final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
final FlutterProject project = FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory); final FlutterProject project =
FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory);
memoryFileSystem.directory('fuchsia').createSync(recursive: true); memoryFileSystem.directory('fuchsia').createSync(recursive: true);
memoryFileSystem.file('pubspec.yaml').createSync(); memoryFileSystem.file('pubspec.yaml').createSync();
...@@ -190,13 +197,15 @@ void main() { ...@@ -190,13 +197,15 @@ void main() {
testWithoutContext('not supported for project', () async { testWithoutContext('not supported for project', () async {
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device'); final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
final FlutterProject project = FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory); final FlutterProject project =
FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory);
memoryFileSystem.file('pubspec.yaml').createSync(); memoryFileSystem.file('pubspec.yaml').createSync();
expect(device.isSupportedForProject(project), false); expect(device.isSupportedForProject(project), false);
}); });
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', name: 'device'); final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
expect(await device.targetPlatform, TargetPlatform.fuchsia_arm64); expect(await device.targetPlatform, TargetPlatform.fuchsia_arm64);
...@@ -236,8 +245,15 @@ void main() { ...@@ -236,8 +245,15 @@ void main() {
testUsingContext('hostAddress parsing works', () async { testUsingContext('hostAddress parsing works', () async {
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', 'id', r'echo $SSH_CONNECTION'], command: <String>[
stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22', 'ssh',
'-F',
'/ssh_config',
'id',
r'echo $SSH_CONNECTION'
],
stdout:
'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
)); ));
final FuchsiaDevice device = FuchsiaDevice('id', name: 'device'); final FuchsiaDevice device = FuchsiaDevice('id', name: 'device');
...@@ -248,9 +264,16 @@ void main() { ...@@ -248,9 +264,16 @@ void main() {
ProcessManager: () => processManager, ProcessManager: () => processManager,
}); });
testUsingContext('hostAddress parsing throws tool error on failure', () async { testUsingContext('hostAddress parsing throws tool error on failure',
() async {
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', 'id', r'echo $SSH_CONNECTION'], command: <String>[
'ssh',
'-F',
'/ssh_config',
'id',
r'echo $SSH_CONNECTION'
],
exitCode: 1, exitCode: 1,
)); ));
...@@ -262,9 +285,16 @@ void main() { ...@@ -262,9 +285,16 @@ void main() {
ProcessManager: () => processManager, ProcessManager: () => processManager,
}); });
testUsingContext('hostAddress parsing throws tool error on empty response', () async { testUsingContext('hostAddress parsing throws tool error on empty response',
() async {
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', 'id', r'echo $SSH_CONNECTION'], command: <String>[
'ssh',
'-F',
'/ssh_config',
'id',
r'echo $SSH_CONNECTION'
],
)); ));
final FuchsiaDevice device = FuchsiaDevice('id', name: 'device'); final FuchsiaDevice device = FuchsiaDevice('id', name: 'device');
...@@ -287,28 +317,38 @@ void main() { ...@@ -287,28 +317,38 @@ void main() {
testUsingContext('No vmservices found', () async { testUsingContext('No vmservices found', () async {
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>['ssh', '-F', '/artifact', 'id', 'find /hub -name vmservice-port'], command: <String>[
'ssh',
'-F',
'/artifact',
'id',
'find /hub -name vmservice-port'
],
)); ));
final FuchsiaDevice device = FuchsiaDevice('id', name: 'device'); final FuchsiaDevice device = FuchsiaDevice('id', name: 'device');
await expectLater(device.servicePorts, throwsToolExit(message: 'No Dart Observatories found. Are you running a debug build?')); await expectLater(
device.servicePorts,
throwsToolExit(
message:
'No Dart Observatories found. Are you running a debug build?'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
FuchsiaArtifacts: () => FuchsiaArtifacts( FuchsiaArtifacts: () => FuchsiaArtifacts(
sshConfig: artifactFile, sshConfig: artifactFile,
ffx: artifactFile, ffx: artifactFile,
), ),
FuchsiaSdk: () => FakeFuchsiaSdk(), FuchsiaSdk: () => FakeFuchsiaSdk(),
}); });
group('device logs', () { group('device logs', () {
const String exampleUtcLogs = ''' const String exampleUtcLogs = '''
[2018-11-09 01:27:45][3][297950920][log] INFO: example_app.cmx(flutter): Error doing thing [2018-11-09 01:27:45][3][297950920][log] INFO: example_app.cm(flutter): Error doing thing
[2018-11-09 01:27:58][46257][46269][foo] INFO: Using a thing [2018-11-09 01:27:58][46257][46269][foo] INFO: Using a thing
[2018-11-09 01:29:58][46257][46269][foo] INFO: Blah blah blah [2018-11-09 01:29:58][46257][46269][foo] INFO: Blah blah blah
[2018-11-09 01:29:58][46257][46269][foo] INFO: other_app.cmx(flutter): Do thing [2018-11-09 01:29:58][46257][46269][foo] INFO: other_app.cm(flutter): Do thing
[2018-11-09 01:30:02][41175][41187][bar] INFO: Invoking a bar [2018-11-09 01:30:02][41175][41187][bar] INFO: Invoking a bar
[2018-11-09 01:30:12][52580][52983][log] INFO: example_app.cmx(flutter): Did thing this time [2018-11-09 01:30:12][52580][52983][log] INFO: example_app.cm(flutter): Did thing this time
'''; ''';
FakeProcessManager processManager; FakeProcessManager processManager;
...@@ -316,21 +356,29 @@ void main() { ...@@ -316,21 +356,29 @@ void main() {
File sshConfig; File sshConfig;
setUp(() { setUp(() {
processManager = FakeProcessManager.empty(); processManager = FakeProcessManager.empty();
final FileSystem memoryFileSystem = MemoryFileSystem.test(); final FileSystem memoryFileSystem = MemoryFileSystem.test();
ffx = memoryFileSystem.file('ffx')..writeAsStringSync('\n'); ffx = memoryFileSystem.file('ffx')..writeAsStringSync('\n');
sshConfig = memoryFileSystem.file('ssh_config')..writeAsStringSync('\n'); sshConfig = memoryFileSystem.file('ssh_config')
..writeAsStringSync('\n');
}); });
testUsingContext('can be parsed for an app', () async { testUsingContext('can be parsed for an app', () async {
final Completer<void> lock = Completer<void>(); final Completer<void> lock = Completer<void>();
processManager.addCommand(FakeCommand( processManager.addCommand(FakeCommand(
command: const <String>['ssh', '-F', '/ssh_config', 'id', 'log_listener --clock Local'], command: const <String>[
'ssh',
'-F',
'/ssh_config',
'id',
'log_listener --clock Local'
],
stdout: exampleUtcLogs, stdout: exampleUtcLogs,
completer: lock, completer: lock,
)); ));
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
final DeviceLogReader reader = device.getLogReader(app: FuchsiaModulePackage(name: 'example_app')); final DeviceLogReader reader =
device.getLogReader(app: FuchsiaModulePackage(name: 'example_app'));
final List<String> logLines = <String>[]; final List<String> logLines = <String>[];
reader.logLines.listen((String line) { reader.logLines.listen((String line) {
logLines.add(line); logLines.add(line);
...@@ -355,12 +403,19 @@ void main() { ...@@ -355,12 +403,19 @@ void main() {
testUsingContext('cuts off prior logs', () async { testUsingContext('cuts off prior logs', () async {
final Completer<void> lock = Completer<void>(); final Completer<void> lock = Completer<void>();
processManager.addCommand(FakeCommand( processManager.addCommand(FakeCommand(
command: const <String>['ssh', '-F', '/ssh_config', 'id', 'log_listener --clock Local'], command: const <String>[
'ssh',
'-F',
'/ssh_config',
'id',
'log_listener --clock Local'
],
stdout: exampleUtcLogs, stdout: exampleUtcLogs,
completer: lock, completer: lock,
)); ));
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
final DeviceLogReader reader = device.getLogReader(app: FuchsiaModulePackage(name: 'example_app')); final DeviceLogReader reader =
device.getLogReader(app: FuchsiaModulePackage(name: 'example_app'));
final List<String> logLines = <String>[]; final List<String> logLines = <String>[];
reader.logLines.listen((String line) { reader.logLines.listen((String line) {
logLines.add(line); logLines.add(line);
...@@ -376,13 +431,20 @@ void main() { ...@@ -376,13 +431,20 @@ void main() {
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 29, 45)), SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 29, 45)),
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig, ffx: ffx), FuchsiaArtifacts: () =>
FuchsiaArtifacts(sshConfig: sshConfig, ffx: ffx),
}); });
testUsingContext('can be parsed for all apps', () async { testUsingContext('can be parsed for all apps', () async {
final Completer<void> lock = Completer<void>(); final Completer<void> lock = Completer<void>();
processManager.addCommand(FakeCommand( processManager.addCommand(FakeCommand(
command: const <String>['ssh', '-F', '/ssh_config', 'id', 'log_listener --clock Local'], command: const <String>[
'ssh',
'-F',
'/ssh_config',
'id',
'log_listener --clock Local'
],
stdout: exampleUtcLogs, stdout: exampleUtcLogs,
completer: lock, completer: lock,
)); ));
...@@ -407,7 +469,8 @@ void main() { ...@@ -407,7 +469,8 @@ void main() {
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)), SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)),
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig, ffx: ffx), FuchsiaArtifacts: () =>
FuchsiaArtifacts(sshConfig: sshConfig, ffx: ffx),
}); });
}); });
}); });
...@@ -432,8 +495,8 @@ void main() { ...@@ -432,8 +495,8 @@ void main() {
expect(device.supportsScreenshot, false); expect(device.supportsScreenshot, false);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => FakePlatform( Platform: () => FakePlatform(
operatingSystem: 'windows', operatingSystem: 'windows',
), ),
}); });
test("takeScreenshot throws if file isn't .ppm", () async { test("takeScreenshot throws if file isn't .ppm", () async {
...@@ -441,43 +504,38 @@ void main() { ...@@ -441,43 +504,38 @@ void main() {
await expectLater( await expectLater(
() => device.takeScreenshot(globals.fs.file('file.invalid')), () => device.takeScreenshot(globals.fs.file('file.invalid')),
throwsA(isA<Exception>().having( throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(), (Exception exception) => exception.toString(),
'message', 'message',
contains('file.invalid must be a .ppm file') contains('file.invalid must be a .ppm file'))),
)),
); );
}); });
testUsingContext('takeScreenshot throws if screencap failed', () async { testUsingContext('takeScreenshot throws if screencap failed', () async {
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(command: <String>[
command: <String>[ 'ssh',
'ssh', '-F',
'-F', '/fuchsia/out/default/.ssh',
'/fuchsia/out/default/.ssh', '0.0.0.0',
'0.0.0.0', 'screencap > /tmp/screenshot.ppm',
'screencap > /tmp/screenshot.ppm', ], exitCode: 1, stderr: '<error-message>'));
],
exitCode: 1,
stderr: '<error-message>'
));
final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
await expectLater( await expectLater(
() => device.takeScreenshot(globals.fs.file('file.ppm')), () => device.takeScreenshot(globals.fs.file('file.ppm')),
throwsA(isA<Exception>().having( throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(), (Exception exception) => exception.toString(),
'message', 'message',
contains('Could not take a screenshot on device tester:\n<error-message>') contains(
)), 'Could not take a screenshot on device tester:\n<error-message>'))),
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => MemoryFileSystem.test(),
Platform: () => FakePlatform( Platform: () => FakePlatform(
environment: <String, String>{ environment: <String, String>{
'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
}, },
), ),
}); });
testUsingContext('takeScreenshot throws if scp failed', () async { testUsingContext('takeScreenshot throws if scp failed', () async {
...@@ -515,22 +573,24 @@ void main() { ...@@ -515,22 +573,24 @@ void main() {
await expectLater( await expectLater(
() => device.takeScreenshot(globals.fs.file('file.ppm')), () => device.takeScreenshot(globals.fs.file('file.ppm')),
throwsA(isA<Exception>().having( throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(), (Exception exception) => exception.toString(),
'message', 'message',
contains('Failed to copy screenshot from device:\n<error-message>') contains(
)), 'Failed to copy screenshot from device:\n<error-message>'))),
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => MemoryFileSystem.test(),
Platform: () => FakePlatform( Platform: () => FakePlatform(
environment: <String, String>{ environment: <String, String>{
'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
}, },
), ),
}); });
testUsingContext("takeScreenshot prints error if can't delete file from device", () async { testUsingContext(
"takeScreenshot prints error if can't delete file from device",
() async {
final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>[ command: <String>[
...@@ -565,16 +625,17 @@ void main() { ...@@ -565,16 +625,17 @@ void main() {
await device.takeScreenshot(globals.fs.file('file.ppm')); await device.takeScreenshot(globals.fs.file('file.ppm'));
expect( expect(
testLogger.errorText, testLogger.errorText,
contains('Failed to delete screenshot.ppm from the device:\n<error-message>'), contains(
'Failed to delete screenshot.ppm from the device:\n<error-message>'),
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => MemoryFileSystem.test(),
Platform: () => FakePlatform( Platform: () => FakePlatform(
environment: <String, String>{ environment: <String, String>{
'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
}, },
), ),
}, testOn: 'posix'); }, testOn: 'posix');
testUsingContext('takeScreenshot returns', () async { testUsingContext('takeScreenshot returns', () async {
...@@ -607,15 +668,16 @@ void main() { ...@@ -607,15 +668,16 @@ void main() {
], ],
)); ));
expect(() => device.takeScreenshot(globals.fs.file('file.ppm')), returnsNormally); expect(() => device.takeScreenshot(globals.fs.file('file.ppm')),
returnsNormally);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => MemoryFileSystem.test(),
Platform: () => FakePlatform( Platform: () => FakePlatform(
environment: <String, String>{ environment: <String, String>{
'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
}, },
), ),
}); });
}); });
...@@ -625,21 +687,36 @@ void main() { ...@@ -625,21 +687,36 @@ void main() {
setUp(() { setUp(() {
processManager = FakeProcessManager.empty(); processManager = FakeProcessManager.empty();
sshConfig = MemoryFileSystem.test().file('irrelevant')..writeAsStringSync('\n'); sshConfig = MemoryFileSystem.test().file('irrelevant')
..writeAsStringSync('\n');
}); });
testUsingContext('`unforward` prints stdout and stderr if ssh command failed', () async { testUsingContext(
'`unforward` prints stdout and stderr if ssh command failed', () async {
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>['ssh', '-F', '/irrelevant', '-O', 'cancel', '-vvv', '-L', '0:127.0.0.1:1', 'id'], command: <String>[
'ssh',
'-F',
'/irrelevant',
'-O',
'cancel',
'-vvv',
'-L',
'0:127.0.0.1:1',
'id'
],
exitCode: 1, exitCode: 1,
stdout: '<stdout>', stdout: '<stdout>',
stderr: '<stderr>', stderr: '<stderr>',
)); ));
await expectLater( await expectLater(
() => device.portForwarder.unforward(ForwardedPort(/*hostPort=*/ 0, /*devicePort=*/ 1)), () => device.portForwarder
throwsToolExit(message: 'Unforward command failed:\nstdout: <stdout>\nstderr: <stderr>'), .unforward(ForwardedPort(/*hostPort=*/ 0, /*devicePort=*/ 1)),
throwsToolExit(
message:
'Unforward command failed:\nstdout: <stdout>\nstderr: <stderr>'),
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
...@@ -647,26 +724,26 @@ void main() { ...@@ -647,26 +724,26 @@ void main() {
}); });
}); });
group('FuchsiaIsolateDiscoveryProtocol', () { group('FuchsiaIsolateDiscoveryProtocol', () {
Future<Uri> findUri(List<FlutterView> views, String expectedIsolateName) async { Future<Uri> findUri(
List<FlutterView> views, String expectedIsolateName) async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[ requests: <VmServiceExpectation>[
FakeVmServiceRequest( FakeVmServiceRequest(
method: kListViewsMethod, method: kListViewsMethod,
jsonResponse: <String, Object>{ jsonResponse: <String, Object>{
'views': <Object>[ 'views': <Object>[
for (FlutterView view in views) for (FlutterView view in views) view.toJson(),
view.toJson(),
], ],
}, },
), ),
], ],
httpAddress: Uri.parse('example'), httpAddress: Uri.parse('example'),
); );
final MockFuchsiaDevice fuchsiaDevice = MockFuchsiaDevice('123', const NoOpDevicePortForwarder(), false); final MockFuchsiaDevice fuchsiaDevice =
MockFuchsiaDevice('123', const NoOpDevicePortForwarder(), false);
final FuchsiaIsolateDiscoveryProtocol discoveryProtocol = final FuchsiaIsolateDiscoveryProtocol discoveryProtocol =
FuchsiaIsolateDiscoveryProtocol( FuchsiaIsolateDiscoveryProtocol(
fuchsiaDevice, fuchsiaDevice,
expectedIsolateName, expectedIsolateName,
(Uri uri) async => fakeVmServiceHost.vmService, (Uri uri) async => fakeVmServiceHost.vmService,
...@@ -676,7 +753,8 @@ void main() { ...@@ -676,7 +753,8 @@ void main() {
return discoveryProtocol.uri; return discoveryProtocol.uri;
} }
testUsingContext('can find flutter view with matching isolate name', () async { testUsingContext('can find flutter view with matching isolate name',
() async {
const String expectedIsolateName = 'foobar'; const String expectedIsolateName = 'foobar';
final Uri uri = await findUri(<FlutterView>[ final Uri uri = await findUri(<FlutterView>[
// no ui isolate. // no ui isolate.
...@@ -693,25 +771,29 @@ void main() { ...@@ -693,25 +771,29 @@ void main() {
FlutterView( FlutterView(
id: '3', id: '3',
uiIsolate: vm_service.Isolate.parse(<String, dynamic>{ uiIsolate: vm_service.Isolate.parse(<String, dynamic>{
...fakeIsolate.toJson(), ...fakeIsolate.toJson(),
'name': expectedIsolateName, 'name': expectedIsolateName,
}), }),
), ),
], expectedIsolateName); ], expectedIsolateName);
expect(uri.toString(), 'http://${InternetAddress.loopbackIPv4.address}:0/'); expect(
uri.toString(), 'http://${InternetAddress.loopbackIPv4.address}:0/');
}); });
testUsingContext('can handle flutter view without matching isolate name', () async { testUsingContext('can handle flutter view without matching isolate name',
() async {
const String expectedIsolateName = 'foobar'; const String expectedIsolateName = 'foobar';
final Future<Uri> uri = findUri(<FlutterView>[ final Future<Uri> uri = findUri(<FlutterView>[
// no ui isolate. // no ui isolate.
FlutterView(id: '1', uiIsolate: fakeIsolate), FlutterView(id: '1', uiIsolate: fakeIsolate),
// wrong name. // wrong name.
FlutterView(id: '2', uiIsolate: vm_service.Isolate.parse(<String, Object>{ FlutterView(
...fakeIsolate.toJson(), id: '2',
'name': 'wrong name', uiIsolate: vm_service.Isolate.parse(<String, Object>{
})), ...fakeIsolate.toJson(),
'name': 'wrong name',
})),
], expectedIsolateName); ], expectedIsolateName);
expect(uri, throwsException); expect(uri, throwsException);
...@@ -738,28 +820,32 @@ void main() { ...@@ -738,28 +820,32 @@ void main() {
platform: FakePlatform(), platform: FakePlatform(),
operatingSystemUtils: globals.os, operatingSystemUtils: globals.os,
); );
expect(artifacts.getArtifactPath( expect(
artifacts.getArtifactPath(
Artifact.fuchsiaFlutterRunner, Artifact.fuchsiaFlutterRunner,
platform: TargetPlatform.fuchsia_x64, platform: TargetPlatform.fuchsia_x64,
mode: BuildMode.debug, mode: BuildMode.debug,
), ),
contains('flutter_jit_runner'), contains('flutter_jit_runner'),
); );
expect(artifacts.getArtifactPath( expect(
artifacts.getArtifactPath(
Artifact.fuchsiaFlutterRunner, Artifact.fuchsiaFlutterRunner,
platform: TargetPlatform.fuchsia_x64, platform: TargetPlatform.fuchsia_x64,
mode: BuildMode.profile, mode: BuildMode.profile,
), ),
contains('flutter_aot_runner'), contains('flutter_aot_runner'),
); );
expect(artifacts.getArtifactPath( expect(
artifacts.getArtifactPath(
Artifact.fuchsiaFlutterRunner, Artifact.fuchsiaFlutterRunner,
platform: TargetPlatform.fuchsia_x64, platform: TargetPlatform.fuchsia_x64,
mode: BuildMode.release, mode: BuildMode.release,
), ),
contains('flutter_aot_product_runner'), contains('flutter_aot_product_runner'),
); );
expect(artifacts.getArtifactPath( expect(
artifacts.getArtifactPath(
Artifact.fuchsiaFlutterRunner, Artifact.fuchsiaFlutterRunner,
platform: TargetPlatform.fuchsia_x64, platform: TargetPlatform.fuchsia_x64,
mode: BuildMode.jitRelease, mode: BuildMode.jitRelease,
...@@ -773,7 +859,8 @@ void main() { ...@@ -773,7 +859,8 @@ void main() {
FakeProcessManager processManager; FakeProcessManager processManager;
setUp(() { setUp(() {
sshConfig = MemoryFileSystem.test().file('ssh_config')..writeAsStringSync('\n'); sshConfig = MemoryFileSystem.test().file('ssh_config')
..writeAsStringSync('\n');
processManager = FakeProcessManager.empty(); processManager = FakeProcessManager.empty();
}); });
...@@ -787,11 +874,15 @@ void main() { ...@@ -787,11 +874,15 @@ void main() {
FuchsiaSdk: () => FakeFuchsiaSdk(), FuchsiaSdk: () => FakeFuchsiaSdk(),
}); });
testUsingContext('returns what we get from the device on success', () async { testUsingContext('returns what we get from the device on success',
processManager.addCommand(const FakeCommand( () async {
command: <String>['ssh', '-F', '/ssh_config', '123', 'cat /pkgfs/packages/build-info/0/data/version'], processManager.addCommand(const FakeCommand(command: <String>[
stdout: 'version' 'ssh',
)); '-F',
'/ssh_config',
'123',
'cat /pkgfs/packages/build-info/0/data/version'
], stdout: 'version'));
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device'); final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
expect(await device.sdkNameAndVersion, equals('Fuchsia version')); expect(await device.sdkNameAndVersion, equals('Fuchsia version'));
...@@ -803,7 +894,13 @@ void main() { ...@@ -803,7 +894,13 @@ void main() {
testUsingContext('returns "Fuchsia" when device command fails', () async { testUsingContext('returns "Fuchsia" when device command fails', () async {
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', '123', 'cat /pkgfs/packages/build-info/0/data/version'], command: <String>[
'ssh',
'-F',
'/ssh_config',
'123',
'cat /pkgfs/packages/build-info/0/data/version'
],
exitCode: 1, exitCode: 1,
)); ));
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device'); final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
...@@ -815,9 +912,16 @@ void main() { ...@@ -815,9 +912,16 @@ void main() {
FuchsiaSdk: () => FakeFuchsiaSdk(), FuchsiaSdk: () => FakeFuchsiaSdk(),
}); });
testUsingContext('returns "Fuchsia" when device gives an empty result', () async { testUsingContext('returns "Fuchsia" when device gives an empty result',
() async {
processManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', '123', 'cat /pkgfs/packages/build-info/0/data/version'], command: <String>[
'ssh',
'-F',
'/ssh_config',
'123',
'cat /pkgfs/packages/build-info/0/data/version'
],
)); ));
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device'); final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
...@@ -855,7 +959,8 @@ class MockFuchsiaDevice extends Fake implements FuchsiaDevice { ...@@ -855,7 +959,8 @@ class MockFuchsiaDevice extends Fake implements FuchsiaDevice {
final DevicePortForwarder portForwarder; final DevicePortForwarder portForwarder;
@override @override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.fuchsia_arm64; Future<TargetPlatform> get targetPlatform async =>
TargetPlatform.fuchsia_arm64;
@override @override
String get name => 'fuchsia'; String get name => 'fuchsia';
...@@ -886,6 +991,16 @@ class FakeFuchsiaFfx implements FuchsiaFfx { ...@@ -886,6 +991,16 @@ class FakeFuchsiaFfx implements FuchsiaFfx {
Future<String> resolve(String deviceName) async { Future<String> resolve(String deviceName) async {
return '192.168.42.10'; return '192.168.42.10';
} }
@override
Future<String> sessionShow() async {
return null;
}
@override
Future<bool> sessionAdd(String url) async {
return false;
}
} }
class FakeFuchsiaSdk extends Fake implements FuchsiaSdk { class FakeFuchsiaSdk extends Fake implements FuchsiaSdk {
...@@ -894,10 +1009,10 @@ class FakeFuchsiaSdk extends Fake implements FuchsiaSdk { ...@@ -894,10 +1009,10 @@ class FakeFuchsiaSdk extends Fake implements FuchsiaSdk {
FuchsiaKernelCompiler compiler, FuchsiaKernelCompiler compiler,
FuchsiaFfx ffx, FuchsiaFfx ffx,
String devices, String devices,
}) : fuchsiaPM = pm, }) : fuchsiaPM = pm,
fuchsiaKernelCompiler = compiler, fuchsiaKernelCompiler = compiler,
fuchsiaFfx = ffx ?? FakeFuchsiaFfx(), fuchsiaFfx = ffx ?? FakeFuchsiaFfx(),
_devices = devices; _devices = devices;
@override @override
final FuchsiaPM fuchsiaPM; final FuchsiaPM fuchsiaPM;
...@@ -916,7 +1031,8 @@ class FakeFuchsiaSdk extends Fake implements FuchsiaSdk { ...@@ -916,7 +1031,8 @@ class FakeFuchsiaSdk extends Fake implements FuchsiaSdk {
} }
} }
class FakeDartDevelopmentService extends Fake implements DartDevelopmentService { class FakeDartDevelopmentService extends Fake
implements DartDevelopmentService {
@override @override
Future<void> startDartDevelopmentService( Future<void> startDartDevelopmentService(
Uri observatoryUri, { Uri observatoryUri, {
......
...@@ -43,14 +43,14 @@ void main() { ...@@ -43,14 +43,14 @@ void main() {
]); ]);
const String expected = '\n' const String expected = '\n'
'┌────────────────────────────────────────────────────────────────────────────\n' '┌───────────────────────────────────────────────────────────────────┐\n'
'│ General Info \n' '│ General Info │\n'
'│ [✓] App Name: flutter_gallery \n' '│ [✓] App Name: flutter_gallery │\n'
'│ [✓] Supported Platforms: android, ios, web, macos, linux, windows, fuchsia\n' '│ [✓] Supported Platforms: android, ios, web, macos, linux, windows │\n'
'│ [✓] Is Flutter Package: yes \n' '│ [✓] Is Flutter Package: yes │\n'
'│ [✓] Uses Material Design: yes \n' '│ [✓] Uses Material Design: yes │\n'
'│ [✓] Is Plugin: no \n' '│ [✓] Is Plugin: no │\n'
'└────────────────────────────────────────────────────────────────────────────\n'; '└───────────────────────────────────────────────────────────────────┘\n';
expect(loggerTest.statusText, contains(expected)); expect(loggerTest.statusText, contains(expected));
}); });
......
...@@ -252,9 +252,6 @@ class FakeStdin extends Fake implements Stdin { ...@@ -252,9 +252,6 @@ class FakeStdin extends Fake implements Stdin {
@override @override
bool echoMode = true; bool echoMode = true;
@override
bool echoNewlineMode = true;
@override @override
bool lineMode = true; bool lineMode = true;
......
...@@ -25,7 +25,8 @@ void main() { ...@@ -25,7 +25,8 @@ void main() {
} }
fuchsiaVmServiceConnectionFunction = fakeServiceFunction; fuchsiaVmServiceConnectionFunction = fakeServiceFunction;
final DartVm vm = await DartVm.connect(Uri.parse('http://this.whatever/ws')); final DartVm vm =
await DartVm.connect(Uri.parse('http://this.whatever/ws'));
expect(vm, isNot(null)); expect(vm, isNot(null));
await vm.stop(); await vm.stop();
expect(service.disposed, true); expect(service.disposed, true);
...@@ -79,12 +80,14 @@ void main() { ...@@ -79,12 +80,14 @@ void main() {
Uri uri, { Uri uri, {
Duration? timeout, Duration? timeout,
}) { }) {
fakeService.flutterListViews = vms.Response.parse(flutterViewCannedResponses); fakeService.flutterListViews =
vms.Response.parse(flutterViewCannedResponses);
return Future<vms.VmService>(() => fakeService); return Future<vms.VmService>(() => fakeService);
} }
fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction; fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction;
final DartVm vm = await DartVm.connect(Uri.parse('http://whatever.com/ws')); final DartVm vm =
await DartVm.connect(Uri.parse('http://whatever.com/ws'));
expect(vm, isNot(null)); expect(vm, isNot(null));
final List<FlutterView> views = await vm.getAllFlutterViews(); final List<FlutterView> views = await vm.getAllFlutterViews();
expect(views.length, 3); expect(views.length, 3);
...@@ -135,12 +138,14 @@ void main() { ...@@ -135,12 +138,14 @@ void main() {
Uri uri, { Uri uri, {
Duration? timeout, Duration? timeout,
}) { }) {
fakeService.flutterListViews = vms.Response.parse(flutterViewCannedResponses); fakeService.flutterListViews =
vms.Response.parse(flutterViewCannedResponses);
return Future<vms.VmService>(() => fakeService); return Future<vms.VmService>(() => fakeService);
} }
fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction; fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction;
final DartVm vm = await DartVm.connect(Uri.parse('http://whatever.com/ws')); final DartVm vm =
await DartVm.connect(Uri.parse('http://whatever.com/ws'));
expect(vm, isNot(null)); expect(vm, isNot(null));
final List<FlutterView> views = await vm.getAllFlutterViews(); final List<FlutterView> views = await vm.getAllFlutterViews();
expect(views.length, 3); expect(views.length, 3);
...@@ -156,7 +161,8 @@ void main() { ...@@ -156,7 +161,8 @@ void main() {
}); });
test('invalid flutter view missing ID', () async { test('invalid flutter view missing ID', () async {
final Map<String, dynamic> flutterViewCannedResponseMissingId = <String, dynamic>{ final Map<String, dynamic> flutterViewCannedResponseMissingId =
<String, dynamic>{
'views': <Map<String, dynamic>>[ 'views': <Map<String, dynamic>>[
// Valid flutter view. // Valid flutter view.
<String, dynamic>{ <String, dynamic>{
...@@ -182,12 +188,14 @@ void main() { ...@@ -182,12 +188,14 @@ void main() {
Uri uri, { Uri uri, {
Duration? timeout, Duration? timeout,
}) { }) {
fakeService.flutterListViews = vms.Response.parse(flutterViewCannedResponseMissingId); fakeService.flutterListViews =
vms.Response.parse(flutterViewCannedResponseMissingId);
return Future<vms.VmService>(() => fakeService); return Future<vms.VmService>(() => fakeService);
} }
fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction; fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction;
final DartVm vm = await DartVm.connect(Uri.parse('http://whatever.com/ws')); final DartVm vm =
await DartVm.connect(Uri.parse('http://whatever.com/ws'));
expect(vm, isNot(null)); expect(vm, isNot(null));
Future<void> failingFunction() async { Future<void> failingFunction() async {
await vm.getAllFlutterViews(); await vm.getAllFlutterViews();
...@@ -217,7 +225,7 @@ void main() { ...@@ -217,7 +225,7 @@ void main() {
'type': '@Isolate', 'type': '@Isolate',
'fixedId': 'true', 'fixedId': 'true',
'id': 'isolates/3', 'id': 'isolates/3',
'name': 'flutterBinary.cmx', 'name': 'flutterBinary.cm',
'number': '3', 'number': '3',
})!, })!,
vms.IsolateRef.parse(<String, dynamic>{ vms.IsolateRef.parse(<String, dynamic>{
...@@ -238,16 +246,20 @@ void main() { ...@@ -238,16 +246,20 @@ void main() {
} }
fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction; fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction;
final DartVm vm = await DartVm.connect(Uri.parse('http://whatever.com/ws')); final DartVm vm =
await DartVm.connect(Uri.parse('http://whatever.com/ws'));
expect(vm, isNot(null)); expect(vm, isNot(null));
final List<IsolateRef> matchingFlutterIsolates = await vm.getMainIsolatesByPattern('flutterBinary.cmx'); final List<IsolateRef> matchingFlutterIsolates =
await vm.getMainIsolatesByPattern('flutterBinary.cm');
expect(matchingFlutterIsolates.length, 1); expect(matchingFlutterIsolates.length, 1);
final List<IsolateRef> allIsolates = await vm.getMainIsolatesByPattern(''); final List<IsolateRef> allIsolates =
await vm.getMainIsolatesByPattern('');
expect(allIsolates.length, 4); expect(allIsolates.length, 4);
}); });
test('invalid flutter view missing ID', () async { test('invalid flutter view missing ID', () async {
final Map<String, dynamic> flutterViewCannedResponseMissingIsolateName = <String, dynamic>{ final Map<String, dynamic> flutterViewCannedResponseMissingIsolateName =
<String, dynamic>{
'views': <Map<String, dynamic>>[ 'views': <Map<String, dynamic>>[
// Missing isolate name. // Missing isolate name.
<String, dynamic>{ <String, dynamic>{
...@@ -267,12 +279,14 @@ void main() { ...@@ -267,12 +279,14 @@ void main() {
Uri uri, { Uri uri, {
Duration? timeout, Duration? timeout,
}) { }) {
fakeService.flutterListViews = vms.Response.parse(flutterViewCannedResponseMissingIsolateName); fakeService.flutterListViews =
vms.Response.parse(flutterViewCannedResponseMissingIsolateName);
return Future<vms.VmService>(() => fakeService); return Future<vms.VmService>(() => fakeService);
} }
fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction; fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction;
final DartVm vm = await DartVm.connect(Uri.parse('http://whatever.com/ws')); final DartVm vm =
await DartVm.connect(Uri.parse('http://whatever.com/ws'));
expect(vm, isNot(null)); expect(vm, isNot(null));
Future<void> failingFunction() async { Future<void> failingFunction() async {
await vm.getAllFlutterViews(); await vm.getAllFlutterViews();
...@@ -298,7 +312,8 @@ class FakeVmService extends Fake implements vms.VmService { ...@@ -298,7 +312,8 @@ class FakeVmService extends Fake implements vms.VmService {
} }
@override @override
Future<vms.Response> callMethod(String method, {String? isolateId, Map<String, dynamic>? args}) async { Future<vms.Response> callMethod(String method,
{String? isolateId, Map<String, dynamic>? args}) async {
if (method == '_flutter.listViews') { if (method == '_flutter.listViews') {
return flutterListViews!; return flutterListViews!;
} }
......
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