Commit 6757515d authored by Yegor's avatar Yegor

Merge pull request #2204 from yjbanov/driver-ios-emulator

decouple `flutter drive` from `flutter start`; make things in `flutter_tools` more testable
parents 76b9d8d1 677e63b7
...@@ -54,6 +54,8 @@ class AndroidDevice extends Device { ...@@ -54,6 +54,8 @@ class AndroidDevice extends Device {
bool _connected; bool _connected;
bool get isLocalEmulator => false;
List<String> adbCommandForDevice(List<String> args) { List<String> adbCommandForDevice(List<String> args) {
return <String>[androidSdk.adbPath, '-s', id]..addAll(args); return <String>[androidSdk.adbPath, '-s', id]..addAll(args);
} }
......
...@@ -5,7 +5,10 @@ ...@@ -5,7 +5,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
final OperatingSystemUtils os = new OperatingSystemUtils._(); import 'context.dart';
/// Returns [OperatingSystemUtils] active in the current app context (i.e. zone).
OperatingSystemUtils get os => context[OperatingSystemUtils] ?? (context[OperatingSystemUtils] = new OperatingSystemUtils._());
abstract class OperatingSystemUtils { abstract class OperatingSystemUtils {
factory OperatingSystemUtils._() { factory OperatingSystemUtils._() {
...@@ -16,6 +19,14 @@ abstract class OperatingSystemUtils { ...@@ -16,6 +19,14 @@ abstract class OperatingSystemUtils {
} }
} }
OperatingSystemUtils._private();
String get operatingSystem => Platform.operatingSystem;
bool get isMacOS => operatingSystem == 'macos';
bool get isWindows => operatingSystem == 'windows';
bool get isLinux => operatingSystem == 'linux';
/// Make the given file executable. This may be a no-op on some platforms. /// Make the given file executable. This may be a no-op on some platforms.
ProcessResult makeExecutable(File file); ProcessResult makeExecutable(File file);
...@@ -24,7 +35,9 @@ abstract class OperatingSystemUtils { ...@@ -24,7 +35,9 @@ abstract class OperatingSystemUtils {
File which(String execName); File which(String execName);
} }
class _PosixUtils implements OperatingSystemUtils { class _PosixUtils extends OperatingSystemUtils {
_PosixUtils() : super._private();
ProcessResult makeExecutable(File file) { ProcessResult makeExecutable(File file) {
return Process.runSync('chmod', ['u+x', file.path]); return Process.runSync('chmod', ['u+x', file.path]);
} }
...@@ -40,7 +53,9 @@ class _PosixUtils implements OperatingSystemUtils { ...@@ -40,7 +53,9 @@ class _PosixUtils implements OperatingSystemUtils {
} }
} }
class _WindowsUtils implements OperatingSystemUtils { class _WindowsUtils extends OperatingSystemUtils {
_WindowsUtils() : super._private();
// This is a no-op. // This is a no-op.
ProcessResult makeExecutable(File file) { ProcessResult makeExecutable(File file) {
return new ProcessResult(0, 0, null, null); return new ProcessResult(0, 0, null, null);
......
...@@ -420,7 +420,7 @@ Future<int> buildAndroid({ ...@@ -420,7 +420,7 @@ Future<int> buildAndroid({
// TODO(mpcomplete): move this to Device? // TODO(mpcomplete): move this to Device?
/// This is currently Android specific. /// This is currently Android specific.
Future buildAll( Future<int> buildAll(
DeviceStore devices, DeviceStore devices,
ApplicationPackageStore applicationPackages, ApplicationPackageStore applicationPackages,
Toolchain toolchain, Toolchain toolchain,
...@@ -434,31 +434,44 @@ Future buildAll( ...@@ -434,31 +434,44 @@ Future buildAll(
continue; continue;
// TODO(mpcomplete): Temporary hack. We only support the apk builder atm. // TODO(mpcomplete): Temporary hack. We only support the apk builder atm.
if (package == applicationPackages.android) { if (package != applicationPackages.android)
// TODO(devoncarew): Remove this warning after a few releases. continue;
if (FileSystemEntity.isDirectorySync('apk') && !FileSystemEntity.isDirectorySync('android')) {
// Tell people the android directory location changed. // TODO(devoncarew): Remove this warning after a few releases.
printStatus( if (FileSystemEntity.isDirectorySync('apk') && !FileSystemEntity.isDirectorySync('android')) {
"Warning: Flutter now looks for Android resources in the android/ directory; " // Tell people the android directory location changed.
"consider renaming your 'apk/' directory to 'android/'."); printStatus(
} "Warning: Flutter now looks for Android resources in the android/ directory; "
"consider renaming your 'apk/' directory to 'android/'.");
if (!FileSystemEntity.isFileSync(_kDefaultAndroidManifestPath)) {
printStatus('Using pre-built SkyShell.apk.');
continue;
}
int result = await buildAndroid(
toolchain: toolchain,
configs: configs,
enginePath: enginePath,
force: false,
target: target
);
if (result != 0)
return result;
} }
int result = await build(toolchain, configs, enginePath: enginePath,
target: target);
if (result != 0)
return result;
} }
return 0; return 0;
} }
Future<int> build(
Toolchain toolchain,
List<BuildConfiguration> configs, {
String enginePath,
String target: ''
}) async {
if (!FileSystemEntity.isFileSync(_kDefaultAndroidManifestPath)) {
printStatus('Using pre-built SkyShell.apk.');
return 0;
}
int result = await buildAndroid(
toolchain: toolchain,
configs: configs,
enginePath: enginePath,
force: false,
target: target
);
return result;
}
...@@ -51,6 +51,11 @@ abstract class RunCommandBase extends FlutterCommand { ...@@ -51,6 +51,11 @@ abstract class RunCommandBase extends FlutterCommand {
argParser.addOption('route', argParser.addOption('route',
help: 'Which route to load when starting the app.'); help: 'Which route to load when starting the app.');
} }
bool get checked => argResults['checked'];
bool get traceStartup => argResults['trace-startup'];
String get target => argResults['target'];
String get route => argResults['route'];
} }
class RunCommand extends RunCommandBase { class RunCommand extends RunCommandBase {
...@@ -219,7 +224,7 @@ Future<int> startApp( ...@@ -219,7 +224,7 @@ Future<int> startApp(
// wait for the observatory port to become available before returning from // wait for the observatory port to become available before returning from
// `startApp()`. // `startApp()`.
if (startPaused && device.supportsStartPaused) { if (startPaused && device.supportsStartPaused) {
await _delayUntilObservatoryAvailable('localhost', debugPort); await delayUntilObservatoryAvailable('localhost', debugPort);
} }
} }
} }
...@@ -242,7 +247,7 @@ Future<int> startApp( ...@@ -242,7 +247,7 @@ Future<int> startApp(
/// ///
/// This does not fail if we're unable to connect, and times out after the given /// This does not fail if we're unable to connect, and times out after the given
/// [timeout]. /// [timeout].
Future _delayUntilObservatoryAvailable(String host, int port, { Future delayUntilObservatoryAvailable(String host, int port, {
Duration timeout: const Duration(seconds: 10) Duration timeout: const Duration(seconds: 10)
}) async { }) async {
Stopwatch stopwatch = new Stopwatch()..start(); Stopwatch stopwatch = new Stopwatch()..start();
......
...@@ -130,6 +130,9 @@ abstract class Device { ...@@ -130,6 +130,9 @@ abstract class Device {
bool get supportsStartPaused => true; bool get supportsStartPaused => true;
/// Whether it is an emulated device running on localhost.
bool get isLocalEmulator;
/// Install an app package on the current device /// Install an app package on the current device
bool installApp(ApplicationPackage app); bool installApp(ApplicationPackage app);
...@@ -259,7 +262,7 @@ class DeviceStore { ...@@ -259,7 +262,7 @@ class DeviceStore {
break; break;
case TargetPlatform.iOSSimulator: case TargetPlatform.iOSSimulator:
assert(iOSSimulator == null); assert(iOSSimulator == null);
iOSSimulator = _deviceForConfig(config, IOSSimulator.getAttachedDevices()); iOSSimulator = _deviceForConfig(config, IOSSimulatorUtils.instance.getAttachedDevices());
break; break;
case TargetPlatform.mac: case TargetPlatform.mac:
case TargetPlatform.linux: case TargetPlatform.linux:
......
...@@ -62,6 +62,8 @@ class IOSDevice extends Device { ...@@ -62,6 +62,8 @@ class IOSDevice extends Device {
final String name; final String name;
bool get isLocalEmulator => false;
bool get supportsStartPaused => false; bool get supportsStartPaused => false;
static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) { static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) {
......
...@@ -10,6 +10,7 @@ import 'package:path/path.dart' as path; ...@@ -10,6 +10,7 @@ import 'package:path/path.dart' as path;
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../build_configuration.dart'; import '../build_configuration.dart';
import '../device.dart'; import '../device.dart';
...@@ -26,12 +27,29 @@ class IOSSimulators extends PollingDeviceDiscovery { ...@@ -26,12 +27,29 @@ class IOSSimulators extends PollingDeviceDiscovery {
IOSSimulators() : super('IOSSimulators'); IOSSimulators() : super('IOSSimulators');
bool get supportsPlatform => Platform.isMacOS; bool get supportsPlatform => Platform.isMacOS;
List<Device> pollingGetDevices() => IOSSimulator.getAttachedDevices(); List<Device> pollingGetDevices() => IOSSimulatorUtils.instance.getAttachedDevices();
}
class IOSSimulatorUtils {
/// Returns [IOSSimulatorUtils] active in the current app context (i.e. zone).
static IOSSimulatorUtils get instance => context[IOSSimulatorUtils] ?? (context[IOSSimulatorUtils] = new IOSSimulatorUtils());
List<IOSSimulator> getAttachedDevices() {
if (!xcode.isInstalledAndMeetsVersionCheck)
return <IOSSimulator>[];
return SimControl.instance.getConnectedDevices().map((SimDevice device) {
return new IOSSimulator(device.udid, name: device.name);
}).toList();
}
} }
/// A wrapper around the `simctl` command line tool. /// A wrapper around the `simctl` command line tool.
class SimControl { class SimControl {
static Future<bool> boot({String deviceId}) async { /// Returns [SimControl] active in the current app context (i.e. zone).
static SimControl get instance => context[SimControl] ?? (context[SimControl] = new SimControl());
Future<bool> boot({String deviceId}) async {
if (_isAnyConnected()) if (_isAnyConnected())
return true; return true;
...@@ -65,7 +83,7 @@ class SimControl { ...@@ -65,7 +83,7 @@ class SimControl {
} }
/// Returns a list of all available devices, both potential and connected. /// Returns a list of all available devices, both potential and connected.
static List<SimDevice> getDevices() { List<SimDevice> getDevices() {
// { // {
// "devices" : { // "devices" : {
// "com.apple.CoreSimulator.SimRuntime.iOS-8-2" : [ // "com.apple.CoreSimulator.SimRuntime.iOS-8-2" : [
...@@ -102,18 +120,18 @@ class SimControl { ...@@ -102,18 +120,18 @@ class SimControl {
} }
/// Returns all the connected simulator devices. /// Returns all the connected simulator devices.
static List<SimDevice> getConnectedDevices() { List<SimDevice> getConnectedDevices() {
return getDevices().where((SimDevice device) => device.isBooted).toList(); return getDevices().where((SimDevice device) => device.isBooted).toList();
} }
static StreamController<List<SimDevice>> _trackDevicesControler; StreamController<List<SimDevice>> _trackDevicesControler;
/// Listens to changes in the set of connected devices. The implementation /// Listens to changes in the set of connected devices. The implementation
/// currently uses polling. Callers should be careful to call cancel() on any /// currently uses polling. Callers should be careful to call cancel() on any
/// stream subscription when finished. /// stream subscription when finished.
/// ///
/// TODO(devoncarew): We could investigate using the usbmuxd protocol directly. /// TODO(devoncarew): We could investigate using the usbmuxd protocol directly.
static Stream<List<SimDevice>> trackDevices() { Stream<List<SimDevice>> trackDevices() {
if (_trackDevicesControler == null) { if (_trackDevicesControler == null) {
Timer timer; Timer timer;
Set<String> deviceIds = new Set<String>(); Set<String> deviceIds = new Set<String>();
...@@ -138,7 +156,7 @@ class SimControl { ...@@ -138,7 +156,7 @@ class SimControl {
} }
/// Update the cached set of device IDs and return whether there were any changes. /// Update the cached set of device IDs and return whether there were any changes.
static bool _updateDeviceIds(List<SimDevice> devices, Set<String> deviceIds) { bool _updateDeviceIds(List<SimDevice> devices, Set<String> deviceIds) {
Set<String> newIds = new Set<String>.from(devices.map((SimDevice device) => device.udid)); Set<String> newIds = new Set<String>.from(devices.map((SimDevice device) => device.udid));
bool changed = false; bool changed = false;
...@@ -159,13 +177,13 @@ class SimControl { ...@@ -159,13 +177,13 @@ class SimControl {
return changed; return changed;
} }
static bool _isAnyConnected() => getConnectedDevices().isNotEmpty; bool _isAnyConnected() => getConnectedDevices().isNotEmpty;
static void install(String deviceId, String appPath) { void install(String deviceId, String appPath) {
runCheckedSync([_xcrunPath, 'simctl', 'install', deviceId, appPath]); runCheckedSync([_xcrunPath, 'simctl', 'install', deviceId, appPath]);
} }
static void launch(String deviceId, String appIdentifier, [List<String> launchArgs]) { void launch(String deviceId, String appIdentifier, [List<String> launchArgs]) {
List<String> args = [_xcrunPath, 'simctl', 'launch', deviceId, appIdentifier]; List<String> args = [_xcrunPath, 'simctl', 'launch', deviceId, appIdentifier];
if (launchArgs != null) if (launchArgs != null)
args.addAll(launchArgs); args.addAll(launchArgs);
...@@ -190,17 +208,10 @@ class SimDevice { ...@@ -190,17 +208,10 @@ class SimDevice {
class IOSSimulator extends Device { class IOSSimulator extends Device {
IOSSimulator(String id, { this.name }) : super(id); IOSSimulator(String id, { this.name }) : super(id);
static List<IOSSimulator> getAttachedDevices() {
if (!xcode.isInstalledAndMeetsVersionCheck)
return <IOSSimulator>[];
return SimControl.getConnectedDevices().map((SimDevice device) {
return new IOSSimulator(device.udid, name: device.name);
}).toList();
}
final String name; final String name;
bool get isLocalEmulator => true;
String get xcrunPath => path.join('/usr', 'bin', 'xcrun'); String get xcrunPath => path.join('/usr', 'bin', 'xcrun');
String _getSimulatorPath() { String _getSimulatorPath() {
...@@ -220,7 +231,7 @@ class IOSSimulator extends Device { ...@@ -220,7 +231,7 @@ class IOSSimulator extends Device {
return false; return false;
try { try {
SimControl.install(id, app.localPath); SimControl.instance.install(id, app.localPath);
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
...@@ -231,7 +242,7 @@ class IOSSimulator extends Device { ...@@ -231,7 +242,7 @@ class IOSSimulator extends Device {
bool isConnected() { bool isConnected() {
if (!Platform.isMacOS) if (!Platform.isMacOS)
return false; return false;
return SimControl.getConnectedDevices().any((SimDevice device) => device.udid == id); return SimControl.instance.getConnectedDevices().any((SimDevice device) => device.udid == id);
} }
@override @override
...@@ -333,7 +344,7 @@ class IOSSimulator extends Device { ...@@ -333,7 +344,7 @@ class IOSSimulator extends Device {
} }
// Step 3: Install the updated bundle to the simulator. // Step 3: Install the updated bundle to the simulator.
SimControl.install(id, path.absolute(bundle.path)); SimControl.instance.install(id, path.absolute(bundle.path));
// Step 4: Prepare launch arguments. // Step 4: Prepare launch arguments.
List<String> args = <String>[]; List<String> args = <String>[];
...@@ -349,7 +360,7 @@ class IOSSimulator extends Device { ...@@ -349,7 +360,7 @@ class IOSSimulator extends Device {
// Step 5: Launch the updated application in the simulator. // Step 5: Launch the updated application in the simulator.
try { try {
SimControl.launch(id, app.id, args); SimControl.instance.launch(id, app.id, args);
} catch (error) { } catch (error) {
printError('$error'); printError('$error');
return false; return false;
......
...@@ -5,10 +5,15 @@ ...@@ -5,10 +5,15 @@
import 'dart:async'; import 'dart:async';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:flutter_tools/src/commands/drive.dart'; import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/commands/drive.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart'; import 'package:flutter_tools/src/globals.dart';
import 'package:flutter_tools/src/ios/simulators.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'src/common.dart'; import 'src/common.dart';
...@@ -19,18 +24,45 @@ main() => defineTests(); ...@@ -19,18 +24,45 @@ main() => defineTests();
defineTests() { defineTests() {
group('drive', () { group('drive', () {
DriveCommand command;
Device mockDevice;
void withMockDevice([Device mock]) {
mockDevice = mock ?? new MockDevice();
targetDeviceFinder = () async => mockDevice;
testDeviceManager.addDevice(mockDevice);
}
setUp(() { setUp(() {
command = new DriveCommand();
applyMocksToCommand(command);
useInMemoryFileSystem(cwd: '/some/app'); useInMemoryFileSystem(cwd: '/some/app');
toolchainDownloader = (_) async { };
targetDeviceFinder = () {
throw 'Unexpected call to targetDeviceFinder';
};
appStarter = (_) {
throw 'Unexpected call to appStarter';
};
testRunner = (_) {
throw 'Unexpected call to testRunner';
};
appStopper = (_) {
throw 'Unexpected call to appStopper';
};
}); });
tearDown(() { tearDown(() {
command = null;
restoreFileSystem(); restoreFileSystem();
restoreAppStarter();
restoreAppStopper();
restoreTestRunner();
restoreTargetDeviceFinder();
}); });
testUsingContext('returns 1 when test file is not found', () { testUsingContext('returns 1 when test file is not found', () {
DriveCommand command = new DriveCommand(); withMockDevice();
applyMocksToCommand(command);
List<String> args = [ List<String> args = [
'drive', 'drive',
'--target=/some/app/test/e2e.dart', '--target=/some/app/test/e2e.dart',
...@@ -44,10 +76,8 @@ defineTests() { ...@@ -44,10 +76,8 @@ defineTests() {
}); });
testUsingContext('returns 1 when app fails to run', () async { testUsingContext('returns 1 when app fails to run', () async {
DriveCommand command = new DriveCommand.custom(runAppFn: expectAsync(() { withMockDevice();
return new Future.value(1); appStarter = expectAsync((_) async => 1);
}));
applyMocksToCommand(command);
String testApp = '/some/app/test_driver/e2e.dart'; String testApp = '/some/app/test_driver/e2e.dart';
String testFile = '/some/app/test_driver/e2e_test.dart'; String testFile = '/some/app/test_driver/e2e_test.dart';
...@@ -72,8 +102,6 @@ defineTests() { ...@@ -72,8 +102,6 @@ defineTests() {
testUsingContext('returns 1 when app file is outside package', () async { testUsingContext('returns 1 when app file is outside package', () async {
String packageDir = '/my/app'; String packageDir = '/my/app';
useInMemoryFileSystem(cwd: packageDir); useInMemoryFileSystem(cwd: packageDir);
DriveCommand command = new DriveCommand();
applyMocksToCommand(command);
String appFile = '/not/in/my/app.dart'; String appFile = '/not/in/my/app.dart';
List<String> args = [ List<String> args = [
...@@ -92,8 +120,6 @@ defineTests() { ...@@ -92,8 +120,6 @@ defineTests() {
testUsingContext('returns 1 when app file is in the root dir', () async { testUsingContext('returns 1 when app file is in the root dir', () async {
String packageDir = '/my/app'; String packageDir = '/my/app';
useInMemoryFileSystem(cwd: packageDir); useInMemoryFileSystem(cwd: packageDir);
DriveCommand command = new DriveCommand();
applyMocksToCommand(command);
String appFile = '/my/app/main.dart'; String appFile = '/my/app/main.dart';
List<String> args = [ List<String> args = [
...@@ -111,22 +137,21 @@ defineTests() { ...@@ -111,22 +137,21 @@ defineTests() {
}); });
testUsingContext('returns 0 when test ends successfully', () async { testUsingContext('returns 0 when test ends successfully', () async {
withMockDevice();
String testApp = '/some/app/test/e2e.dart'; String testApp = '/some/app/test/e2e.dart';
String testFile = '/some/app/test_driver/e2e_test.dart'; String testFile = '/some/app/test_driver/e2e_test.dart';
DriveCommand command = new DriveCommand.custom( appStarter = expectAsync((_) {
runAppFn: expectAsync(() { return new Future<int>.value(0);
return new Future<int>.value(0); });
}), testRunner = expectAsync((List<String> testArgs) {
runTestsFn: expectAsync((List<String> testArgs) { expect(testArgs, [testFile]);
expect(testArgs, [testFile]); return new Future<Null>.value();
return new Future<Null>.value(); });
}), appStopper = expectAsync((_) {
stopAppFn: expectAsync(() { return new Future<int>.value(0);
return new Future<int>.value(0); });
})
);
applyMocksToCommand(command);
MemoryFileSystem memFs = fs; MemoryFileSystem memFs = fs;
await memFs.file(testApp).writeAsString('main() {}'); await memFs.file(testApp).writeAsString('main() {}');
...@@ -142,5 +167,88 @@ defineTests() { ...@@ -142,5 +167,88 @@ defineTests() {
expect(buffer.errorText, isEmpty); expect(buffer.errorText, isEmpty);
}); });
}); });
group('findTargetDevice', () {
testUsingContext('uses specified device', () async {
testDeviceManager.specifiedDeviceId = '123';
withMockDevice();
when(mockDevice.name).thenReturn('specified-device');
when(mockDevice.id).thenReturn('123');
Device device = await findTargetDevice();
expect(device.name, 'specified-device');
});
});
group('findTargetDevice on iOS', () {
setOs() {
when(os.isMacOS).thenReturn(true);
when(os.isLinux).thenReturn(false);
}
testUsingContext('uses existing emulator', () async {
setOs();
withMockDevice();
when(mockDevice.name).thenReturn('mock-simulator');
when(mockDevice.isLocalEmulator).thenReturn(true);
Device device = await findTargetDevice();
expect(device.name, 'mock-simulator');
});
testUsingContext('uses existing Android device if and there are no simulators', () async {
setOs();
mockDevice = new MockAndroidDevice();
when(mockDevice.name).thenReturn('mock-android-device');
when(mockDevice.isLocalEmulator).thenReturn(false);
withMockDevice(mockDevice);
Device device = await findTargetDevice();
expect(device.name, 'mock-android-device');
});
testUsingContext('launches emulator', () async {
setOs();
when(SimControl.instance.boot()).thenReturn(true);
Device emulator = new MockDevice();
when(emulator.name).thenReturn('new-simulator');
when(IOSSimulatorUtils.instance.getAttachedDevices())
.thenReturn([emulator]);
Device device = await findTargetDevice();
expect(device.name, 'new-simulator');
});
});
group('findTargetDevice on Linux', () {
setOs() {
when(os.isMacOS).thenReturn(false);
when(os.isLinux).thenReturn(true);
}
testUsingContext('returns null if no devices found', () async {
setOs();
expect(await findTargetDevice(), isNull);
});
testUsingContext('uses existing Android device', () async {
setOs();
mockDevice = new MockAndroidDevice();
when(mockDevice.name).thenReturn('mock-android-device');
withMockDevice(mockDevice);
Device device = await findTargetDevice();
expect(device.name, 'mock-android-device');
});
});
}); });
} }
class MockDevice extends Mock implements Device {
MockDevice() {
when(this.isSupported()).thenReturn(true);
}
}
class MockAndroidDevice extends Mock implements AndroidDevice { }
...@@ -15,7 +15,7 @@ defineTests() { ...@@ -15,7 +15,7 @@ defineTests() {
group('listen', () { group('listen', () {
testUsingContext('returns 1 when no device is connected', () { testUsingContext('returns 1 when no device is connected', () {
ListenCommand command = new ListenCommand(singleRun: true); ListenCommand command = new ListenCommand(singleRun: true);
applyMocksToCommand(command, noDevices: true); applyMocksToCommand(command);
return createTestCommandRunner(command).run(['listen']).then((int code) { return createTestCommandRunner(command).run(['listen']).then((int code) {
expect(code, equals(1)); expect(code, equals(1));
}); });
......
...@@ -7,9 +7,13 @@ import 'dart:io'; ...@@ -7,9 +7,13 @@ import 'dart:io';
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/simulators.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
/// Return the test logger. This assumes that the current Logger is a BufferLogger. /// Return the test logger. This assumes that the current Logger is a BufferLogger.
...@@ -38,6 +42,15 @@ void testUsingContext(String description, dynamic testMethod(), { ...@@ -38,6 +42,15 @@ void testUsingContext(String description, dynamic testMethod(), {
if (!overrides.containsKey(Doctor)) if (!overrides.containsKey(Doctor))
testContext[Doctor] = new MockDoctor(); testContext[Doctor] = new MockDoctor();
if (!overrides.containsKey(SimControl))
testContext[SimControl] = new MockSimControl();
if (!overrides.containsKey(OperatingSystemUtils))
testContext[OperatingSystemUtils] = new MockOperatingSystemUtils();
if (!overrides.containsKey(IOSSimulatorUtils))
testContext[IOSSimulatorUtils] = new MockIOSSimulatorUtils();
if (Platform.isMacOS) { if (Platform.isMacOS) {
if (!overrides.containsKey(XCode)) if (!overrides.containsKey(XCode))
testContext[XCode] = new XCode(); testContext[XCode] = new XCode();
...@@ -76,3 +89,13 @@ class MockDoctor extends Doctor { ...@@ -76,3 +89,13 @@ class MockDoctor extends Doctor {
// True for testing. // True for testing.
bool get canLaunchAnything => true; bool get canLaunchAnything => true;
} }
class MockSimControl extends Mock implements SimControl {
MockSimControl() {
when(this.getConnectedDevices()).thenReturn([]);
}
}
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
class MockIOSSimulatorUtils extends Mock implements IOSSimulatorUtils {}
...@@ -12,8 +12,6 @@ import 'package:flutter_tools/src/runner/flutter_command.dart'; ...@@ -12,8 +12,6 @@ import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/toolchain.dart'; import 'package:flutter_tools/src/toolchain.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'context.dart';
class MockApplicationPackageStore extends ApplicationPackageStore { class MockApplicationPackageStore extends ApplicationPackageStore {
MockApplicationPackageStore() : super( MockApplicationPackageStore() : super(
android: new AndroidApk(localPath: '/mock/path/to/android/SkyShell.apk'), android: new AndroidApk(localPath: '/mock/path/to/android/SkyShell.apk'),
...@@ -53,13 +51,10 @@ class MockDeviceStore extends DeviceStore { ...@@ -53,13 +51,10 @@ class MockDeviceStore extends DeviceStore {
iOSSimulator: new MockIOSSimulator()); iOSSimulator: new MockIOSSimulator());
} }
void applyMocksToCommand(FlutterCommand command, { bool noDevices: false }) { void applyMocksToCommand(FlutterCommand command) {
command command
..applicationPackages = new MockApplicationPackageStore() ..applicationPackages = new MockApplicationPackageStore()
..toolchain = new MockToolchain() ..toolchain = new MockToolchain()
..devices = new MockDeviceStore() ..devices = new MockDeviceStore()
..projectRootValidator = () => true; ..projectRootValidator = () => true;
if (!noDevices)
testDeviceManager.addDevice(command.devices.android);
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment