| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // @dart = 2.8 |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| |
| 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/common.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/os.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_forwader.dart'; |
| import 'package:flutter_tools/src/fuchsia/amber_ctl.dart'; |
| import 'package:flutter_tools/src/fuchsia/application_package.dart'; |
| import 'package:flutter_tools/src/fuchsia/fuchsia_dev_finder.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/fuchsia/tiles_ctl.dart'; |
| import 'package:flutter_tools/src/globals.dart' as globals; |
| import 'package:flutter_tools/src/project.dart'; |
| import 'package:flutter_tools/src/vmservice.dart'; |
| import 'package:meta/meta.dart'; |
| import 'package:mockito/mockito.dart'; |
| import 'package:vm_service/vm_service.dart' as vm_service; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| |
| final vm_service.Isolate fakeIsolate = vm_service.Isolate( |
| id: '1', |
| pauseEvent: vm_service.Event( |
| kind: vm_service.EventKind.kResume, |
| timestamp: 0 |
| ), |
| breakpoints: <vm_service.Breakpoint>[], |
| exceptionPauseMode: null, |
| libraries: <vm_service.LibraryRef>[], |
| livePorts: 0, |
| name: 'wrong name', |
| number: '1', |
| pauseOnExit: false, |
| runnable: true, |
| startTime: 0, |
| isSystemIsolate: false, |
| isolateFlags: <vm_service.IsolateFlag>[], |
| ); |
| |
| void main() { |
| group('fuchsia device', () { |
| MemoryFileSystem memoryFileSystem; |
| File sshConfig; |
| |
| setUp(() { |
| memoryFileSystem = MemoryFileSystem.test(); |
| sshConfig = memoryFileSystem.file('ssh_config')..writeAsStringSync('\n'); |
| }); |
| |
| 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 MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); |
| final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( |
| platform: FakePlatform(operatingSystem: 'linux'), |
| fuchsiaSdk: null, |
| fuchsiaWorkflow: fuchsiaWorkflow, |
| logger: BufferLogger.test(), |
| ); |
| when(fuchsiaWorkflow.canListDevices).thenReturn(false); |
| |
| expect(fuchsiaDevices.canListAnything, false); |
| expect(await fuchsiaDevices.pollingGetDevices(), isEmpty); |
| }); |
| |
| testWithoutContext('can parse ffx output for single device', () async { |
| final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); |
| final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk(); |
| final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( |
| platform: FakePlatform(operatingSystem: 'linux', environment: <String, String>{},), |
| fuchsiaSdk: fuchsiaSdk, |
| fuchsiaWorkflow: fuchsiaWorkflow, |
| logger: BufferLogger.test(), |
| ); |
| when(fuchsiaWorkflow.shouldUseDeviceFinder).thenReturn(false); |
| when(fuchsiaWorkflow.canListDevices).thenReturn(true); |
| when(fuchsiaSdk.listDevices(useDeviceFinder: false)).thenAnswer((Invocation invocation) async { |
| return '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel'; |
| }); |
| |
| final Device device = (await fuchsiaDevices.pollingGetDevices()).single; |
| |
| expect(device.name, 'paper-pulp-bush-angel'); |
| expect(device.id, '192.168.42.10'); |
| }); |
| |
| testWithoutContext('can parse device-finder output for single device', () async { |
| final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); |
| final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk(); |
| final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( |
| platform: FakePlatform(operatingSystem: 'linux', environment: <String, String>{'FUCHSIA_DISABLED_ffx_discovery': '1'},), |
| fuchsiaSdk: fuchsiaSdk, |
| fuchsiaWorkflow: fuchsiaWorkflow, |
| logger: BufferLogger.test(), |
| ); |
| when(fuchsiaWorkflow.shouldUseDeviceFinder).thenReturn(true); |
| when(fuchsiaWorkflow.canListDevices).thenReturn(true); |
| when(fuchsiaSdk.listDevices(useDeviceFinder: true)).thenAnswer((Invocation invocation) async { |
| return '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel'; |
| }); |
| |
| final Device device = (await fuchsiaDevices.pollingGetDevices()).single; |
| |
| expect(device.name, 'paper-pulp-bush-angel'); |
| expect(device.id, '192.168.11.999'); |
| }); |
| |
| testWithoutContext('can parse ffx output for multiple devices', () async { |
| final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); |
| final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk(); |
| final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( |
| platform: FakePlatform(operatingSystem: 'linux'), |
| fuchsiaSdk: fuchsiaSdk, |
| fuchsiaWorkflow: fuchsiaWorkflow, |
| logger: BufferLogger.test(), |
| ); |
| when(fuchsiaWorkflow.shouldUseDeviceFinder).thenReturn(false); |
| when(fuchsiaWorkflow.canListDevices).thenReturn(true); |
| when(fuchsiaSdk.listDevices(useDeviceFinder: false)).thenAnswer((Invocation invocation) async { |
| return '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 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 device-finder output for multiple devices', () async { |
| final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); |
| final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk(); |
| final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( |
| platform: FakePlatform(operatingSystem: 'linux'), |
| fuchsiaSdk: fuchsiaSdk, |
| fuchsiaWorkflow: fuchsiaWorkflow, |
| logger: BufferLogger.test(), |
| ); |
| when(fuchsiaWorkflow.shouldUseDeviceFinder).thenReturn(true); |
| when(fuchsiaWorkflow.canListDevices).thenReturn(true); |
| when(fuchsiaSdk.listDevices(useDeviceFinder: true)).thenAnswer((Invocation invocation) async { |
| return '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 List<Device> devices = await fuchsiaDevices.pollingGetDevices(); |
| |
| expect(devices.first.name, 'paper-pulp-bush-angel'); |
| expect(devices.first.id, '192.168.11.999'); |
| expect(devices.last.name, 'foo-bar-fiz-buzz'); |
| expect(devices.last.id, '192.168.11.999'); |
| }); |
| |
| testWithoutContext('can parse junk output from ffx', () async { |
| final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); |
| final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk(); |
| final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( |
| platform: FakePlatform(operatingSystem: 'linux'), |
| fuchsiaSdk: fuchsiaSdk, |
| fuchsiaWorkflow: fuchsiaWorkflow, |
| logger: BufferLogger.test(), |
| ); |
| when(fuchsiaWorkflow.shouldUseDeviceFinder).thenReturn(false); |
| when(fuchsiaWorkflow.canListDevices).thenReturn(true); |
| when(fuchsiaSdk.listDevices()).thenAnswer((Invocation invocation) async { |
| return 'junk'; |
| }); |
| |
| final List<Device> devices = await fuchsiaDevices.pollingGetDevices(); |
| |
| expect(devices, isEmpty); |
| }); |
| |
| testWithoutContext('can parse junk output from device-finder', () async { |
| final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow(); |
| final MockFuchsiaSdk fuchsiaSdk = MockFuchsiaSdk(); |
| final FuchsiaDevices fuchsiaDevices = FuchsiaDevices( |
| platform: FakePlatform(operatingSystem: 'linux'), |
| fuchsiaSdk: fuchsiaSdk, |
| fuchsiaWorkflow: fuchsiaWorkflow, |
| logger: BufferLogger.test(), |
| ); |
| when(fuchsiaWorkflow.shouldUseDeviceFinder).thenReturn(true); |
| when(fuchsiaWorkflow.canListDevices).thenReturn(true); |
| when(fuchsiaSdk.listDevices(useDeviceFinder: true)).thenAnswer((Invocation invocation) async { |
| return 'junk'; |
| }); |
| |
| final List<Device> devices = await fuchsiaDevices.pollingGetDevices(); |
| |
| expect(devices, isEmpty); |
| }); |
| |
| testUsingContext('disposing device disposes the portForwarder', () async { |
| final MockPortForwarder mockPortForwarder = MockPortForwarder(); |
| final FuchsiaDevice device = FuchsiaDevice('123'); |
| device.portForwarder = mockPortForwarder; |
| await device.dispose(); |
| verify(mockPortForwarder.dispose()).called(1); |
| }); |
| |
| testWithoutContext('default capabilities', () async { |
| final FuchsiaDevice device = FuchsiaDevice('123'); |
| 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'); |
| |
| expect(device.ephemeral, true); |
| }); |
| |
| testWithoutContext('supported for project', () async { |
| final FuchsiaDevice device = FuchsiaDevice('123'); |
| 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'); |
| 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'); |
| |
| expect(await device.targetPlatform, TargetPlatform.fuchsia_arm64); |
| }, overrides: <Type, Generator>{ |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: null), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| ProcessManager: () => MockProcessManager(), |
| }); |
| |
| testUsingContext('targetPlatform arm64 works', () async { |
| when(globals.processManager.run(any)).thenAnswer((Invocation _) async { |
| return ProcessResult(1, 0, 'aarch64', ''); |
| }); |
| final FuchsiaDevice device = FuchsiaDevice('123'); |
| expect(await device.targetPlatform, TargetPlatform.fuchsia_arm64); |
| }, overrides: <Type, Generator>{ |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| ProcessManager: () => MockProcessManager(), |
| }); |
| |
| testUsingContext('targetPlatform x64 works', () async { |
| when(globals.processManager.run(any)).thenAnswer((Invocation _) async { |
| return ProcessResult(1, 0, 'x86_64', ''); |
| }); |
| final FuchsiaDevice device = FuchsiaDevice('123'); |
| expect(await device.targetPlatform, TargetPlatform.fuchsia_x64); |
| }, overrides: <Type, Generator>{ |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| ProcessManager: () => MockProcessManager(), |
| }); |
| |
| testUsingContext('hostAddress parsing works', () async { |
| when(globals.processManager.run(any)).thenAnswer((Invocation _) async { |
| return ProcessResult( |
| 1, |
| 0, |
| 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22', |
| '', |
| ); |
| }); |
| final FuchsiaDevice device = FuchsiaDevice('id'); |
| expect(await device.hostAddress, 'fe80::8c6c:2fff:fe3d:c5e1%25ethp0003'); |
| }, overrides: <Type, Generator>{ |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| ProcessManager: () => MockProcessManager(), |
| }); |
| |
| testUsingContext('hostAddress parsing throws tool error on failure', () async { |
| when(globals.processManager.run(any)).thenAnswer((Invocation _) async { |
| return ProcessResult(1, 1, '', ''); |
| }); |
| final FuchsiaDevice device = FuchsiaDevice('id'); |
| expect(() async => device.hostAddress, throwsToolExit()); |
| }, overrides: <Type, Generator>{ |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| ProcessManager: () => MockProcessManager(), |
| }); |
| |
| testUsingContext('hostAddress parsing throws tool error on empty response', () async { |
| when(globals.processManager.run(any)).thenAnswer((Invocation _) async { |
| return ProcessResult(1, 0, '', ''); |
| }); |
| final FuchsiaDevice device = FuchsiaDevice('id'); |
| expect(() async => device.hostAddress, throwsToolExit()); |
| }, overrides: <Type, Generator>{ |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| ProcessManager: () => MockProcessManager(), |
| }); |
| }); |
| |
| group('displays friendly error when', () { |
| MockProcessManager mockProcessManager; |
| MockProcessResult mockProcessResult; |
| File artifactFile; |
| MockProcessManager emptyStdoutProcessManager; |
| MockProcessResult emptyStdoutProcessResult; |
| |
| setUp(() { |
| mockProcessManager = MockProcessManager(); |
| mockProcessResult = MockProcessResult(); |
| artifactFile = MemoryFileSystem.test().file('artifact'); |
| when(mockProcessManager.run( |
| any, |
| environment: anyNamed('environment'), |
| workingDirectory: anyNamed('workingDirectory'), |
| )).thenAnswer((Invocation invocation) => |
| Future<ProcessResult>.value(mockProcessResult)); |
| when(mockProcessResult.exitCode).thenReturn(1); |
| when<String>(mockProcessResult.stdout as String).thenReturn(''); |
| when<String>(mockProcessResult.stderr as String).thenReturn(''); |
| |
| emptyStdoutProcessManager = MockProcessManager(); |
| emptyStdoutProcessResult = MockProcessResult(); |
| when(emptyStdoutProcessManager.run( |
| any, |
| environment: anyNamed('environment'), |
| workingDirectory: anyNamed('workingDirectory'), |
| )).thenAnswer((Invocation invocation) => |
| Future<ProcessResult>.value(emptyStdoutProcessResult)); |
| when(emptyStdoutProcessResult.exitCode).thenReturn(0); |
| when<String>(emptyStdoutProcessResult.stdout as String).thenReturn(''); |
| when<String>(emptyStdoutProcessResult.stderr as String).thenReturn(''); |
| }); |
| |
| testUsingContext('No vmservices found', () async { |
| final FuchsiaDevice device = FuchsiaDevice('id'); |
| ToolExit toolExit; |
| try { |
| await device.servicePorts(); |
| } on ToolExit catch (err) { |
| toolExit = err; |
| } |
| expect( |
| toolExit.message, |
| contains( |
| 'No Dart Observatories found. Are you running a debug build?')); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => emptyStdoutProcessManager, |
| FuchsiaArtifacts: () => FuchsiaArtifacts( |
| sshConfig: artifactFile, |
| devFinder: artifactFile, |
| ffx: artifactFile, |
| ), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| }); |
| |
| group('device logs', () { |
| const String exampleUtcLogs = ''' |
| [2018-11-09 01:27:45][3][297950920][log] INFO: example_app.cmx(flutter): Error doing thing |
| [2018-11-09 01:27: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.cmx(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.cmx(flutter): Did thing this time |
| |
| '''; |
| MockProcessManager mockProcessManager; |
| MockProcess mockProcess; |
| Completer<int> exitCode; |
| StreamController<List<int>> stdout; |
| StreamController<List<int>> stderr; |
| File devFinder; |
| File ffx; |
| File sshConfig; |
| |
| setUp(() { |
| mockProcessManager = MockProcessManager(); |
| mockProcess = MockProcess(); |
| stdout = StreamController<List<int>>(sync: true); |
| stderr = StreamController<List<int>>(sync: true); |
| exitCode = Completer<int>(); |
| when(mockProcessManager.start(any)) |
| .thenAnswer((Invocation _) => Future<Process>.value(mockProcess)); |
| when(mockProcess.exitCode).thenAnswer((Invocation _) => exitCode.future); |
| when(mockProcess.stdout).thenAnswer((Invocation _) => stdout.stream); |
| when(mockProcess.stderr).thenAnswer((Invocation _) => stderr.stream); |
| final FileSystem memoryFileSystem = MemoryFileSystem.test(); |
| devFinder = memoryFileSystem.file('device-finder')..writeAsStringSync('\n'); |
| ffx = memoryFileSystem.file('ffx')..writeAsStringSync('\n'); |
| sshConfig = memoryFileSystem.file('ssh_config')..writeAsStringSync('\n'); |
| }); |
| |
| tearDown(() { |
| exitCode.complete(0); |
| }); |
| |
| testUsingContext('can be parsed for an app', () async { |
| final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); |
| final DeviceLogReader reader = device.getLogReader( |
| app: FuchsiaModulePackage(name: 'example_app')); |
| final List<String> logLines = <String>[]; |
| final Completer<void> lock = Completer<void>(); |
| reader.logLines.listen((String line) { |
| logLines.add(line); |
| if (logLines.length == 2) { |
| lock.complete(); |
| } |
| }); |
| expect(logLines, isEmpty); |
| |
| stdout.add(utf8.encode(exampleUtcLogs)); |
| await stdout.close(); |
| 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:30:12.000] Flutter: Did thing this time', |
| ]); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockProcessManager, |
| SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)), |
| FuchsiaArtifacts: () => |
| FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: ffx), |
| }); |
| |
| testUsingContext('cuts off prior logs', () async { |
| final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); |
| final DeviceLogReader reader = device.getLogReader( |
| app: FuchsiaModulePackage(name: 'example_app')); |
| final List<String> logLines = <String>[]; |
| final Completer<void> lock = Completer<void>(); |
| reader.logLines.listen((String line) { |
| logLines.add(line); |
| lock.complete(); |
| }); |
| expect(logLines, isEmpty); |
| |
| stdout.add(utf8.encode(exampleUtcLogs)); |
| await stdout.close(); |
| 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: () => mockProcessManager, |
| SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 29, 45)), |
| FuchsiaArtifacts: () => |
| FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: ffx), |
| }); |
| |
| testUsingContext('can be parsed for all apps', () async { |
| final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); |
| final DeviceLogReader reader = device.getLogReader(); |
| final List<String> logLines = <String>[]; |
| final Completer<void> lock = Completer<void>(); |
| reader.logLines.listen((String line) { |
| logLines.add(line); |
| if (logLines.length == 3) { |
| lock.complete(); |
| } |
| }); |
| expect(logLines, isEmpty); |
| |
| stdout.add(utf8.encode(exampleUtcLogs)); |
| await stdout.close(); |
| 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: () => mockProcessManager, |
| SystemClock: () => SystemClock.fixed(DateTime(2018, 11, 9, 1, 25, 45)), |
| FuchsiaArtifacts: () => |
| FuchsiaArtifacts(devFinder: devFinder, sshConfig: sshConfig, ffx: ffx), |
| }); |
| }); |
| }); |
| |
| group('screenshot', () { |
| testUsingContext('is supported on posix platforms', () { |
| final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); |
| expect(device.supportsScreenshot, true); |
| }, overrides: <Type, Generator>{ |
| Platform: () => FakePlatform( |
| operatingSystem: 'linux', |
| ), |
| }); |
| |
| 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(equals('file.invalid must be a .ppm file')), |
| ); |
| }); |
| |
| testUsingContext('takeScreenshot throws if screencap failed', () async { |
| final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); |
| |
| when(globals.processManager.run( |
| const <String>[ |
| 'ssh', |
| '-F', |
| '/fuchsia/out/default/.ssh', |
| '0.0.0.0', |
| 'screencap > /tmp/screenshot.ppm', |
| ], |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((_) async => ProcessResult(0, 1, '', '<error-message>')); |
| |
| await expectLater( |
| () => device.takeScreenshot(globals.fs.file('file.ppm')), |
| throwsA(equals('Could not take a screenshot on device tester:\n<error-message>')), |
| ); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => MockProcessManager(), |
| FileSystem: () => MemoryFileSystem.test(), |
| Platform: () => FakePlatform( |
| environment: <String, String>{ |
| 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', |
| }, |
| operatingSystem: 'linux', |
| ), |
| }); |
| |
| testUsingContext('takeScreenshot throws if scp failed', () async { |
| final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); |
| |
| when(globals.processManager.run( |
| const <String>[ |
| 'ssh', |
| '-F', |
| '/fuchsia/out/default/.ssh', |
| '0.0.0.0', |
| 'screencap > /tmp/screenshot.ppm', |
| ], |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); |
| |
| when(globals.processManager.run( |
| const <String>[ |
| 'scp', |
| '-F', |
| '/fuchsia/out/default/.ssh', |
| '0.0.0.0:/tmp/screenshot.ppm', |
| 'file.ppm', |
| ], |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((_) async => ProcessResult(0, 1, '', '<error-message>')); |
| |
| when(globals.processManager.run( |
| const <String>[ |
| 'ssh', |
| '-F', |
| '/fuchsia/out/default/.ssh', |
| '0.0.0.0', |
| 'rm /tmp/screenshot.ppm', |
| ], |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); |
| |
| await expectLater( |
| () => device.takeScreenshot(globals.fs.file('file.ppm')), |
| throwsA(equals('Failed to copy screenshot from device:\n<error-message>')), |
| ); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => MockProcessManager(), |
| FileSystem: () => MemoryFileSystem.test(), |
| Platform: () => FakePlatform( |
| environment: <String, String>{ |
| 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', |
| }, |
| operatingSystem: 'linux', |
| ), |
| }); |
| |
| testUsingContext("takeScreenshot prints error if can't delete file from device", () async { |
| final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); |
| |
| when(globals.processManager.run( |
| const <String>[ |
| 'ssh', |
| '-F', |
| '/fuchsia/out/default/.ssh', |
| '0.0.0.0', |
| 'screencap > /tmp/screenshot.ppm', |
| ], |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); |
| |
| when(globals.processManager.run( |
| const <String>[ |
| 'scp', |
| '-F', |
| '/fuchsia/out/default/.ssh', |
| '0.0.0.0:/tmp/screenshot.ppm', |
| 'file.ppm', |
| ], |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); |
| |
| when(globals.processManager.run( |
| const <String>[ |
| 'ssh', |
| '-F', |
| '/fuchsia/out/default/.ssh', |
| '0.0.0.0', |
| 'rm /tmp/screenshot.ppm', |
| ], |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((_) async => ProcessResult(0, 1, '', '<error-message>')); |
| |
| try { |
| await device.takeScreenshot(globals.fs.file('file.ppm')); |
| } on Exception { |
| assert(false); |
| } |
| expect( |
| testLogger.errorText, |
| contains('Failed to delete screenshot.ppm from the device:\n<error-message>'), |
| ); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => MockProcessManager(), |
| FileSystem: () => MemoryFileSystem.test(), |
| Platform: () => FakePlatform( |
| environment: <String, String>{ |
| 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', |
| }, |
| operatingSystem: 'linux', |
| ), |
| }, testOn: 'posix'); |
| |
| testUsingContext('takeScreenshot returns', () async { |
| final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); |
| |
| when(globals.processManager.run( |
| const <String>[ |
| 'ssh', |
| '-F', |
| '/fuchsia/out/default/.ssh', |
| '0.0.0.0', |
| 'screencap > /tmp/screenshot.ppm', |
| ], |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); |
| |
| when(globals.processManager.run( |
| const <String>[ |
| 'scp', |
| '-F', |
| '/fuchsia/out/default/.ssh', |
| '0.0.0.0:/tmp/screenshot.ppm', |
| 'file.ppm', |
| ], |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); |
| |
| when(globals.processManager.run( |
| const <String>[ |
| 'ssh', |
| '-F', |
| '/fuchsia/out/default/.ssh', |
| '0.0.0.0', |
| 'rm /tmp/screenshot.ppm', |
| ], |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((_) async => ProcessResult(0, 0, '', '')); |
| |
| expect(() async => device.takeScreenshot(globals.fs.file('file.ppm')), |
| returnsNormally); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => MockProcessManager(), |
| FileSystem: () => MemoryFileSystem.test(), |
| Platform: () => FakePlatform( |
| environment: <String, String>{ |
| 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', |
| }, |
| operatingSystem: 'linux', |
| ), |
| }); |
| }); |
| |
| group('portForwarder', () { |
| MockProcessManager mockProcessManager; |
| File sshConfig; |
| |
| setUp(() { |
| mockProcessManager = MockProcessManager(); |
| 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'); |
| |
| final MockProcessResult mockFailureProcessResult = MockProcessResult(); |
| when(mockFailureProcessResult.exitCode).thenReturn(1); |
| when<String>(mockFailureProcessResult.stdout as String).thenReturn('<stdout>'); |
| when<String>(mockFailureProcessResult.stderr as String).thenReturn('<stderr>'); |
| when(mockProcessManager.run(<String>[ |
| 'ssh', |
| '-F', |
| sshConfig.absolute.path, |
| '-O', |
| 'cancel', |
| '-vvv', |
| '-L', |
| '0:127.0.0.1:1', |
| 'id', |
| ])).thenAnswer((Invocation invocation) { |
| return Future<ProcessResult>.value(mockFailureProcessResult); |
| }); |
| await expectLater( |
| () => device.portForwarder.unforward(ForwardedPort(/*hostPort=*/ 0, /*devicePort=*/ 1)), |
| throwsToolExit(message: 'Unforward command failed:\nstdout: <stdout>\nstderr: <stderr>'), |
| ); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockProcessManager, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| }); |
| }); |
| |
| |
| group('FuchsiaIsolateDiscoveryProtocol', () { |
| MockPortForwarder portForwarder; |
| |
| setUp(() { |
| portForwarder = MockPortForwarder(); |
| }); |
| |
| 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', portForwarder, false); |
| final FuchsiaIsolateDiscoveryProtocol discoveryProtocol = |
| FuchsiaIsolateDiscoveryProtocol( |
| fuchsiaDevice, |
| expectedIsolateName, |
| (Uri uri) async => fakeVmServiceHost.vmService, |
| (Device device, Uri uri, bool enableServiceAuthCodes) => null, |
| true, // only poll once. |
| ); |
| final MockDartDevelopmentService mockDds = MockDartDevelopmentService(); |
| when(fuchsiaDevice.dds).thenReturn(mockDds); |
| when(mockDds.startDartDevelopmentService(any, any, any, any)).thenReturn(null); |
| when(mockDds.uri).thenReturn(Uri.parse('example')); |
| when(fuchsiaDevice.servicePorts()) |
| .thenAnswer((Invocation invocation) async => <int>[1]); |
| when(portForwarder.forward(1)) |
| .thenAnswer((Invocation invocation) async => 2); |
| return await 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: null), |
| // 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: null), |
| // 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: null), // 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(operatingSystem: 'linux'), |
| 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('Fuchsia app start and stop: ', () { |
| MemoryFileSystem memoryFileSystem; |
| FakeOperatingSystemUtils osUtils; |
| FakeFuchsiaDeviceTools fuchsiaDeviceTools; |
| MockFuchsiaSdk fuchsiaSdk; |
| Artifacts artifacts; |
| FakeProcessManager fakeSuccessfulProcessManager; |
| FakeProcessManager fakeFailedProcessManager; |
| File sshConfig; |
| |
| setUp(() { |
| memoryFileSystem = MemoryFileSystem.test(); |
| osUtils = FakeOperatingSystemUtils(); |
| fuchsiaDeviceTools = FakeFuchsiaDeviceTools(); |
| fuchsiaSdk = MockFuchsiaSdk(); |
| sshConfig = MemoryFileSystem.test().file('ssh_config')..writeAsStringSync('\n'); |
| artifacts = Artifacts.test(); |
| for (final BuildMode mode in <BuildMode>[BuildMode.debug, BuildMode.release]) { |
| memoryFileSystem.file( |
| artifacts.getArtifactPath(Artifact.fuchsiaKernelCompiler, |
| platform: TargetPlatform.fuchsia_arm64, mode: mode), |
| ).createSync(); |
| |
| memoryFileSystem.file( |
| artifacts.getArtifactPath(Artifact.platformKernelDill, |
| platform: TargetPlatform.fuchsia_arm64, mode: mode), |
| ).createSync(); |
| |
| memoryFileSystem.file( |
| artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, |
| platform: TargetPlatform.fuchsia_arm64, mode: mode), |
| ).createSync(); |
| |
| memoryFileSystem.file( |
| artifacts.getArtifactPath(Artifact.fuchsiaFlutterRunner, |
| platform: TargetPlatform.fuchsia_arm64, mode: mode), |
| ).createSync(); |
| } |
| fakeSuccessfulProcessManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand( |
| command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'], |
| stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22', |
| ), |
| ]); |
| fakeFailedProcessManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand( |
| command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'], |
| stdout: '', |
| stderr: '', |
| exitCode: 1, |
| ), |
| ]); |
| }); |
| |
| Future<LaunchResult> setupAndStartApp({ |
| @required bool prebuilt, |
| @required BuildMode mode, |
| }) async { |
| const String appName = 'app_name'; |
| final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123'); |
| globals.fs.directory('fuchsia').createSync(recursive: true); |
| final File pubspecFile = globals.fs.file('pubspec.yaml')..createSync(); |
| pubspecFile.writeAsStringSync('name: $appName'); |
| |
| FuchsiaApp app; |
| if (prebuilt) { |
| final File far = globals.fs.file('app_name-0.far')..createSync(); |
| app = FuchsiaApp.fromPrebuiltApp(far); |
| } else { |
| globals.fs.file(globals.fs.path.join('fuchsia', 'meta', '$appName.cmx')) |
| ..createSync(recursive: true) |
| ..writeAsStringSync('{}'); |
| globals.fs.file('.packages').createSync(); |
| globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); |
| app = BuildableFuchsiaApp(project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory).fuchsia); |
| } |
| |
| final DebuggingOptions debuggingOptions = DebuggingOptions.disabled(BuildInfo(mode, null, treeShakeIcons: false)); |
| return device.startApp( |
| app, |
| prebuiltApplication: prebuilt, |
| debuggingOptions: debuggingOptions, |
| ); |
| } |
| |
| testUsingContext('start prebuilt in release mode', () async { |
| final LaunchResult launchResult = |
| await setupAndStartApp(prebuilt: true, mode: BuildMode.release); |
| expect(launchResult.started, isTrue); |
| expect(launchResult.hasObservatory, isFalse); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| FileSystem: () => memoryFileSystem, |
| ProcessManager: () => fakeSuccessfulProcessManager, |
| FuchsiaDeviceTools: () => fuchsiaDeviceTools, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => fuchsiaSdk, |
| OperatingSystemUtils: () => osUtils, |
| }); |
| |
| testUsingContext('start and stop prebuilt in release mode', () async { |
| const String appName = 'app_name'; |
| final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123'); |
| globals.fs.directory('fuchsia').createSync(recursive: true); |
| final File pubspecFile = globals.fs.file('pubspec.yaml')..createSync(); |
| pubspecFile.writeAsStringSync('name: $appName'); |
| final File far = globals.fs.file('app_name-0.far')..createSync(); |
| |
| final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far); |
| final DebuggingOptions debuggingOptions = |
| DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)); |
| final LaunchResult launchResult = await device.startApp(app, |
| prebuiltApplication: true, |
| debuggingOptions: debuggingOptions); |
| expect(launchResult.started, isTrue); |
| expect(launchResult.hasObservatory, isFalse); |
| expect(await device.stopApp(app), isTrue); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| FileSystem: () => memoryFileSystem, |
| ProcessManager: () => fakeSuccessfulProcessManager, |
| FuchsiaDeviceTools: () => fuchsiaDeviceTools, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => fuchsiaSdk, |
| OperatingSystemUtils: () => osUtils, |
| }); |
| |
| testUsingContext('start prebuilt in debug mode', () async { |
| final LaunchResult launchResult = |
| await setupAndStartApp(prebuilt: true, mode: BuildMode.debug); |
| expect(launchResult.started, isTrue); |
| expect(launchResult.hasObservatory, isTrue); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| FileSystem: () => memoryFileSystem, |
| ProcessManager: () => fakeSuccessfulProcessManager, |
| FuchsiaDeviceTools: () => fuchsiaDeviceTools, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => fuchsiaSdk, |
| OperatingSystemUtils: () => osUtils, |
| }); |
| |
| testUsingContext('start buildable in release mode', () async { |
| final LaunchResult launchResult = |
| await setupAndStartApp(prebuilt: false, mode: BuildMode.release); |
| expect(launchResult.started, isTrue); |
| expect(launchResult.hasObservatory, isFalse); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| FileSystem: () => memoryFileSystem, |
| ProcessManager: () => FakeProcessManager.list(<FakeCommand>[ |
| const FakeCommand( |
| command: <String>[ |
| 'Artifact.genSnapshot.TargetPlatform.fuchsia_arm64.release', |
| '--deterministic', |
| '--snapshot_kind=app-aot-elf', |
| '--elf=build/fuchsia/elf.aotsnapshot', |
| 'build/fuchsia/app_name.dil' |
| ], |
| ), |
| FakeCommand( |
| command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'], |
| stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22', |
| ), |
| ]), |
| FuchsiaDeviceTools: () => fuchsiaDeviceTools, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => fuchsiaSdk, |
| OperatingSystemUtils: () => osUtils, |
| }); |
| |
| testUsingContext('start buildable in debug mode', () async { |
| final LaunchResult launchResult = |
| await setupAndStartApp(prebuilt: false, mode: BuildMode.debug); |
| expect(launchResult.started, isTrue); |
| expect(launchResult.hasObservatory, isTrue); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| FileSystem: () => memoryFileSystem, |
| ProcessManager: () => fakeSuccessfulProcessManager, |
| FuchsiaDeviceTools: () => fuchsiaDeviceTools, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => fuchsiaSdk, |
| OperatingSystemUtils: () => osUtils, |
| }); |
| |
| testUsingContext('fail when cant get ssh config', () async { |
| expect(() async => |
| setupAndStartApp(prebuilt: true, mode: BuildMode.release), |
| throwsToolExit(message: 'Cannot interact with device. No ssh config.\n' |
| 'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.')); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| FileSystem: () => memoryFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| FuchsiaDeviceTools: () => fuchsiaDeviceTools, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: null), |
| OperatingSystemUtils: () => osUtils, |
| }); |
| |
| testUsingContext('fail when cant get host address', () async { |
| expect(() async => |
| setupAndStartApp(prebuilt: true, mode: BuildMode.release), |
| throwsToolExit(message: 'Failed to get local address, aborting.')); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| FileSystem: () => memoryFileSystem, |
| ProcessManager: () => fakeFailedProcessManager, |
| FuchsiaDeviceTools: () => fuchsiaDeviceTools, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| OperatingSystemUtils: () => osUtils, |
| }); |
| |
| testUsingContext('fail with correct LaunchResult when pm fails', () async { |
| final LaunchResult launchResult = |
| await setupAndStartApp(prebuilt: true, mode: BuildMode.release); |
| expect(launchResult.started, isFalse); |
| expect(launchResult.hasObservatory, isFalse); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| FileSystem: () => memoryFileSystem, |
| ProcessManager: () => fakeSuccessfulProcessManager, |
| FuchsiaDeviceTools: () => fuchsiaDeviceTools, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => MockFuchsiaSdk(pm: FailingPM()), |
| OperatingSystemUtils: () => osUtils, |
| }); |
| |
| testUsingContext('fail with correct LaunchResult when amber fails', () async { |
| final LaunchResult launchResult = |
| await setupAndStartApp(prebuilt: true, mode: BuildMode.release); |
| expect(launchResult.started, isFalse); |
| expect(launchResult.hasObservatory, isFalse); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| FileSystem: () => memoryFileSystem, |
| ProcessManager: () => fakeSuccessfulProcessManager, |
| FuchsiaDeviceTools: () => FakeFuchsiaDeviceTools(amber: FailingAmberCtl()), |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => fuchsiaSdk, |
| OperatingSystemUtils: () => osUtils, |
| }); |
| |
| testUsingContext('fail with correct LaunchResult when tiles fails', () async { |
| final LaunchResult launchResult = |
| await setupAndStartApp(prebuilt: true, mode: BuildMode.release); |
| expect(launchResult.started, isFalse); |
| expect(launchResult.hasObservatory, isFalse); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| FileSystem: () => memoryFileSystem, |
| ProcessManager: () => fakeSuccessfulProcessManager, |
| FuchsiaDeviceTools: () => FakeFuchsiaDeviceTools(tiles: FailingTilesCtl()), |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => fuchsiaSdk, |
| OperatingSystemUtils: () => osUtils, |
| }); |
| |
| }); |
| |
| group('sdkNameAndVersion: ', () { |
| File sshConfig; |
| MockProcessManager mockSuccessProcessManager; |
| MockProcessResult mockSuccessProcessResult; |
| MockProcessManager mockFailureProcessManager; |
| MockProcessResult mockFailureProcessResult; |
| MockProcessManager emptyStdoutProcessManager; |
| MockProcessResult emptyStdoutProcessResult; |
| |
| setUp(() { |
| sshConfig = MemoryFileSystem.test().file('ssh_config')..writeAsStringSync('\n'); |
| |
| mockSuccessProcessManager = MockProcessManager(); |
| mockSuccessProcessResult = MockProcessResult(); |
| when(mockSuccessProcessManager.run(any)).thenAnswer( |
| (Invocation invocation) => Future<ProcessResult>.value(mockSuccessProcessResult)); |
| when(mockSuccessProcessResult.exitCode).thenReturn(0); |
| when<String>(mockSuccessProcessResult.stdout as String).thenReturn('version'); |
| when<String>(mockSuccessProcessResult.stderr as String).thenReturn(''); |
| |
| mockFailureProcessManager = MockProcessManager(); |
| mockFailureProcessResult = MockProcessResult(); |
| when(mockFailureProcessManager.run(any)).thenAnswer( |
| (Invocation invocation) => Future<ProcessResult>.value(mockFailureProcessResult)); |
| when(mockFailureProcessResult.exitCode).thenReturn(1); |
| when<String>(mockFailureProcessResult.stdout as String).thenReturn(''); |
| when<String>(mockFailureProcessResult.stderr as String).thenReturn(''); |
| |
| emptyStdoutProcessManager = MockProcessManager(); |
| emptyStdoutProcessResult = MockProcessResult(); |
| when(emptyStdoutProcessManager.run(any)).thenAnswer((Invocation invocation) => |
| Future<ProcessResult>.value(emptyStdoutProcessResult)); |
| when(emptyStdoutProcessResult.exitCode).thenReturn(0); |
| when<String>(emptyStdoutProcessResult.stdout as String).thenReturn(''); |
| when<String>(emptyStdoutProcessResult.stderr as String).thenReturn(''); |
| }); |
| |
| testUsingContext('does not throw on non-existant ssh config', () async { |
| final FuchsiaDevice device = FuchsiaDevice('123'); |
| expect(await device.sdkNameAndVersion, equals('Fuchsia')); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockSuccessProcessManager, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: null), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| }); |
| |
| testUsingContext('returns what we get from the device on success', () async { |
| final FuchsiaDevice device = FuchsiaDevice('123'); |
| expect(await device.sdkNameAndVersion, equals('Fuchsia version')); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockSuccessProcessManager, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| }); |
| |
| testUsingContext('returns "Fuchsia" when device command fails', () async { |
| final FuchsiaDevice device = FuchsiaDevice('123'); |
| expect(await device.sdkNameAndVersion, equals('Fuchsia')); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockFailureProcessManager, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| }); |
| |
| testUsingContext('returns "Fuchsia" when device gives an empty result', () async { |
| final FuchsiaDevice device = FuchsiaDevice('123'); |
| expect(await device.sdkNameAndVersion, equals('Fuchsia')); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => emptyStdoutProcessManager, |
| FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), |
| FuchsiaSdk: () => MockFuchsiaSdk(), |
| }); |
| }); |
| } |
| |
| class FuchsiaModulePackage extends ApplicationPackage { |
| FuchsiaModulePackage({@required this.name}) : super(id: name); |
| |
| @override |
| final String name; |
| } |
| |
| class MockProcessManager extends Mock implements ProcessManager {} |
| |
| class MockProcessResult extends Mock implements ProcessResult {} |
| |
| class MockProcess extends Mock implements Process {} |
| |
| Process _createMockProcess({ |
| int exitCode = 0, |
| String stdout = '', |
| String stderr = '', |
| bool persistent = false, |
| }) { |
| final Stream<List<int>> stdoutStream = Stream<List<int>>.fromIterable(<List<int>>[ |
| utf8.encode(stdout), |
| ]); |
| final Stream<List<int>> stderrStream = Stream<List<int>>.fromIterable(<List<int>>[ |
| utf8.encode(stderr), |
| ]); |
| final Process process = MockProcess(); |
| |
| when(process.stdout).thenAnswer((_) => stdoutStream); |
| when(process.stderr).thenAnswer((_) => stderrStream); |
| |
| if (persistent) { |
| final Completer<int> exitCodeCompleter = Completer<int>(); |
| when(process.kill()).thenAnswer((_) { |
| exitCodeCompleter.complete(-11); |
| return true; |
| }); |
| when(process.exitCode).thenAnswer((_) => exitCodeCompleter.future); |
| } else { |
| when(process.exitCode).thenAnswer((_) => Future<int>.value(exitCode)); |
| } |
| return process; |
| } |
| |
| class MockFuchsiaDevice extends Mock 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; |
| } |
| |
| class MockPortForwarder extends Mock implements DevicePortForwarder {} |
| |
| class FuchsiaDeviceWithFakeDiscovery extends FuchsiaDevice { |
| FuchsiaDeviceWithFakeDiscovery(String id, {String name}) : super(id, name: name); |
| |
| @override |
| FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(String isolateName) { |
| return FakeFuchsiaIsolateDiscoveryProtocol(); |
| } |
| |
| @override |
| Future<TargetPlatform> get targetPlatform async => TargetPlatform.fuchsia_arm64; |
| } |
| |
| class FakeFuchsiaIsolateDiscoveryProtocol implements FuchsiaIsolateDiscoveryProtocol { |
| @override |
| FutureOr<Uri> get uri => Uri.parse('http://[::1]:37'); |
| |
| @override |
| void dispose() {} |
| } |
| |
| class FakeFuchsiaAmberCtl implements FuchsiaAmberCtl { |
| @override |
| Future<bool> addSrc(FuchsiaDevice device, FuchsiaPackageServer server) async { |
| return true; |
| } |
| |
| @override |
| Future<bool> rmSrc(FuchsiaDevice device, FuchsiaPackageServer server) async { |
| return true; |
| } |
| |
| @override |
| Future<bool> getUp(FuchsiaDevice device, String packageName) async { |
| return true; |
| } |
| |
| @override |
| Future<bool> addRepoCfg(FuchsiaDevice device, FuchsiaPackageServer server) async { |
| return true; |
| } |
| |
| @override |
| Future<bool> pkgCtlResolve(FuchsiaDevice device, FuchsiaPackageServer server, String packageName) async { |
| return true; |
| } |
| |
| @override |
| Future<bool> pkgCtlRepoRemove(FuchsiaDevice device, FuchsiaPackageServer server) async { |
| return true; |
| } |
| } |
| |
| class FailingAmberCtl implements FuchsiaAmberCtl { |
| @override |
| Future<bool> addSrc(FuchsiaDevice device, FuchsiaPackageServer server) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> rmSrc(FuchsiaDevice device, FuchsiaPackageServer server) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> getUp(FuchsiaDevice device, String packageName) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> addRepoCfg(FuchsiaDevice device, FuchsiaPackageServer server) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> pkgCtlResolve(FuchsiaDevice device, FuchsiaPackageServer server, String packageName) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> pkgCtlRepoRemove(FuchsiaDevice device, FuchsiaPackageServer server) async { |
| return false; |
| } |
| } |
| |
| class FakeFuchsiaTilesCtl implements FuchsiaTilesCtl { |
| final Map<int, String> _runningApps = <int, String>{}; |
| bool _started = false; |
| int _nextAppId = 1; |
| |
| @override |
| Future<bool> start(FuchsiaDevice device) async { |
| _started = true; |
| return true; |
| } |
| |
| @override |
| Future<Map<int, String>> list(FuchsiaDevice device) async { |
| if (!_started) { |
| return null; |
| } |
| return _runningApps; |
| } |
| |
| @override |
| Future<bool> add(FuchsiaDevice device, String url, List<String> args) async { |
| if (!_started) { |
| return false; |
| } |
| _runningApps[_nextAppId] = url; |
| _nextAppId++; |
| return true; |
| } |
| |
| @override |
| Future<bool> remove(FuchsiaDevice device, int key) async { |
| if (!_started) { |
| return false; |
| } |
| _runningApps.remove(key); |
| return true; |
| } |
| |
| @override |
| Future<bool> quit(FuchsiaDevice device) async { |
| if (!_started) { |
| return false; |
| } |
| _started = false; |
| return true; |
| } |
| } |
| |
| class FailingTilesCtl implements FuchsiaTilesCtl { |
| @override |
| Future<bool> start(FuchsiaDevice device) async { |
| return false; |
| } |
| |
| @override |
| Future<Map<int, String>> list(FuchsiaDevice device) async { |
| return null; |
| } |
| |
| @override |
| Future<bool> add(FuchsiaDevice device, String url, List<String> args) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> remove(FuchsiaDevice device, int key) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> quit(FuchsiaDevice device) async { |
| return false; |
| } |
| } |
| |
| class FakeFuchsiaDeviceTools implements FuchsiaDeviceTools { |
| FakeFuchsiaDeviceTools({ |
| FuchsiaAmberCtl amber, |
| FuchsiaTilesCtl tiles, |
| }) : amberCtl = amber ?? FakeFuchsiaAmberCtl(), |
| tilesCtl = tiles ?? FakeFuchsiaTilesCtl(); |
| |
| @override |
| final FuchsiaAmberCtl amberCtl; |
| |
| @override |
| final FuchsiaTilesCtl tilesCtl; |
| } |
| |
| class FakeFuchsiaPM implements FuchsiaPM { |
| String _appName; |
| |
| @override |
| Future<bool> init(String buildPath, String appName) async { |
| if (!globals.fs.directory(buildPath).existsSync()) { |
| return false; |
| } |
| globals.fs |
| .file(globals.fs.path.join(buildPath, 'meta', 'package')) |
| .createSync(recursive: true); |
| _appName = appName; |
| return true; |
| } |
| |
| @override |
| Future<bool> genkey(String buildPath, String outKeyPath) async { |
| if (!globals.fs.file(globals.fs.path.join(buildPath, 'meta', 'package')).existsSync()) { |
| return false; |
| } |
| globals.fs.file(outKeyPath).createSync(recursive: true); |
| return true; |
| } |
| |
| @override |
| Future<bool> build(String buildPath, String keyPath, String manifestPath) async { |
| if (!globals.fs.file(globals.fs.path.join(buildPath, 'meta', 'package')).existsSync() || |
| !globals.fs.file(keyPath).existsSync() || |
| !globals.fs.file(manifestPath).existsSync()) { |
| return false; |
| } |
| globals.fs.file(globals.fs.path.join(buildPath, 'meta.far')).createSync(recursive: true); |
| return true; |
| } |
| |
| @override |
| Future<bool> archive(String buildPath, String keyPath, String manifestPath) async { |
| if (!globals.fs.file(globals.fs.path.join(buildPath, 'meta', 'package')).existsSync() || |
| !globals.fs.file(keyPath).existsSync() || |
| !globals.fs.file(manifestPath).existsSync()) { |
| return false; |
| } |
| if (_appName == null) { |
| return false; |
| } |
| globals.fs |
| .file(globals.fs.path.join(buildPath, '$_appName-0.far')) |
| .createSync(recursive: true); |
| return true; |
| } |
| |
| @override |
| Future<bool> newrepo(String repoPath) async { |
| if (!globals.fs.directory(repoPath).existsSync()) { |
| return false; |
| } |
| return true; |
| } |
| |
| @override |
| Future<Process> serve(String repoPath, String host, int port) async { |
| return _createMockProcess(persistent: true); |
| } |
| |
| @override |
| Future<bool> publish(String repoPath, String packagePath) async { |
| if (!globals.fs.directory(repoPath).existsSync()) { |
| return false; |
| } |
| if (!globals.fs.file(packagePath).existsSync()) { |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| class FailingPM implements FuchsiaPM { |
| @override |
| Future<bool> init(String buildPath, String appName) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> genkey(String buildPath, String outKeyPath) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> build(String buildPath, String keyPath, String manifestPath) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> archive(String buildPath, String keyPath, String manifestPath) async { |
| return false; |
| } |
| |
| @override |
| Future<bool> newrepo(String repoPath) async { |
| return false; |
| } |
| |
| @override |
| Future<Process> serve(String repoPath, String host, int port) async { |
| return _createMockProcess(exitCode: 6); |
| } |
| |
| @override |
| Future<bool> publish(String repoPath, String packagePath) async { |
| return false; |
| } |
| } |
| |
| class FakeFuchsiaKernelCompiler implements FuchsiaKernelCompiler { |
| @override |
| Future<void> build({ |
| @required FuchsiaProject fuchsiaProject, |
| @required String target, // E.g., lib/main.dart |
| BuildInfo buildInfo = BuildInfo.debug, |
| }) async { |
| final String outDir = getFuchsiaBuildDirectory(); |
| final String appName = fuchsiaProject.project.manifest.appName; |
| final String manifestPath = globals.fs.path.join(outDir, '$appName.dilpmanifest'); |
| globals.fs.file(manifestPath).createSync(recursive: true); |
| } |
| } |
| |
| class FailingKernelCompiler implements FuchsiaKernelCompiler { |
| @override |
| Future<void> build({ |
| @required FuchsiaProject fuchsiaProject, |
| @required String target, // E.g., lib/main.dart |
| BuildInfo buildInfo = BuildInfo.debug, |
| }) async { |
| throwToolExit('Build process failed'); |
| } |
| } |
| |
| class FakeFuchsiaDevFinder implements FuchsiaDevFinder { |
| @override |
| Future<List<String>> list({ Duration timeout }) async { |
| return <String>['192.168.11.999 scare-cable-device-finder']; |
| } |
| |
| @override |
| Future<String> resolve(String deviceName) async { |
| return '192.168.11.999'; |
| } |
| } |
| |
| 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'; |
| } |
| } |
| |
| class MockFuchsiaSdk extends Mock implements FuchsiaSdk { |
| MockFuchsiaSdk({ |
| FuchsiaPM pm, |
| FuchsiaKernelCompiler compiler, |
| FuchsiaDevFinder devFinder, |
| FuchsiaFfx ffx, |
| }) : fuchsiaPM = pm ?? FakeFuchsiaPM(), |
| fuchsiaKernelCompiler = compiler ?? FakeFuchsiaKernelCompiler(), |
| fuchsiaDevFinder = devFinder ?? FakeFuchsiaDevFinder(), |
| fuchsiaFfx = ffx ?? FakeFuchsiaFfx(); |
| |
| @override |
| final FuchsiaPM fuchsiaPM; |
| |
| @override |
| final FuchsiaKernelCompiler fuchsiaKernelCompiler; |
| |
| @override |
| final FuchsiaDevFinder fuchsiaDevFinder; |
| |
| @override |
| final FuchsiaFfx fuchsiaFfx; |
| } |
| |
| class MockDartDevelopmentService extends Mock implements DartDevelopmentService {} |
| class MockFuchsiaWorkflow extends Mock implements FuchsiaWorkflow {} |