Ben Konyi 33b402d24c
Reland "Launch DDS from Dart SDK and prepare to serve DevTools from DDS (#146593)" (#152386)
This reverts commit 7cdc23b3e1bae2bc7bc2d1f34773eaa3629d4fcc.

The failure in the `native_assets_test` integration test on Windows was caused by the DevTools process not being shutdown by the `ColdRunner` when running the profile mode portion of the test. This resulted in the test being unable to clean up the project created by the test as DevTools was still holding onto a handle within the directory. This PR adds back the mistakenly removed DevTools shutdown logic in the `ColdRunner`.
2024-07-26 20:51:19 +00:00

1085 lines
35 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.
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/features.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/resident_runner.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';
import '../../src/fakes.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;
final Logger logger = FakeLogger();
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, logger: logger);
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, logger: logger);
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', logger: logger);
device.portForwarder = portForwarder;
await device.dispose();
expect(portForwarder.disposed, true);
});
testWithoutContext('default capabilities', () async {
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device', logger: logger);
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', logger: logger);
expect(device.ephemeral, true);
});
testWithoutContext('supported for project', () async {
final FuchsiaDevice device = FuchsiaDevice('123', name: 'device', logger: logger);
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', logger: logger);
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', logger: logger);
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', logger: logger);
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', logger: logger);
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', logger: logger);
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', logger: logger);
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', logger: logger);
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;
final Logger logger = FakeLogger();
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', logger: logger);
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;
final Logger logger = FakeLogger();
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', logger: logger);
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', logger: logger);
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', logger: logger);
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;
final Logger logger = FakeLogger();
setUp(() {
processManager = FakeProcessManager.empty();
});
testUsingContext('is supported on posix platforms', () {
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester', logger: logger);
expect(device.supportsScreenshot, true);
}, overrides: <Type, Generator>{
Platform: () => FakePlatform(),
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
});
testUsingContext('is not supported on Windows', () {
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester', logger: logger);
expect(device.supportsScreenshot, false);
}, overrides: <Type, Generator>{
Platform: () => FakePlatform(
operatingSystem: 'windows',
),
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
});
test("takeScreenshot throws if file isn't .ppm", () async {
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester', logger: logger);
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', logger: logger);
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',
},
),
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
});
testUsingContext('takeScreenshot throws if scp failed', () async {
final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester', logger: logger);
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',
},
),
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
});
testUsingContext(
"takeScreenshot prints error if can't delete file from device",
() async {
final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester', logger: logger);
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',
},
),
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
}, testOn: 'posix');
testUsingContext('takeScreenshot returns', () async {
final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester', logger: logger);
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',
},
),
FeatureFlags: () => TestFeatureFlags(isFuchsiaEnabled: true),
});
});
group('portForwarder', () {
late FakeProcessManager processManager;
late File sshConfig;
final Logger logger = FakeLogger();
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', logger: logger);
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 (final 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;
final Logger logger = FakeLogger();
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', logger: logger);
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', logger: logger);
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', logger: logger);
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', logger: logger);
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;
}
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 vmServiceUri, {
FlutterDevice? device,
int? ddsPort,
bool? ipv6,
bool? disableServiceAuthCodes,
bool cacheStartupProfile = false,
bool enableDevTools = false,
String? google3WorkspaceRoot,
Uri? devToolsServerAddress,
}) 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;
}