mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

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.
1070 lines
34 KiB
Dart
1070 lines
34 KiB
Dart
// 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 'dart:async';
|
|
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/application_package.dart';
|
|
import 'package:flutter_tools/src/artifacts.dart';
|
|
import 'package:flutter_tools/src/base/dds.dart';
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:flutter_tools/src/base/time.dart';
|
|
import 'package:flutter_tools/src/build_info.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/device.dart';
|
|
import 'package:flutter_tools/src/device_port_forwarder.dart';
|
|
import 'package:flutter_tools/src/fuchsia/fuchsia_device.dart';
|
|
import 'package:flutter_tools/src/fuchsia/fuchsia_ffx.dart';
|
|
import 'package:flutter_tools/src/fuchsia/fuchsia_kernel_compiler.dart';
|
|
import 'package:flutter_tools/src/fuchsia/fuchsia_pm.dart';
|
|
import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart';
|
|
import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
import 'package:flutter_tools/src/project.dart';
|
|
import 'package:flutter_tools/src/vmservice.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:test/fake.dart';
|
|
import 'package:vm_service/vm_service.dart' as vm_service;
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
import '../../src/fake_vm_services.dart';
|
|
|
|
final vm_service.Isolate fakeIsolate = vm_service.Isolate(
|
|
id: '1',
|
|
pauseEvent: vm_service.Event(
|
|
kind: vm_service.EventKind.kResume,
|
|
timestamp: 0,
|
|
),
|
|
breakpoints: <vm_service.Breakpoint>[],
|
|
exceptionPauseMode: null,
|
|
libraries: <vm_service.LibraryRef>[],
|
|
livePorts: 0,
|
|
name: 'wrong name',
|
|
number: '1',
|
|
pauseOnExit: false,
|
|
runnable: true,
|
|
startTime: 0,
|
|
isSystemIsolate: false,
|
|
isolateFlags: <vm_service.IsolateFlag>[],
|
|
);
|
|
|
|
void main() {
|
|
group('fuchsia device', () {
|
|
MemoryFileSystem memoryFileSystem;
|
|
File sshConfig;
|
|
FakeProcessManager processManager;
|
|
|
|
setUp(() {
|
|
memoryFileSystem = MemoryFileSystem.test();
|
|
sshConfig = memoryFileSystem.file('ssh_config')..writeAsStringSync('\n');
|
|
processManager = FakeProcessManager.empty();
|
|
});
|
|
|
|
testWithoutContext('stores the requested id and name', () {
|
|
const String deviceId = 'e80::0000:a00a:f00f:2002/3';
|
|
const String name = 'halfbaked';
|
|
final FuchsiaDevice device = FuchsiaDevice(deviceId, name: name);
|
|
|
|
expect(device.id, deviceId);
|
|
expect(device.name, name);
|
|
});
|
|
|
|
testWithoutContext('supports all runtime modes besides jitRelease', () {
|
|
const String deviceId = 'e80::0000:a00a:f00f:2002/3';
|
|
const String name = 'halfbaked';
|
|
final FuchsiaDevice device = FuchsiaDevice(deviceId, name: name);
|
|
|
|
expect(device.supportsRuntimeMode(BuildMode.debug), true);
|
|
expect(device.supportsRuntimeMode(BuildMode.profile), true);
|
|
expect(device.supportsRuntimeMode(BuildMode.release), true);
|
|
expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
|
|
});
|
|
|
|
testWithoutContext('lists nothing when workflow cannot list devices',
|
|
() async {
|
|
final FakeFuchsiaWorkflow fuchsiaWorkflow =
|
|
FakeFuchsiaWorkflow(canListDevices: false);
|
|
final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
|
|
platform: FakePlatform(),
|
|
fuchsiaSdk: FakeFuchsiaSdk(devices: 'ignored'),
|
|
fuchsiaWorkflow: fuchsiaWorkflow,
|
|
logger: BufferLogger.test(),
|
|
);
|
|
|
|
expect(fuchsiaDevices.canListAnything, false);
|
|
expect(await fuchsiaDevices.pollingGetDevices(), isEmpty);
|
|
});
|
|
|
|
testWithoutContext('can parse ffx output for single device', () async {
|
|
final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow();
|
|
final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(
|
|
devices:
|
|
'2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel');
|
|
final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
|
|
platform: FakePlatform(environment: <String, String>{}),
|
|
fuchsiaSdk: fuchsiaSdk,
|
|
fuchsiaWorkflow: fuchsiaWorkflow,
|
|
logger: BufferLogger.test(),
|
|
);
|
|
|
|
final Device device = (await fuchsiaDevices.pollingGetDevices()).single;
|
|
|
|
expect(device.name, 'paper-pulp-bush-angel');
|
|
expect(device.id, '192.168.42.10');
|
|
});
|
|
|
|
testWithoutContext('can parse ffx output for multiple devices', () async {
|
|
final FakeFuchsiaWorkflow fuchsiaWorkflow = FakeFuchsiaWorkflow();
|
|
final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(
|
|
devices:
|
|
'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(
|
|
platform: FakePlatform(),
|
|
fuchsiaSdk: fuchsiaSdk,
|
|
fuchsiaWorkflow: fuchsiaWorkflow,
|
|
logger: BufferLogger.test(),
|
|
);
|
|
|
|
final List<Device> devices = await fuchsiaDevices.pollingGetDevices();
|
|
|
|
expect(devices.first.name, 'paper-pulp-bush-angel');
|
|
expect(devices.first.id, '192.168.42.10');
|
|
expect(devices.last.name, 'foo-bar-fiz-buzz');
|
|
expect(devices.last.id, '192.168.42.10');
|
|
});
|
|
|
|
testWithoutContext('can parse junk output from ffx', () async {
|
|
final FakeFuchsiaWorkflow fuchsiaWorkflow =
|
|
FakeFuchsiaWorkflow(canListDevices: false);
|
|
final FakeFuchsiaSdk fuchsiaSdk = FakeFuchsiaSdk(devices: 'junk');
|
|
final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
|
|
platform: FakePlatform(),
|
|
fuchsiaSdk: fuchsiaSdk,
|
|
fuchsiaWorkflow: fuchsiaWorkflow,
|
|
logger: BufferLogger.test(),
|
|
);
|
|
|
|
final List<Device> devices = await fuchsiaDevices.pollingGetDevices();
|
|
|
|
expect(devices, isEmpty);
|
|
});
|
|
|
|
testUsingContext('disposing device disposes the portForwarder', () async {
|
|
final FakePortForwarder portForwarder = FakePortForwarder();
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
device.portForwarder = portForwarder;
|
|
await device.dispose();
|
|
|
|
expect(portForwarder.disposed, true);
|
|
});
|
|
|
|
testWithoutContext('default capabilities', () async {
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
final FlutterProject project =
|
|
FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory);
|
|
memoryFileSystem.directory('fuchsia').createSync(recursive: true);
|
|
memoryFileSystem.file('pubspec.yaml').createSync();
|
|
|
|
expect(device.supportsHotReload, true);
|
|
expect(device.supportsHotRestart, false);
|
|
expect(device.supportsFlutterExit, false);
|
|
expect(device.isSupportedForProject(project), true);
|
|
});
|
|
|
|
test('is ephemeral', () {
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
|
|
expect(device.ephemeral, true);
|
|
});
|
|
|
|
testWithoutContext('supported for project', () async {
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
final FlutterProject project =
|
|
FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory);
|
|
memoryFileSystem.directory('fuchsia').createSync(recursive: true);
|
|
memoryFileSystem.file('pubspec.yaml').createSync();
|
|
|
|
expect(device.isSupportedForProject(project), true);
|
|
});
|
|
|
|
testWithoutContext('not supported for project', () async {
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
final FlutterProject project =
|
|
FlutterProject.fromDirectoryTest(memoryFileSystem.currentDirectory);
|
|
memoryFileSystem.file('pubspec.yaml').createSync();
|
|
|
|
expect(device.isSupportedForProject(project), false);
|
|
});
|
|
|
|
testUsingContext('targetPlatform does not throw when sshConfig is missing',
|
|
() async {
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
|
|
expect(await device.targetPlatform, TargetPlatform.fuchsia_arm64);
|
|
}, overrides: <Type, Generator>{
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('targetPlatform arm64 works', () async {
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>['ssh', '-F', '/ssh_config', '123', 'uname -m'],
|
|
stdout: 'aarch64',
|
|
));
|
|
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
expect(await device.targetPlatform, TargetPlatform.fuchsia_arm64);
|
|
}, overrides: <Type, Generator>{
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('targetPlatform x64 works', () async {
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>['ssh', '-F', '/ssh_config', '123', 'uname -m'],
|
|
stdout: 'x86_64',
|
|
));
|
|
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
expect(await device.targetPlatform, TargetPlatform.fuchsia_x64);
|
|
}, overrides: <Type, Generator>{
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('hostAddress parsing works', () async {
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'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');
|
|
expect(await device.hostAddress, 'fe80::8c6c:2fff:fe3d:c5e1%25ethp0003');
|
|
}, overrides: <Type, Generator>{
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('hostAddress parsing throws tool error on failure',
|
|
() async {
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/ssh_config',
|
|
'id',
|
|
r'echo $SSH_CONNECTION'
|
|
],
|
|
exitCode: 1,
|
|
));
|
|
|
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'device');
|
|
await expectLater(() => device.hostAddress, throwsToolExit());
|
|
}, overrides: <Type, Generator>{
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('hostAddress parsing throws tool error on empty response',
|
|
() async {
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/ssh_config',
|
|
'id',
|
|
r'echo $SSH_CONNECTION'
|
|
],
|
|
));
|
|
|
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'device');
|
|
expect(() async => device.hostAddress, throwsToolExit());
|
|
}, overrides: <Type, Generator>{
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
ProcessManager: () => processManager,
|
|
});
|
|
});
|
|
|
|
group('displays friendly error when', () {
|
|
File artifactFile;
|
|
FakeProcessManager processManager;
|
|
|
|
setUp(() {
|
|
processManager = FakeProcessManager.empty();
|
|
artifactFile = MemoryFileSystem.test().file('artifact');
|
|
});
|
|
|
|
testUsingContext('No vmservices found', () async {
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/artifact',
|
|
'id',
|
|
'find /hub -name vmservice-port'
|
|
],
|
|
));
|
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'device');
|
|
|
|
await expectLater(
|
|
device.servicePorts,
|
|
throwsToolExit(
|
|
message:
|
|
'No Dart Observatories found. Are you running a debug build?'));
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(
|
|
sshConfig: artifactFile,
|
|
ffx: artifactFile,
|
|
),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
});
|
|
|
|
group('device logs', () {
|
|
const String exampleUtcLogs = '''
|
|
[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:29:58][46257][46269][foo] INFO: Blah blah blah
|
|
[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:12][52580][52983][log] INFO: example_app.cm(flutter): Did thing this time
|
|
|
|
''';
|
|
FakeProcessManager processManager;
|
|
File ffx;
|
|
File sshConfig;
|
|
|
|
setUp(() {
|
|
processManager = FakeProcessManager.empty();
|
|
final FileSystem memoryFileSystem = MemoryFileSystem.test();
|
|
ffx = memoryFileSystem.file('ffx')..writeAsStringSync('\n');
|
|
sshConfig = memoryFileSystem.file('ssh_config')
|
|
..writeAsStringSync('\n');
|
|
});
|
|
|
|
testUsingContext('can be parsed for an app', () async {
|
|
final Completer<void> lock = Completer<void>();
|
|
processManager.addCommand(FakeCommand(
|
|
command: const <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/ssh_config',
|
|
'id',
|
|
'log_listener --clock Local'
|
|
],
|
|
stdout: exampleUtcLogs,
|
|
completer: lock,
|
|
));
|
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
|
final DeviceLogReader reader =
|
|
device.getLogReader(app: FuchsiaModulePackage(name: 'example_app'));
|
|
final List<String> logLines = <String>[];
|
|
reader.logLines.listen((String line) {
|
|
logLines.add(line);
|
|
if (logLines.length == 2) {
|
|
lock.complete();
|
|
}
|
|
});
|
|
expect(logLines, isEmpty);
|
|
|
|
await lock.future;
|
|
expect(logLines, <String>[
|
|
'[2018-11-09 01:27:45.000] Flutter: Error doing thing',
|
|
'[2018-11-09 01:30:12.000] Flutter: Did thing this time',
|
|
]);
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)),
|
|
FuchsiaArtifacts: () =>
|
|
FuchsiaArtifacts(sshConfig: sshConfig, ffx: ffx),
|
|
});
|
|
|
|
testUsingContext('cuts off prior logs', () async {
|
|
final Completer<void> lock = Completer<void>();
|
|
processManager.addCommand(FakeCommand(
|
|
command: const <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/ssh_config',
|
|
'id',
|
|
'log_listener --clock Local'
|
|
],
|
|
stdout: exampleUtcLogs,
|
|
completer: lock,
|
|
));
|
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
|
final DeviceLogReader reader =
|
|
device.getLogReader(app: FuchsiaModulePackage(name: 'example_app'));
|
|
final List<String> logLines = <String>[];
|
|
reader.logLines.listen((String line) {
|
|
logLines.add(line);
|
|
lock.complete();
|
|
});
|
|
expect(logLines, isEmpty);
|
|
|
|
await lock.future.timeout(const Duration(seconds: 1));
|
|
|
|
expect(logLines, <String>[
|
|
'[2018-11-09 01:30:12.000] Flutter: Did thing this time',
|
|
]);
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 29, 45)),
|
|
FuchsiaArtifacts: () =>
|
|
FuchsiaArtifacts(sshConfig: sshConfig, ffx: ffx),
|
|
});
|
|
|
|
testUsingContext('can be parsed for all apps', () async {
|
|
final Completer<void> lock = Completer<void>();
|
|
processManager.addCommand(FakeCommand(
|
|
command: const <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/ssh_config',
|
|
'id',
|
|
'log_listener --clock Local'
|
|
],
|
|
stdout: exampleUtcLogs,
|
|
completer: lock,
|
|
));
|
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
|
final DeviceLogReader reader = device.getLogReader();
|
|
final List<String> logLines = <String>[];
|
|
reader.logLines.listen((String line) {
|
|
logLines.add(line);
|
|
if (logLines.length == 3) {
|
|
lock.complete();
|
|
}
|
|
});
|
|
expect(logLines, isEmpty);
|
|
|
|
await lock.future.timeout(const Duration(seconds: 1));
|
|
|
|
expect(logLines, <String>[
|
|
'[2018-11-09 01:27:45.000] Flutter: Error doing thing',
|
|
'[2018-11-09 01:29:58.000] Flutter: Do thing',
|
|
'[2018-11-09 01:30:12.000] Flutter: Did thing this time',
|
|
]);
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)),
|
|
FuchsiaArtifacts: () =>
|
|
FuchsiaArtifacts(sshConfig: sshConfig, ffx: ffx),
|
|
});
|
|
});
|
|
});
|
|
|
|
group('screenshot', () {
|
|
FakeProcessManager processManager;
|
|
|
|
setUp(() {
|
|
processManager = FakeProcessManager.empty();
|
|
});
|
|
|
|
testUsingContext('is supported on posix platforms', () {
|
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
|
expect(device.supportsScreenshot, true);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => FakePlatform(),
|
|
});
|
|
|
|
testUsingContext('is not supported on Windows', () {
|
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
|
|
|
expect(device.supportsScreenshot, false);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => FakePlatform(
|
|
operatingSystem: 'windows',
|
|
),
|
|
});
|
|
|
|
test("takeScreenshot throws if file isn't .ppm", () async {
|
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
|
await expectLater(
|
|
() => device.takeScreenshot(globals.fs.file('file.invalid')),
|
|
throwsA(isA<Exception>().having(
|
|
(Exception exception) => exception.toString(),
|
|
'message',
|
|
contains('file.invalid must be a .ppm file'))),
|
|
);
|
|
});
|
|
|
|
testUsingContext('takeScreenshot throws if screencap failed', () async {
|
|
processManager.addCommand(const FakeCommand(command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/fuchsia/out/default/.ssh',
|
|
'0.0.0.0',
|
|
'screencap > /tmp/screenshot.ppm',
|
|
], exitCode: 1, stderr: '<error-message>'));
|
|
final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
|
|
|
|
await expectLater(
|
|
() => device.takeScreenshot(globals.fs.file('file.ppm')),
|
|
throwsA(isA<Exception>().having(
|
|
(Exception exception) => exception.toString(),
|
|
'message',
|
|
contains(
|
|
'Could not take a screenshot on device tester:\n<error-message>'))),
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
FileSystem: () => MemoryFileSystem.test(),
|
|
Platform: () => FakePlatform(
|
|
environment: <String, String>{
|
|
'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
|
|
},
|
|
),
|
|
});
|
|
|
|
testUsingContext('takeScreenshot throws if scp failed', () async {
|
|
final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/fuchsia/out/default/.ssh',
|
|
'0.0.0.0',
|
|
'screencap > /tmp/screenshot.ppm',
|
|
],
|
|
));
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'scp',
|
|
'-F',
|
|
'/fuchsia/out/default/.ssh',
|
|
'0.0.0.0:/tmp/screenshot.ppm',
|
|
'file.ppm',
|
|
],
|
|
exitCode: 1,
|
|
stderr: '<error-message>',
|
|
));
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/fuchsia/out/default/.ssh',
|
|
'0.0.0.0',
|
|
'rm /tmp/screenshot.ppm',
|
|
],
|
|
));
|
|
|
|
await expectLater(
|
|
() => device.takeScreenshot(globals.fs.file('file.ppm')),
|
|
throwsA(isA<Exception>().having(
|
|
(Exception exception) => exception.toString(),
|
|
'message',
|
|
contains(
|
|
'Failed to copy screenshot from device:\n<error-message>'))),
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
FileSystem: () => MemoryFileSystem.test(),
|
|
Platform: () => FakePlatform(
|
|
environment: <String, String>{
|
|
'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
|
|
},
|
|
),
|
|
});
|
|
|
|
testUsingContext(
|
|
"takeScreenshot prints error if can't delete file from device",
|
|
() async {
|
|
final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/fuchsia/out/default/.ssh',
|
|
'0.0.0.0',
|
|
'screencap > /tmp/screenshot.ppm',
|
|
],
|
|
));
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'scp',
|
|
'-F',
|
|
'/fuchsia/out/default/.ssh',
|
|
'0.0.0.0:/tmp/screenshot.ppm',
|
|
'file.ppm',
|
|
],
|
|
));
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/fuchsia/out/default/.ssh',
|
|
'0.0.0.0',
|
|
'rm /tmp/screenshot.ppm',
|
|
],
|
|
exitCode: 1,
|
|
stderr: '<error-message>',
|
|
));
|
|
|
|
await device.takeScreenshot(globals.fs.file('file.ppm'));
|
|
expect(
|
|
testLogger.errorText,
|
|
contains(
|
|
'Failed to delete screenshot.ppm from the device:\n<error-message>'),
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
FileSystem: () => MemoryFileSystem.test(),
|
|
Platform: () => FakePlatform(
|
|
environment: <String, String>{
|
|
'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
|
|
},
|
|
),
|
|
}, testOn: 'posix');
|
|
|
|
testUsingContext('takeScreenshot returns', () async {
|
|
final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/fuchsia/out/default/.ssh',
|
|
'0.0.0.0',
|
|
'screencap > /tmp/screenshot.ppm',
|
|
],
|
|
));
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'scp',
|
|
'-F',
|
|
'/fuchsia/out/default/.ssh',
|
|
'0.0.0.0:/tmp/screenshot.ppm',
|
|
'file.ppm',
|
|
],
|
|
));
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/fuchsia/out/default/.ssh',
|
|
'0.0.0.0',
|
|
'rm /tmp/screenshot.ppm',
|
|
],
|
|
));
|
|
|
|
expect(() => device.takeScreenshot(globals.fs.file('file.ppm')),
|
|
returnsNormally);
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
FileSystem: () => MemoryFileSystem.test(),
|
|
Platform: () => FakePlatform(
|
|
environment: <String, String>{
|
|
'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
|
|
},
|
|
),
|
|
});
|
|
});
|
|
|
|
group('portForwarder', () {
|
|
FakeProcessManager processManager;
|
|
File sshConfig;
|
|
|
|
setUp(() {
|
|
processManager = FakeProcessManager.empty();
|
|
sshConfig = MemoryFileSystem.test().file('irrelevant')
|
|
..writeAsStringSync('\n');
|
|
});
|
|
|
|
testUsingContext(
|
|
'`unforward` prints stdout and stderr if ssh command failed', () async {
|
|
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/irrelevant',
|
|
'-O',
|
|
'cancel',
|
|
'-vvv',
|
|
'-L',
|
|
'0:127.0.0.1:1',
|
|
'id'
|
|
],
|
|
exitCode: 1,
|
|
stdout: '<stdout>',
|
|
stderr: '<stderr>',
|
|
));
|
|
|
|
await expectLater(
|
|
() => device.portForwarder
|
|
.unforward(ForwardedPort(/*hostPort=*/ 0, /*devicePort=*/ 1)),
|
|
throwsToolExit(
|
|
message:
|
|
'Unforward command failed:\nstdout: <stdout>\nstderr: <stderr>'),
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
|
|
});
|
|
});
|
|
|
|
group('FuchsiaIsolateDiscoveryProtocol', () {
|
|
Future<Uri> findUri(
|
|
List<FlutterView> views, String expectedIsolateName) async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
jsonResponse: <String, Object>{
|
|
'views': <Object>[
|
|
for (FlutterView view in views) view.toJson(),
|
|
],
|
|
},
|
|
),
|
|
],
|
|
httpAddress: Uri.parse('example'),
|
|
);
|
|
final MockFuchsiaDevice fuchsiaDevice =
|
|
MockFuchsiaDevice('123', const NoOpDevicePortForwarder(), false);
|
|
final FuchsiaIsolateDiscoveryProtocol discoveryProtocol =
|
|
FuchsiaIsolateDiscoveryProtocol(
|
|
fuchsiaDevice,
|
|
expectedIsolateName,
|
|
(Uri uri) async => fakeVmServiceHost.vmService,
|
|
(Device device, Uri uri, bool enableServiceAuthCodes) => null,
|
|
true, // only poll once.
|
|
);
|
|
return discoveryProtocol.uri;
|
|
}
|
|
|
|
testUsingContext('can find flutter view with matching isolate name',
|
|
() async {
|
|
const String expectedIsolateName = 'foobar';
|
|
final Uri uri = await findUri(<FlutterView>[
|
|
// no ui isolate.
|
|
FlutterView(id: '1', uiIsolate: fakeIsolate),
|
|
// wrong name.
|
|
FlutterView(
|
|
id: '2',
|
|
uiIsolate: vm_service.Isolate.parse(<String, dynamic>{
|
|
...fakeIsolate.toJson(),
|
|
'name': 'Wrong name',
|
|
}),
|
|
),
|
|
// matching name.
|
|
FlutterView(
|
|
id: '3',
|
|
uiIsolate: vm_service.Isolate.parse(<String, dynamic>{
|
|
...fakeIsolate.toJson(),
|
|
'name': expectedIsolateName,
|
|
}),
|
|
),
|
|
], expectedIsolateName);
|
|
|
|
expect(
|
|
uri.toString(), 'http://${InternetAddress.loopbackIPv4.address}:0/');
|
|
});
|
|
|
|
testUsingContext('can handle flutter view without matching isolate name',
|
|
() async {
|
|
const String expectedIsolateName = 'foobar';
|
|
final Future<Uri> uri = findUri(<FlutterView>[
|
|
// no ui isolate.
|
|
FlutterView(id: '1', uiIsolate: fakeIsolate),
|
|
// wrong name.
|
|
FlutterView(
|
|
id: '2',
|
|
uiIsolate: vm_service.Isolate.parse(<String, Object>{
|
|
...fakeIsolate.toJson(),
|
|
'name': 'wrong name',
|
|
})),
|
|
], expectedIsolateName);
|
|
|
|
expect(uri, throwsException);
|
|
});
|
|
|
|
testUsingContext('can handle non flutter view', () async {
|
|
const String expectedIsolateName = 'foobar';
|
|
final Future<Uri> uri = findUri(<FlutterView>[
|
|
FlutterView(id: '1', uiIsolate: fakeIsolate), // no ui isolate.
|
|
], expectedIsolateName);
|
|
|
|
expect(uri, throwsException);
|
|
});
|
|
});
|
|
|
|
testUsingContext('Correct flutter runner', () async {
|
|
final Cache cache = Cache.test(
|
|
processManager: FakeProcessManager.any(),
|
|
);
|
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
|
final CachedArtifacts artifacts = CachedArtifacts(
|
|
cache: cache,
|
|
fileSystem: fileSystem,
|
|
platform: FakePlatform(),
|
|
operatingSystemUtils: globals.os,
|
|
);
|
|
expect(
|
|
artifacts.getArtifactPath(
|
|
Artifact.fuchsiaFlutterRunner,
|
|
platform: TargetPlatform.fuchsia_x64,
|
|
mode: BuildMode.debug,
|
|
),
|
|
contains('flutter_jit_runner'),
|
|
);
|
|
expect(
|
|
artifacts.getArtifactPath(
|
|
Artifact.fuchsiaFlutterRunner,
|
|
platform: TargetPlatform.fuchsia_x64,
|
|
mode: BuildMode.profile,
|
|
),
|
|
contains('flutter_aot_runner'),
|
|
);
|
|
expect(
|
|
artifacts.getArtifactPath(
|
|
Artifact.fuchsiaFlutterRunner,
|
|
platform: TargetPlatform.fuchsia_x64,
|
|
mode: BuildMode.release,
|
|
),
|
|
contains('flutter_aot_product_runner'),
|
|
);
|
|
expect(
|
|
artifacts.getArtifactPath(
|
|
Artifact.fuchsiaFlutterRunner,
|
|
platform: TargetPlatform.fuchsia_x64,
|
|
mode: BuildMode.jitRelease,
|
|
),
|
|
contains('flutter_jit_product_runner'),
|
|
);
|
|
});
|
|
|
|
group('sdkNameAndVersion: ', () {
|
|
File sshConfig;
|
|
FakeProcessManager processManager;
|
|
|
|
setUp(() {
|
|
sshConfig = MemoryFileSystem.test().file('ssh_config')
|
|
..writeAsStringSync('\n');
|
|
processManager = FakeProcessManager.empty();
|
|
});
|
|
|
|
testUsingContext('does not throw on non-existent ssh config', () async {
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
|
|
expect(await device.sdkNameAndVersion, equals('Fuchsia'));
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
});
|
|
|
|
testUsingContext('returns what we get from the device on success',
|
|
() async {
|
|
processManager.addCommand(const FakeCommand(command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/ssh_config',
|
|
'123',
|
|
'cat /pkgfs/packages/build-info/0/data/version'
|
|
], stdout: 'version'));
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
|
|
expect(await device.sdkNameAndVersion, equals('Fuchsia version'));
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
});
|
|
|
|
testUsingContext('returns "Fuchsia" when device command fails', () async {
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/ssh_config',
|
|
'123',
|
|
'cat /pkgfs/packages/build-info/0/data/version'
|
|
],
|
|
exitCode: 1,
|
|
));
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
|
|
expect(await device.sdkNameAndVersion, equals('Fuchsia'));
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
});
|
|
|
|
testUsingContext('returns "Fuchsia" when device gives an empty result',
|
|
() async {
|
|
processManager.addCommand(const FakeCommand(
|
|
command: <String>[
|
|
'ssh',
|
|
'-F',
|
|
'/ssh_config',
|
|
'123',
|
|
'cat /pkgfs/packages/build-info/0/data/version'
|
|
],
|
|
));
|
|
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device');
|
|
|
|
expect(await device.sdkNameAndVersion, equals('Fuchsia'));
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => processManager,
|
|
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
|
|
FuchsiaSdk: () => FakeFuchsiaSdk(),
|
|
});
|
|
});
|
|
}
|
|
|
|
class FuchsiaModulePackage extends ApplicationPackage {
|
|
FuchsiaModulePackage({@required this.name}) : super(id: name);
|
|
|
|
@override
|
|
final String name;
|
|
}
|
|
|
|
// Unfortunately Device, despite not being immutable, has an `operator ==`.
|
|
// Until we fix that, we have to also ignore related lints here.
|
|
// ignore: avoid_implementing_value_types
|
|
class MockFuchsiaDevice extends Fake implements FuchsiaDevice {
|
|
MockFuchsiaDevice(this.id, this.portForwarder, this._ipv6);
|
|
|
|
final bool _ipv6;
|
|
|
|
@override
|
|
bool get ipv6 => _ipv6;
|
|
|
|
@override
|
|
final String id;
|
|
|
|
@override
|
|
final DevicePortForwarder portForwarder;
|
|
|
|
@override
|
|
Future<TargetPlatform> get targetPlatform async =>
|
|
TargetPlatform.fuchsia_arm64;
|
|
|
|
@override
|
|
String get name => 'fuchsia';
|
|
|
|
@override
|
|
Future<List<int>> servicePorts() async => <int>[1];
|
|
|
|
@override
|
|
DartDevelopmentService get dds => FakeDartDevelopmentService();
|
|
}
|
|
|
|
class FakePortForwarder extends Fake implements DevicePortForwarder {
|
|
bool disposed = false;
|
|
|
|
@override
|
|
Future<void> dispose() async {
|
|
disposed = true;
|
|
}
|
|
}
|
|
|
|
class FakeFuchsiaFfx implements FuchsiaFfx {
|
|
@override
|
|
Future<List<String>> list({Duration timeout}) async {
|
|
return <String>['192.168.42.172 scare-cable-skip-ffx'];
|
|
}
|
|
|
|
@override
|
|
Future<String> resolve(String deviceName) async {
|
|
return '192.168.42.10';
|
|
}
|
|
|
|
@override
|
|
Future<String> sessionShow() async {
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
Future<bool> sessionAdd(String url) async {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class FakeFuchsiaSdk extends Fake implements FuchsiaSdk {
|
|
FakeFuchsiaSdk({
|
|
FuchsiaPM pm,
|
|
FuchsiaKernelCompiler compiler,
|
|
FuchsiaFfx ffx,
|
|
String devices,
|
|
}) : fuchsiaPM = pm,
|
|
fuchsiaKernelCompiler = compiler,
|
|
fuchsiaFfx = ffx ?? FakeFuchsiaFfx(),
|
|
_devices = devices;
|
|
|
|
@override
|
|
final FuchsiaPM fuchsiaPM;
|
|
|
|
@override
|
|
final FuchsiaKernelCompiler fuchsiaKernelCompiler;
|
|
|
|
@override
|
|
final FuchsiaFfx fuchsiaFfx;
|
|
|
|
final String _devices;
|
|
|
|
@override
|
|
Future<String> listDevices({Duration timeout}) async {
|
|
return _devices;
|
|
}
|
|
}
|
|
|
|
class FakeDartDevelopmentService extends Fake
|
|
implements DartDevelopmentService {
|
|
@override
|
|
Future<void> startDartDevelopmentService(
|
|
Uri observatoryUri, {
|
|
@required Logger logger,
|
|
int hostPort,
|
|
bool ipv6,
|
|
bool disableServiceAuthCodes,
|
|
bool cacheStartupProfile = false,
|
|
}) async {}
|
|
|
|
@override
|
|
Uri get uri => Uri.parse('example');
|
|
}
|
|
|
|
class FakeFuchsiaWorkflow implements FuchsiaWorkflow {
|
|
FakeFuchsiaWorkflow({
|
|
this.appliesToHostPlatform = true,
|
|
this.canLaunchDevices = true,
|
|
this.canListDevices = true,
|
|
this.canListEmulators = true,
|
|
});
|
|
|
|
@override
|
|
bool appliesToHostPlatform;
|
|
|
|
@override
|
|
bool canLaunchDevices;
|
|
|
|
@override
|
|
bool canListDevices;
|
|
|
|
@override
|
|
bool canListEmulators;
|
|
}
|