| // 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; |
| } |