// 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(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(['attach']); verify( portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')), ).called(1); }, overrides: { 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([ '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 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 [filesystemRoot]); }, overrides: { 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(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( ['attach', '-t', foo.path, '-v']); verify(mockHotRunnerFactory.build(any, target: foo.path, debuggingOptions: anyNamed('debuggingOptions'), packagesFilePath: anyNamed('packagesFilePath'), usesTerminalUI: anyNamed('usesTerminalUI'))).called(1); }, overrides: { 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(hostPort, devicePort)]); when(portForwarder.unforward(any)).thenAnswer((_) async => null); testDeviceManager.addDevice(device); final AttachCommand command = AttachCommand(); await createTestCommandRunner(command).run( ['attach', '--debug-port', '$devicePort']); verify(portForwarder.forward(devicePort)).called(1); }, overrides: { FileSystem: () => testFileSystem, },); testUsingContext('exits when no device connected', () async { final AttachCommand command = AttachCommand(); await expectLater( createTestCommandRunner(command).run(['attach']), throwsA(isInstanceOf()), ); expect(testLogger.statusText, contains('No connected devices')); }, overrides: { 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(['attach']), throwsA(isInstanceOf()), ); expect(testLogger.statusText, contains('More than one device')); expect(testLogger.statusText, contains('xx1')); expect(testLogger.statusText, contains('yy2')); }, overrides: { FileSystem: () => testFileSystem, },); }); } class MockPortForwarder extends Mock implements DevicePortForwarder {} class MockHotRunner extends Mock implements HotRunner {} class MockHotRunnerFactory extends Mock implements HotRunnerFactory {}