blob: d3d51147efdd07436decade3257347801045c485 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import '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: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>[],
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', () {
late MemoryFileSystem memoryFileSystem;
late File sshConfig;
late 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', () {
late File artifactFile;
late 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
''';
late FakeProcessManager processManager;
late File ffx;
late 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', () {
late 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', () {
late FakeProcessManager processManager;
late 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) async {},
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: ', () {
late File sshConfig;
late 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 FakeFuchsiaPM extends Fake implements FuchsiaPM {}
class FakeFuchsiaKernelCompiler extends Fake implements FuchsiaKernelCompiler {}
class FakeFuchsiaSdk extends Fake implements FuchsiaSdk {
FakeFuchsiaSdk({
FuchsiaPM? pm,
FuchsiaKernelCompiler? compiler,
FuchsiaFfx? ffx,
String? devices,
}) : fuchsiaPM = pm ?? FakeFuchsiaPM(),
fuchsiaKernelCompiler = compiler ?? FakeFuchsiaKernelCompiler(),
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;
}