| // 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 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/base/common.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/platform.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| import 'package:flutter_tools/src/commands/attach.dart'; |
| import 'package:flutter_tools/src/device.dart'; |
| import 'package:flutter_tools/src/resident_runner.dart'; |
| import 'package:flutter_tools/src/run_hot.dart'; |
| import 'package:mockito/mockito.dart'; |
| |
| import '../src/common.dart'; |
| import '../src/context.dart'; |
| import '../src/mocks.dart'; |
| |
| void main() { |
| group('attach', () { |
| final FileSystem testFileSystem = MemoryFileSystem( |
| style: platform.isWindows ? FileSystemStyle.windows : FileSystemStyle |
| .posix, |
| ); |
| |
| setUp(() { |
| Cache.disableLocking(); |
| testFileSystem.directory('lib').createSync(); |
| testFileSystem.file('lib/main.dart').createSync(); |
| }); |
| |
| group('with one device and no specified target file', () { |
| const int devicePort = 499; |
| const int hostPort = 42; |
| |
| MockDeviceLogReader mockLogReader; |
| MockPortForwarder portForwarder; |
| MockAndroidDevice device; |
| |
| setUp(() { |
| mockLogReader = MockDeviceLogReader(); |
| portForwarder = MockPortForwarder(); |
| device = MockAndroidDevice(); |
| when(device.getLogReader()).thenAnswer((_) { |
| // Now that the reader is used, start writing messages to it. |
| Timer.run(() { |
| mockLogReader.addLine('Foo'); |
| mockLogReader.addLine( |
| 'Observatory listening on http://127.0.0.1:$devicePort'); |
| }); |
| |
| return mockLogReader; |
| }); |
| when(device.portForwarder).thenReturn(portForwarder); |
| when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort'))) |
| .thenAnswer((_) async => hostPort); |
| when(portForwarder.forwardedPorts).thenReturn( |
| <ForwardedPort>[ForwardedPort(hostPort, devicePort)]); |
| when(portForwarder.unforward(any)).thenAnswer((_) async => null); |
| |
| // We cannot add the device to a device manager because that is |
| // only enabled by the context of each testUsingContext call. |
| // |
| // Instead each test will add the device to the device manager |
| // on its own. |
| }); |
| |
| tearDown(() { |
| mockLogReader.dispose(); |
| }); |
| |
| testUsingContext('finds observatory port and forwards', () async { |
| testDeviceManager.addDevice(device); |
| |
| final AttachCommand command = AttachCommand(); |
| |
| await createTestCommandRunner(command).run(<String>['attach']); |
| |
| verify( |
| portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')), |
| ).called(1); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| }); |
| |
| testUsingContext('accepts filesystem parameters', () async { |
| testDeviceManager.addDevice(device); |
| |
| const String filesystemScheme = 'foo'; |
| const String filesystemRoot = '/build-output/'; |
| const String projectRoot = '/build-output/project-root'; |
| const String outputDill = '/tmp/output.dill'; |
| |
| final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory(); |
| when( |
| mockHotRunnerFactory.build( |
| any, |
| target: anyNamed('target'), |
| projectRootPath: anyNamed('projectRootPath'), |
| dillOutputPath: anyNamed('dillOutputPath'), |
| debuggingOptions: anyNamed('debuggingOptions'), |
| packagesFilePath: anyNamed('packagesFilePath'), |
| usesTerminalUI: anyNamed('usesTerminalUI'), |
| ), |
| )..thenReturn(MockHotRunner()); |
| |
| final AttachCommand command = AttachCommand( |
| hotRunnerFactory: mockHotRunnerFactory, |
| ); |
| await createTestCommandRunner(command).run(<String>[ |
| 'attach', |
| '--filesystem-scheme', |
| filesystemScheme, |
| '--filesystem-root', |
| filesystemRoot, |
| '--project-root', |
| projectRoot, |
| '--output-dill', |
| outputDill, |
| '-v', |
| ]); |
| |
| // Validate the attach call built a mock runner with the right |
| // project root and output dill. |
| final VerificationResult verificationResult = verify( |
| mockHotRunnerFactory.build( |
| captureAny, |
| target: anyNamed('target'), |
| projectRootPath: projectRoot, |
| dillOutputPath: outputDill, |
| debuggingOptions: anyNamed('debuggingOptions'), |
| packagesFilePath: anyNamed('packagesFilePath'), |
| usesTerminalUI: anyNamed('usesTerminalUI'), |
| ), |
| )..called(1); |
| |
| final List<FlutterDevice> flutterDevices = verificationResult.captured.first; |
| expect(flutterDevices, hasLength(1)); |
| |
| // Validate that the attach call built a flutter device with the right |
| // output dill, filesystem scheme, and filesystem root. |
| final FlutterDevice flutterDevice = flutterDevices.first; |
| |
| expect(flutterDevice.dillOutputPath, outputDill); |
| expect(flutterDevice.fileSystemScheme, filesystemScheme); |
| expect(flutterDevice.fileSystemRoots, const <String>[filesystemRoot]); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| }); |
| }); |
| |
| |
| testUsingContext('selects specified target', () async { |
| const int devicePort = 499; |
| const int hostPort = 42; |
| final MockDeviceLogReader mockLogReader = MockDeviceLogReader(); |
| final MockPortForwarder portForwarder = MockPortForwarder(); |
| final MockAndroidDevice device = MockAndroidDevice(); |
| final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory(); |
| when(device.portForwarder).thenReturn(portForwarder); |
| when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort'))) |
| .thenAnswer((_) async => hostPort); |
| when(portForwarder.forwardedPorts).thenReturn( |
| <ForwardedPort>[ForwardedPort(hostPort, devicePort)]); |
| when(portForwarder.unforward(any)).thenAnswer((_) async => null); |
| when(mockHotRunnerFactory.build(any, |
| target: anyNamed('target'), |
| debuggingOptions: anyNamed('debuggingOptions'), |
| packagesFilePath: anyNamed('packagesFilePath'), |
| usesTerminalUI: anyNamed('usesTerminalUI'))).thenReturn( |
| MockHotRunner()); |
| |
| testDeviceManager.addDevice(device); |
| when(device.getLogReader()).thenAnswer((_) { |
| // Now that the reader is used, start writing messages to it. |
| Timer.run(() { |
| mockLogReader.addLine('Foo'); |
| mockLogReader.addLine( |
| 'Observatory listening on http://127.0.0.1:$devicePort'); |
| }); |
| |
| return mockLogReader; |
| }); |
| final File foo = fs.file('lib/foo.dart') |
| ..createSync(); |
| |
| // Delete the main.dart file to be sure that attach works without it. |
| fs.file('lib/main.dart').deleteSync(); |
| |
| final AttachCommand command = AttachCommand( |
| hotRunnerFactory: mockHotRunnerFactory); |
| await createTestCommandRunner(command).run( |
| <String>['attach', '-t', foo.path, '-v']); |
| |
| verify(mockHotRunnerFactory.build(any, |
| target: foo.path, |
| debuggingOptions: anyNamed('debuggingOptions'), |
| packagesFilePath: anyNamed('packagesFilePath'), |
| usesTerminalUI: anyNamed('usesTerminalUI'))).called(1); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| },); |
| |
| testUsingContext('forwards to given port', () async { |
| const int devicePort = 499; |
| const int hostPort = 42; |
| final MockPortForwarder portForwarder = MockPortForwarder(); |
| final MockAndroidDevice device = MockAndroidDevice(); |
| |
| when(device.portForwarder).thenReturn(portForwarder); |
| when(portForwarder.forward(devicePort)).thenAnswer((_) async => hostPort); |
| when(portForwarder.forwardedPorts).thenReturn( |
| <ForwardedPort>[ForwardedPort(hostPort, devicePort)]); |
| when(portForwarder.unforward(any)).thenAnswer((_) async => null); |
| testDeviceManager.addDevice(device); |
| |
| final AttachCommand command = AttachCommand(); |
| |
| await createTestCommandRunner(command).run( |
| <String>['attach', '--debug-port', '$devicePort']); |
| |
| verify(portForwarder.forward(devicePort)).called(1); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| },); |
| |
| testUsingContext('exits when no device connected', () async { |
| final AttachCommand command = AttachCommand(); |
| await expectLater( |
| createTestCommandRunner(command).run(<String>['attach']), |
| throwsA(isInstanceOf<ToolExit>()), |
| ); |
| expect(testLogger.statusText, contains('No connected devices')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| },); |
| |
| testUsingContext('exits when multiple devices connected', () async { |
| Device aDeviceWithId(String id) { |
| final MockAndroidDevice device = MockAndroidDevice(); |
| when(device.name).thenReturn('d$id'); |
| when(device.id).thenReturn(id); |
| when(device.isLocalEmulator).thenAnswer((_) async => false); |
| when(device.sdkNameAndVersion).thenAnswer((_) async => 'Android 46'); |
| return device; |
| } |
| |
| final AttachCommand command = AttachCommand(); |
| testDeviceManager.addDevice(aDeviceWithId('xx1')); |
| testDeviceManager.addDevice(aDeviceWithId('yy2')); |
| await expectLater( |
| createTestCommandRunner(command).run(<String>['attach']), |
| throwsA(isInstanceOf<ToolExit>()), |
| ); |
| expect(testLogger.statusText, contains('More than one device')); |
| expect(testLogger.statusText, contains('xx1')); |
| expect(testLogger.statusText, contains('yy2')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| },); |
| }); |
| } |
| |
| class MockPortForwarder extends Mock implements DevicePortForwarder {} |
| |
| class MockHotRunner extends Mock implements HotRunner {} |
| |
| class MockHotRunnerFactory extends Mock implements HotRunnerFactory {} |