| // Copyright 2018 The Chromium 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:flutter_tools/src/base/logger.dart'; |
| import 'package:flutter_tools/src/vmservice.dart'; |
| import 'package:mockito/mockito.dart'; |
| import 'package:process/process.dart'; |
| |
| import 'package:flutter_tools/src/base/common.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/io.dart'; |
| import 'package:flutter_tools/src/base/time.dart'; |
| import 'package:flutter_tools/src/device.dart'; |
| import 'package:flutter_tools/src/fuchsia/fuchsia_device.dart'; |
| import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart'; |
| |
| import '../src/common.dart'; |
| import '../src/context.dart'; |
| |
| void main() { |
| group('fuchsia device', () { |
| 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); |
| }); |
| |
| test('parse dev_finder output', () { |
| const String example = '192.168.42.56 paper-pulp-bush-angel'; |
| final List<FuchsiaDevice> names = parseListDevices(example); |
| |
| expect(names.length, 1); |
| expect(names.first.name, 'paper-pulp-bush-angel'); |
| expect(names.first.id, '192.168.42.56'); |
| }); |
| |
| test('parse junk dev_finder output', () { |
| const String example = 'junk'; |
| final List<FuchsiaDevice> names = parseListDevices(example); |
| |
| expect(names.length, 0); |
| }); |
| |
| test('default capabilities', () async { |
| final FuchsiaDevice device = FuchsiaDevice('123'); |
| |
| expect(device.supportsHotReload, true); |
| expect(device.supportsHotRestart, false); |
| expect(device.supportsStopApp, false); |
| expect(await device.stopApp(null), false); |
| }); |
| }); |
| |
| group('displays friendly error when', () { |
| final MockProcessManager mockProcessManager = MockProcessManager(); |
| final MockProcessResult mockProcessResult = MockProcessResult(); |
| final MockFile 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).thenReturn(''); |
| when<String>(mockProcessResult.stderr).thenReturn(''); |
| when(mockFile.absolute).thenReturn(mockFile); |
| when(mockFile.path).thenReturn(''); |
| |
| final MockProcessManager emptyStdoutProcessManager = MockProcessManager(); |
| final MockProcessResult 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).thenReturn(''); |
| when<String>(emptyStdoutProcessResult.stderr).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, |
| ), |
| }); |
| |
| group('device logs', () { |
| const String exampleUtcLogs = ''' |
| [2018-11-09 01:27:45][3][297950920][log] INFO: example_app(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(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(flutter): Did thing this time |
| |
| '''; |
| final MockProcessManager mockProcessManager = MockProcessManager(); |
| final MockProcess mockProcess = MockProcess(); |
| Completer<int> exitCode; |
| StreamController<List<int>> stdout; |
| StreamController<List<int>> stderr; |
| 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); |
| |
| setUp(() { |
| stdout = StreamController<List<int>>(sync: true); |
| stderr = StreamController<List<int>>(sync: true); |
| exitCode = Completer<int>(); |
| }); |
| |
| 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)), |
| }); |
| |
| 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)), |
| }); |
| |
| 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)), |
| }); |
| }); |
| }); |
| |
| group(FuchsiaIsolateDiscoveryProtocol, () { |
| Future<Uri> findUri(List<MockFlutterView> views, String expectedIsolateName) { |
| final MockPortForwarder portForwarder = MockPortForwarder(); |
| final MockVMService vmService = MockVMService(); |
| final MockVM vm = MockVM(); |
| vm.vmService = vmService; |
| vmService.vm = vm; |
| vm.views = views; |
| for (MockFlutterView view in views) { |
| view.owner = 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 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/'); |
| }, overrides: <Type, Generator>{ |
| Logger: () => StdoutLogger(), |
| }); |
| |
| 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); |
| }, overrides: <Type, Generator>{ |
| Logger: () => StdoutLogger(), |
| }); |
| |
| 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); |
| }, overrides: <Type, Generator>{ |
| Logger: () => StdoutLogger(), |
| }); |
| }); |
| } |
| |
| class MockProcessManager extends Mock implements ProcessManager {} |
| |
| class MockProcessResult extends Mock implements ProcessResult {} |
| |
| class MockFile extends Mock implements File {} |
| |
| class MockProcess extends Mock implements Process {} |
| |
| class MockFuchsiaDevice extends Mock implements FuchsiaDevice { |
| MockFuchsiaDevice(this.id, this.portForwarder, this.ipv6); |
| |
| @override |
| final bool ipv6; |
| @override |
| final String id; |
| @override |
| final DevicePortForwarder portForwarder; |
| } |
| |
| class MockPortForwarder extends Mock implements DevicePortForwarder {} |
| |
| class MockVMService extends Mock implements VMService { |
| @override |
| VM vm; |
| } |
| |
| class MockVM extends Mock implements VM { |
| @override |
| VMService vmService; |
| |
| @override |
| List<FlutterView> views; |
| } |
| |
| class MockFlutterView extends Mock implements FlutterView { |
| MockFlutterView(this.uiIsolate); |
| |
| @override |
| final Isolate uiIsolate; |
| |
| @override |
| ServiceObjectOwner owner; |
| } |
| |
| class MockIsolate extends Mock implements Isolate { |
| MockIsolate(this.name); |
| |
| @override |
| final String name; |
| } |