// 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 '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/context.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/fuchsia/application_package.dart'; import 'package:flutter_tools/src/fuchsia/amber_ctl.dart'; import 'package:flutter_tools/src/fuchsia/fuchsia_device.dart'; import 'package:flutter_tools/src/fuchsia/fuchsia_dev_finder.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/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:platform/platform.dart'; import 'package:process/process.dart'; import '../../src/common.dart'; import '../../src/context.dart'; void main() { group('fuchsia device', () { MemoryFileSystem memoryFileSystem; MockFile sshConfig; setUp(() { memoryFileSystem = MemoryFileSystem(); sshConfig = MockFile(); when(sshConfig.existsSync()).thenReturn(true); when(sshConfig.absolute).thenReturn(sshConfig); }); testUsingContext('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); }); testUsingContext('parse device-finder output', () async { const String example = '2001:0db8:85a3:0000:0000:8a2e:0370:7334 paper-pulp-bush-angel'; final List<FuchsiaDevice> names = await parseListDevices(example); expect(names.length, 1); expect(names.first.name, 'paper-pulp-bush-angel'); expect(names.first.id, '192.168.42.10'); }, overrides: <Type, Generator>{ FuchsiaSdk: () => MockFuchsiaSdk(), }); testUsingContext('parse junk device-finder output', () async { const String example = 'junk'; final List<FuchsiaDevice> names = await parseListDevices(example); expect(names.length, 0); }, overrides: <Type, Generator>{ FuchsiaSdk: () => MockFuchsiaSdk(), }); 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); }); testUsingContext('default capabilities', () async { final FuchsiaDevice device = FuchsiaDevice('123'); globals.fs.directory('fuchsia').createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); expect(device.supportsHotReload, true); expect(device.supportsHotRestart, false); expect(device.supportsFlutterExit, false); expect(device.isSupportedForProject(FlutterProject.current()), true); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('supported for project', () async { final FuchsiaDevice device = FuchsiaDevice('123'); globals.fs.directory('fuchsia').createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); expect(device.isSupportedForProject(FlutterProject.current()), true); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('not supported for project', () async { final FuchsiaDevice device = FuchsiaDevice('123'); globals.fs.file('pubspec.yaml').createSync(); expect(device.isSupportedForProject(FlutterProject.current()), false); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); 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(), }); }); group('displays friendly error when', () { MockProcessManager mockProcessManager; MockProcessResult mockProcessResult; MockFile mockFile; MockProcessManager emptyStdoutProcessManager; MockProcessResult emptyStdoutProcessResult; setUp(() { mockProcessManager = MockProcessManager(); mockProcessResult = MockProcessResult(); mockFile = MockFile(); 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(''); when(mockFile.absolute).thenReturn(mockFile); when(mockFile.path).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: mockFile, devFinder: mockFile, ), 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; MockFile devFinder; MockFile 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); devFinder = MockFile(); sshConfig = MockFile(); when(devFinder.existsSync()).thenReturn(true); when(sshConfig.existsSync()).thenReturn(true); when(devFinder.absolute).thenReturn(devFinder); when(sshConfig.absolute).thenReturn(sshConfig); }); 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), }); 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), }); 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), }); }); }); group('screenshot', () { MockProcessManager mockProcessManager; setUp(() { mockProcessManager = MockProcessManager(); }); test('is supported on posix platforms', () { final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester'); expect(device.supportsScreenshot, true); }, testOn: 'posix'); 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')), ); }, testOn: 'posix'); testUsingContext('takeScreenshot throws if screencap failed', () async { final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); when(mockProcessManager.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, Platform: () => FakePlatform( environment: <String, String>{ 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', }, operatingSystem: 'linux', ), }, testOn: 'posix'); testUsingContext('takeScreenshot throws if scp failed', () async { final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); when(mockProcessManager.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(mockProcessManager.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(mockProcessManager.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, Platform: () => FakePlatform( environment: <String, String>{ 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', }, operatingSystem: 'linux', ), }, testOn: 'posix'); testUsingContext("takeScreenshot prints error if can't delete file from device", () async { final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester'); when(mockProcessManager.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(mockProcessManager.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(mockProcessManager.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, 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(mockProcessManager.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(mockProcessManager.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(mockProcessManager.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, '', '')); try { await device.takeScreenshot(globals.fs.file('file.ppm')); } on Exception catch (e) { fail('Unexpected exception: $e'); } }, overrides: <Type, Generator>{ ProcessManager: () => mockProcessManager, Platform: () => FakePlatform( environment: <String, String>{ 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh', }, operatingSystem: 'linux', ), }, testOn: 'posix'); }); group(FuchsiaIsolateDiscoveryProtocol, () { MockPortForwarder portForwarder; MockVMService vmService; MockVM vm; setUp(() { portForwarder = MockPortForwarder(); vmService = MockVMService(); vm = MockVM(); when(vm.vmService).thenReturn(vmService); when(vmService.vm).thenReturn(vm); }); Future<Uri> findUri(List<MockFlutterView> views, String expectedIsolateName) async { when(vm.views).thenReturn(views); for (final MockFlutterView view in views) { when(view.owner).thenReturn(vm); } final MockFuchsiaDevice fuchsiaDevice = MockFuchsiaDevice('123', portForwarder, false); final FuchsiaIsolateDiscoveryProtocol discoveryProtocol = FuchsiaIsolateDiscoveryProtocol( fuchsiaDevice, expectedIsolateName, (Uri uri) async => vmService, true, // only poll once. ); when(fuchsiaDevice.servicePorts()) .thenAnswer((Invocation invocation) async => <int>[1]); when(portForwarder.forward(1)) .thenAnswer((Invocation invocation) async => 2); when(vmService.getVM()) .thenAnswer((Invocation invocation) => Future<void>.value(null)); when(vmService.refreshViews()) .thenAnswer((Invocation invocation) => Future<void>.value(null)); when(vmService.httpAddress).thenReturn(Uri.parse('example')); return await discoveryProtocol.uri; } testUsingContext('can find flutter view with matching isolate name', () async { const String expectedIsolateName = 'foobar'; final Uri uri = await findUri(<MockFlutterView>[ MockFlutterView(null), // no ui isolate. MockFlutterView(MockIsolate('wrong name')), // wrong name. MockFlutterView(MockIsolate(expectedIsolateName)), // matching name. ], 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(<MockFlutterView>[ MockFlutterView(null), // no ui isolate. MockFlutterView(MockIsolate('wrong name')), // wrong name. ], expectedIsolateName); expect(uri, throwsException); }); testUsingContext('can handle non flutter view', () async { const String expectedIsolateName = 'foobar'; final Future<Uri> uri = findUri(<MockFlutterView>[ MockFlutterView(null), // no ui isolate. ], expectedIsolateName); expect(uri, throwsException); }); }); testUsingContext('Correct flutter runner', () async { expect(globals.artifacts.getArtifactPath( Artifact.fuchsiaFlutterRunner, platform: TargetPlatform.fuchsia_x64, mode: BuildMode.debug, ), contains('flutter_jit_runner'), ); expect(globals.artifacts.getArtifactPath( Artifact.fuchsiaFlutterRunner, platform: TargetPlatform.fuchsia_x64, mode: BuildMode.profile, ), contains('flutter_aot_runner'), ); expect(globals.artifacts.getArtifactPath( Artifact.fuchsiaFlutterRunner, platform: TargetPlatform.fuchsia_x64, mode: BuildMode.release, ), contains('flutter_aot_product_runner'), ); expect(globals.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; MockFuchsiaArtifacts fuchsiaArtifacts; MockArtifacts mockArtifacts; File compilerSnapshot; File platformDill; File patchedSdk; File runner; setUp(() { memoryFileSystem = MemoryFileSystem(); osUtils = FakeOperatingSystemUtils(); fuchsiaDeviceTools = FakeFuchsiaDeviceTools(); fuchsiaSdk = MockFuchsiaSdk(); fuchsiaArtifacts = MockFuchsiaArtifacts(); compilerSnapshot = memoryFileSystem.file('kernel_compiler.snapshot')..createSync(); platformDill = memoryFileSystem.file('platform_strong.dill')..createSync(); patchedSdk = memoryFileSystem.file('flutter_runner_patched_sdk')..createSync(); runner = memoryFileSystem.file('flutter_jit_runner')..createSync(); mockArtifacts = MockArtifacts(); when(mockArtifacts.getArtifactPath( Artifact.fuchsiaKernelCompiler, platform: anyNamed('platform'), mode: anyNamed('mode'), )).thenReturn(compilerSnapshot.path); when(mockArtifacts.getArtifactPath( Artifact.platformKernelDill, platform: anyNamed('platform'), mode: anyNamed('mode'), )).thenReturn(platformDill.path); when(mockArtifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, platform: anyNamed('platform'), mode: anyNamed('mode'), )).thenReturn(patchedSdk.path); when(mockArtifacts.getArtifactPath( Artifact.fuchsiaFlutterRunner, platform: anyNamed('platform'), mode: anyNamed('mode'), )).thenReturn(runner.path); }); 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.current().fuchsia); } final DebuggingOptions debuggingOptions = DebuggingOptions.disabled(BuildInfo(mode, null, treeShakeIcons: false)); return await 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: () => mockArtifacts, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaArtifacts: () => fuchsiaArtifacts, 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: () => mockArtifacts, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaArtifacts: () => fuchsiaArtifacts, 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: () => mockArtifacts, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaArtifacts: () => fuchsiaArtifacts, 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: () => mockArtifacts, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaArtifacts: () => fuchsiaArtifacts, 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: () => mockArtifacts, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaArtifacts: () => fuchsiaArtifacts, FuchsiaSdk: () => fuchsiaSdk, OperatingSystemUtils: () => osUtils, }); testUsingContext('fail with correct LaunchResult when device-finder fails', () async { final LaunchResult launchResult = await setupAndStartApp(prebuilt: true, mode: BuildMode.release); expect(launchResult.started, isFalse); expect(launchResult.hasObservatory, isFalse); }, overrides: <Type, Generator>{ Artifacts: () => mockArtifacts, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaArtifacts: () => fuchsiaArtifacts, FuchsiaSdk: () => MockFuchsiaSdk(devFinder: FailingDevFinder()), 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: () => mockArtifacts, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), FuchsiaDeviceTools: () => fuchsiaDeviceTools, FuchsiaArtifacts: () => fuchsiaArtifacts, 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: () => mockArtifacts, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), FuchsiaDeviceTools: () => FakeFuchsiaDeviceTools(amber: FailingAmberCtl()), FuchsiaArtifacts: () => fuchsiaArtifacts, 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: () => mockArtifacts, FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), FuchsiaDeviceTools: () => FakeFuchsiaDeviceTools(tiles: FailingTilesCtl()), FuchsiaArtifacts: () => fuchsiaArtifacts, FuchsiaSdk: () => fuchsiaSdk, OperatingSystemUtils: () => osUtils, }); }); group('sdkNameAndVersion: ', () { MockFile sshConfig; MockProcessManager mockSuccessProcessManager; MockProcessResult mockSuccessProcessResult; MockProcessManager mockFailureProcessManager; MockProcessResult mockFailureProcessResult; MockProcessManager emptyStdoutProcessManager; MockProcessResult emptyStdoutProcessResult; setUp(() { sshConfig = MockFile(); when(sshConfig.existsSync()).thenReturn(true); when(sshConfig.absolute).thenReturn(sshConfig); 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 MockArtifacts extends Mock implements Artifacts {} class MockFuchsiaArtifacts extends Mock implements FuchsiaArtifacts {} class MockProcessManager extends Mock implements ProcessManager {} class MockProcessResult extends Mock implements ProcessResult {} class MockFile extends Mock implements File {} 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 MockVMService extends Mock implements VMService {} class MockVM extends Mock implements VM {} class MockFlutterView extends Mock implements FlutterView { MockFlutterView(this.uiIsolate); @override final Isolate uiIsolate; } class MockIsolate extends Mock implements Isolate { MockIsolate(this.name); @override final String name; } 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.42.172 scare-cable-skip-joy']; } @override Future<String> resolve(String deviceName, {bool local = false}) async { return '192.168.42.10'; } } class FailingDevFinder implements FuchsiaDevFinder { @override Future<List<String>> list({ Duration timeout }) async { return null; } @override Future<String> resolve(String deviceName, {bool local = false}) async { return null; } } class MockFuchsiaSdk extends Mock implements FuchsiaSdk { MockFuchsiaSdk({ FuchsiaPM pm, FuchsiaKernelCompiler compiler, FuchsiaDevFinder devFinder, }) : fuchsiaPM = pm ?? FakeFuchsiaPM(), fuchsiaKernelCompiler = compiler ?? FakeFuchsiaKernelCompiler(), fuchsiaDevFinder = devFinder ?? FakeFuchsiaDevFinder(); @override final FuchsiaPM fuchsiaPM; @override final FuchsiaKernelCompiler fuchsiaKernelCompiler; @override final FuchsiaDevFinder fuchsiaDevFinder; }