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 {
bool _connected;
bool get isLocalEmulator => false;
List<String> adbCommandForDevice(List<String> args) {
return <String>[androidSdk.adbPath, '-s', id]..addAll(args);
}
......
......@@ -5,7 +5,10 @@
import 'dart:async';
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 {
factory 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.
ProcessResult makeExecutable(File file);
......@@ -24,7 +35,9 @@ abstract class OperatingSystemUtils {
File which(String execName);
}
class _PosixUtils implements OperatingSystemUtils {
class _PosixUtils extends OperatingSystemUtils {
_PosixUtils() : super._private();
ProcessResult makeExecutable(File file) {
return Process.runSync('chmod', ['u+x', file.path]);
}
......@@ -40,7 +53,9 @@ class _PosixUtils implements OperatingSystemUtils {
}
}
class _WindowsUtils implements OperatingSystemUtils {
class _WindowsUtils extends OperatingSystemUtils {
_WindowsUtils() : super._private();
// This is a no-op.
ProcessResult makeExecutable(File file) {
return new ProcessResult(0, 0, null, null);
......
......@@ -420,7 +420,7 @@ Future<int> buildAndroid({
// TODO(mpcomplete): move this to Device?
/// This is currently Android specific.
Future buildAll(
Future<int> buildAll(
DeviceStore devices,
ApplicationPackageStore applicationPackages,
Toolchain toolchain,
......@@ -434,7 +434,9 @@ Future buildAll(
continue;
// TODO(mpcomplete): Temporary hack. We only support the apk builder atm.
if (package == applicationPackages.android) {
if (package != applicationPackages.android)
continue;
// TODO(devoncarew): Remove this warning after a few releases.
if (FileSystemEntity.isDirectorySync('apk') && !FileSystemEntity.isDirectorySync('android')) {
// Tell people the android directory location changed.
......@@ -443,9 +445,24 @@ Future buildAll(
"consider renaming your 'apk/' directory to 'android/'.");
}
int result = await build(toolchain, configs, enginePath: enginePath,
target: target);
if (result != 0)
return result;
}
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.');
continue;
return 0;
}
int result = await buildAndroid(
......@@ -455,10 +472,6 @@ Future buildAll(
force: false,
target: target
);
if (result != 0)
return result;
}
}
return 0;
return result;
}
......@@ -7,15 +7,18 @@ import 'dart:async';
import 'package:path/path.dart' as path;
import 'package:test/src/executable.dart' as executable;
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/os.dart';
import '../device.dart';
import '../globals.dart';
import '../ios/simulators.dart' show SimControl, IOSSimulatorUtils;
import '../android/android_device.dart' show AndroidDevice;
import '../application_package.dart';
import 'apk.dart' as apk;
import 'run.dart';
import 'stop.dart';
typedef Future<int> RunAppFunction();
typedef Future<Null> RunTestsFunction(List<String> testArgs);
typedef Future<int> StopAppFunction();
/// Runs integration (a.k.a. end-to-end) tests.
///
/// An integration test is a program that runs in a separate process from your
......@@ -36,31 +39,8 @@ typedef Future<int> StopAppFunction();
/// the application is stopped and the command exits. If all these steps are
/// successful the exit code will be `0`. Otherwise, you will see a non-zero
/// exit code.
class DriveCommand extends RunCommand {
final String name = 'drive';
final String description = 'Runs Flutter Driver tests for the current project.';
final List<String> aliases = <String>['driver'];
RunAppFunction _runApp;
RunTestsFunction _runTests;
StopAppFunction _stopApp;
/// Creates a drive command with custom process management functions.
///
/// [runAppFn] starts a Flutter application.
///
/// [runTestsFn] runs tests.
///
/// [stopAppFn] stops the test app after tests are finished.
DriveCommand.custom({
RunAppFunction runAppFn,
RunTestsFunction runTestsFn,
StopAppFunction stopAppFn
}) {
_runApp = runAppFn ?? super.runInProject;
_runTests = runTestsFn ?? executable.main;
_stopApp = stopAppFn ?? this.stop;
class DriveCommand extends RunCommandBase {
DriveCommand() {
argParser.addFlag(
'keep-app-running',
negatable: true,
......@@ -79,19 +59,35 @@ class DriveCommand extends RunCommand {
'already running instance. This will also cause the driver to keep '
'the application running after tests are done.'
);
argParser.addOption('debug-port',
defaultsTo: observatoryDefaultPort.toString(),
help: 'Listen to the given port for a debug connection.');
}
DriveCommand() : this.custom();
final String name = 'drive';
final String description = 'Runs Flutter Driver tests for the current project.';
final List<String> aliases = <String>['driver'];
Device _device;
Device get device => _device;
bool get requiresDevice => true;
int get debugPort => int.parse(argResults['debug-port']);
@override
Future<int> runInProject() async {
await toolchainDownloader(this);
String testFile = _getTestFile();
if (testFile == null) {
return 1;
}
this._device = await targetDeviceFinder();
if (device == null) {
return 1;
}
if (await fs.type(testFile) != FileSystemEntityType.FILE) {
printError('Test file not found: $testFile');
return 1;
......@@ -99,17 +95,17 @@ class DriveCommand extends RunCommand {
if (!argResults['use-existing-app']) {
printStatus('Starting application: ${argResults["target"]}');
int result = await _runApp();
int result = await appStarter(this);
if (result != 0) {
printError('Application failed to start. Will not run test. Quitting.');
return result;
}
} else {
printStatus('Will connect to already running application instance');
printStatus('Will connect to already running application instance.');
}
try {
return await _runTests([testFile])
return await testRunner([testFile])
.then((_) => 0)
.catchError((error, stackTrace) {
printError('CAUGHT EXCEPTION: $error\n$stackTrace');
......@@ -117,10 +113,15 @@ class DriveCommand extends RunCommand {
});
} finally {
if (!argResults['keep-app-running'] && !argResults['use-existing-app']) {
printStatus('Stopping application instance');
await _stopApp();
printStatus('Stopping application instance.');
try {
await appStopper(this);
} catch(error, stackTrace) {
// TODO(yjbanov): remove this guard when this bug is fixed: https://github.com/dart-lang/sdk/issues/25862
printStatus('Could not stop application: $error\n$stackTrace');
}
} else {
printStatus('Leaving the application running');
printStatus('Leaving the application running.');
}
}
}
......@@ -130,7 +131,7 @@ class DriveCommand extends RunCommand {
}
String _getTestFile() {
String appFile = path.normalize(argResults['target']);
String appFile = path.normalize(target);
// This command extends `flutter start` and therefore CWD == package dir
String packageDir = getCurrentDirectory();
......@@ -166,3 +167,159 @@ class DriveCommand extends RunCommand {
return '${pathWithNoExtension}_test${path.extension(appFile)}';
}
}
/// Finds a device to test on. May launch a simulator, if necessary.
typedef Future<Device> TargetDeviceFinder();
TargetDeviceFinder targetDeviceFinder = findTargetDevice;
void restoreTargetDeviceFinder() {
targetDeviceFinder = findTargetDevice;
}
Future<Device> findTargetDevice() async {
if (deviceManager.hasSpecifiedDeviceId) {
return deviceManager.getDeviceById(deviceManager.specifiedDeviceId);
}
List<Device> devices = await deviceManager.getAllConnectedDevices();
if (os.isMacOS) {
// On Mac we look for the iOS Simulator. If available, we use that. Then
// we look for an Android device. If there's one, we use that. Otherwise,
// we launch a new iOS Simulator.
Device reusableDevice = devices.firstWhere(
(d) => d.isLocalEmulator,
orElse: () {
return devices.firstWhere((d) => d is AndroidDevice,
orElse: () => null);
}
);
if (reusableDevice != null) {
printStatus('Found connected ${reusableDevice.isLocalEmulator ? "emulator" : "device"} "${reusableDevice.name}"; will reuse it.');
return reusableDevice;
}
// No running emulator found. Attempt to start one.
printStatus('Starting iOS Simulator, because did not find existing connected devices.');
bool started = await SimControl.instance.boot();
if (started) {
return IOSSimulatorUtils.instance.getAttachedDevices().first;
} else {
printError('Failed to start iOS Simulator.');
return null;
}
} else if (os.isLinux) {
// On Linux, for now, we just grab the first connected device we can find.
if (devices.isEmpty) {
printError('No devices found.');
return null;
} else if (devices.length > 1) {
printStatus('Found multiple connected devices:');
printStatus(devices.map((d) => ' - ${d.name}\n').join(''));
}
printStatus('Using device ${devices.first.name}.');
return devices.first;
} else if (os.isWindows) {
printError('Windows is not yet supported.');
return null;
} else {
printError('The operating system on this computer is not supported.');
return null;
}
}
/// Starts the application on the device given command configuration.
typedef Future<int> AppStarter(DriveCommand command);
AppStarter appStarter = startApp;
void restoreAppStarter() {
appStarter = startApp;
}
Future<int> startApp(DriveCommand command) async {
String mainPath = findMainDartFile(command.target);
if (await fs.type(mainPath) != FileSystemEntityType.FILE) {
printError('Tried to run $mainPath, but that file does not exist.');
return 1;
}
if (command.device is AndroidDevice) {
printTrace('Building an APK.');
int result = await apk.build(command.toolchain, command.buildConfigurations,
enginePath: command.runner.enginePath, target: command.target);
if (result != 0)
return result;
}
printTrace('Stopping previously running application, if any.');
await appStopper(command);
printTrace('Installing application package.');
ApplicationPackage package = command.applicationPackages
.getPackageForPlatform(command.device.platform);
await command.device.installApp(package);
printTrace('Starting application.');
bool started = await command.device.startApp(
package,
command.toolchain,
mainPath: mainPath,
route: command.route,
checked: command.checked,
clearLogs: true,
startPaused: true,
debugPort: command.debugPort,
platformArgs: <String, dynamic>{
'trace-startup': command.traceStartup,
}
);
if (command.device.supportsStartPaused) {
await delayUntilObservatoryAvailable('localhost', command.debugPort);
}
return started ? 0 : 2;
}
/// Runs driver tests.
typedef Future<Null> TestRunner(List<String> testArgs);
TestRunner testRunner = runTests;
void restoreTestRunner() {
testRunner = runTests;
}
Future<Null> runTests(List<String> testArgs) {
printTrace('Running driver tests.');
return executable.main(testArgs);
}
/// Stops the application.
typedef Future<int> AppStopper(DriveCommand command);
AppStopper appStopper = stopApp;
void restoreAppStopper() {
appStopper = stopApp;
}
Future<int> stopApp(DriveCommand command) async {
printTrace('Stopping application.');
ApplicationPackage package = command.applicationPackages
.getPackageForPlatform(command.device.platform);
bool stopped = await command.device.stopApp(package);
return stopped ? 0 : 1;
}
/// Downloads Flutter toolchain.
typedef Future<Null> ToolchainDownloader(DriveCommand command);
ToolchainDownloader toolchainDownloader = downloadToolchain;
void restoreToolchainDownloader() {
toolchainDownloader = downloadToolchain;
}
Future<Null> downloadToolchain(DriveCommand command) async {
printTrace('Downloading toolchain.');
await Future.wait([
command.downloadToolchain(),
command.downloadApplicationPackagesAndConnectToDevices(),
], eagerError: true);
}
......@@ -51,6 +51,11 @@ abstract class RunCommandBase extends FlutterCommand {
argParser.addOption('route',
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 {
......@@ -219,7 +224,7 @@ Future<int> startApp(
// wait for the observatory port to become available before returning from
// `startApp()`.
if (startPaused && device.supportsStartPaused) {
await _delayUntilObservatoryAvailable('localhost', debugPort);
await delayUntilObservatoryAvailable('localhost', debugPort);
}
}
}
......@@ -242,7 +247,7 @@ Future<int> startApp(
///
/// This does not fail if we're unable to connect, and times out after the given
/// [timeout].
Future _delayUntilObservatoryAvailable(String host, int port, {
Future delayUntilObservatoryAvailable(String host, int port, {
Duration timeout: const Duration(seconds: 10)
}) async {
Stopwatch stopwatch = new Stopwatch()..start();
......
......@@ -130,6 +130,9 @@ abstract class Device {
bool get supportsStartPaused => true;
/// Whether it is an emulated device running on localhost.
bool get isLocalEmulator;
/// Install an app package on the current device
bool installApp(ApplicationPackage app);
......@@ -259,7 +262,7 @@ class DeviceStore {
break;
case TargetPlatform.iOSSimulator:
assert(iOSSimulator == null);
iOSSimulator = _deviceForConfig(config, IOSSimulator.getAttachedDevices());
iOSSimulator = _deviceForConfig(config, IOSSimulatorUtils.instance.getAttachedDevices());
break;
case TargetPlatform.mac:
case TargetPlatform.linux:
......
......@@ -62,6 +62,8 @@ class IOSDevice extends Device {
final String name;
bool get isLocalEmulator => false;
bool get supportsStartPaused => false;
static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) {
......
......@@ -10,6 +10,7 @@ import 'package:path/path.dart' as path;
import '../application_package.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/process.dart';
import '../build_configuration.dart';
import '../device.dart';
......@@ -26,12 +27,29 @@ class IOSSimulators extends PollingDeviceDiscovery {
IOSSimulators() : super('IOSSimulators');
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.
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())
return true;
......@@ -65,7 +83,7 @@ class SimControl {
}
/// Returns a list of all available devices, both potential and connected.
static List<SimDevice> getDevices() {
List<SimDevice> getDevices() {
// {
// "devices" : {
// "com.apple.CoreSimulator.SimRuntime.iOS-8-2" : [
......@@ -102,18 +120,18 @@ class SimControl {
}
/// Returns all the connected simulator devices.
static List<SimDevice> getConnectedDevices() {
List<SimDevice> getConnectedDevices() {
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
/// currently uses polling. Callers should be careful to call cancel() on any
/// stream subscription when finished.
///
/// TODO(devoncarew): We could investigate using the usbmuxd protocol directly.
static Stream<List<SimDevice>> trackDevices() {
Stream<List<SimDevice>> trackDevices() {
if (_trackDevicesControler == null) {
Timer timer;
Set<String> deviceIds = new Set<String>();
......@@ -138,7 +156,7 @@ class SimControl {
}
/// 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));
bool changed = false;
......@@ -159,13 +177,13 @@ class SimControl {
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]);
}
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];
if (launchArgs != null)
args.addAll(launchArgs);
......@@ -190,17 +208,10 @@ class SimDevice {
class IOSSimulator extends Device {
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;
bool get isLocalEmulator => true;
String get xcrunPath => path.join('/usr', 'bin', 'xcrun');
String _getSimulatorPath() {
......@@ -220,7 +231,7 @@ class IOSSimulator extends Device {
return false;
try {
SimControl.install(id, app.localPath);
SimControl.instance.install(id, app.localPath);
return true;
} catch (e) {
return false;
......@@ -231,7 +242,7 @@ class IOSSimulator extends Device {
bool isConnected() {
if (!Platform.isMacOS)
return false;
return SimControl.getConnectedDevices().any((SimDevice device) => device.udid == id);
return SimControl.instance.getConnectedDevices().any((SimDevice device) => device.udid == id);
}
@override
......@@ -333,7 +344,7 @@ class IOSSimulator extends Device {
}
// 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.
List<String> args = <String>[];
......@@ -349,7 +360,7 @@ class IOSSimulator extends Device {
// Step 5: Launch the updated application in the simulator.
try {
SimControl.launch(id, app.id, args);
SimControl.instance.launch(id, app.id, args);
} catch (error) {
printError('$error');
return false;
......
......@@ -5,10 +5,15 @@
import 'dart:async';
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/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/ios/simulators.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'src/common.dart';
......@@ -19,18 +24,45 @@ main() => defineTests();
defineTests() {
group('drive', () {
DriveCommand command;
Device mockDevice;
void withMockDevice([Device mock]) {
mockDevice = mock ?? new MockDevice();
targetDeviceFinder = () async => mockDevice;
testDeviceManager.addDevice(mockDevice);
}
setUp(() {
command = new DriveCommand();
applyMocksToCommand(command);
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(() {
command = null;
restoreFileSystem();
restoreAppStarter();
restoreAppStopper();
restoreTestRunner();
restoreTargetDeviceFinder();
});
testUsingContext('returns 1 when test file is not found', () {
DriveCommand command = new DriveCommand();
applyMocksToCommand(command);
withMockDevice();
List<String> args = [
'drive',
'--target=/some/app/test/e2e.dart',
......@@ -44,10 +76,8 @@ defineTests() {
});
testUsingContext('returns 1 when app fails to run', () async {
DriveCommand command = new DriveCommand.custom(runAppFn: expectAsync(() {
return new Future.value(1);
}));
applyMocksToCommand(command);
withMockDevice();
appStarter = expectAsync((_) async => 1);
String testApp = '/some/app/test_driver/e2e.dart';
String testFile = '/some/app/test_driver/e2e_test.dart';
......@@ -72,8 +102,6 @@ defineTests() {
testUsingContext('returns 1 when app file is outside package', () async {
String packageDir = '/my/app';
useInMemoryFileSystem(cwd: packageDir);
DriveCommand command = new DriveCommand();
applyMocksToCommand(command);
String appFile = '/not/in/my/app.dart';
List<String> args = [
......@@ -92,8 +120,6 @@ defineTests() {
testUsingContext('returns 1 when app file is in the root dir', () async {
String packageDir = '/my/app';
useInMemoryFileSystem(cwd: packageDir);
DriveCommand command = new DriveCommand();
applyMocksToCommand(command);
String appFile = '/my/app/main.dart';
List<String> args = [
......@@ -111,22 +137,21 @@ defineTests() {
});
testUsingContext('returns 0 when test ends successfully', () async {
withMockDevice();
String testApp = '/some/app/test/e2e.dart';
String testFile = '/some/app/test_driver/e2e_test.dart';
DriveCommand command = new DriveCommand.custom(
runAppFn: expectAsync(() {
appStarter = expectAsync((_) {
return new Future<int>.value(0);
}),
runTestsFn: expectAsync((List<String> testArgs) {
});
testRunner = expectAsync((List<String> testArgs) {
expect(testArgs, [testFile]);
return new Future<Null>.value();
}),
stopAppFn: expectAsync(() {
});
appStopper = expectAsync((_) {
return new Future<int>.value(0);
})
);
applyMocksToCommand(command);
});
MemoryFileSystem memFs = fs;
await memFs.file(testApp).writeAsString('main() {}');
......@@ -142,5 +167,88 @@ defineTests() {
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() {
group('listen', () {
testUsingContext('returns 1 when no device is connected', () {
ListenCommand command = new ListenCommand(singleRun: true);
applyMocksToCommand(command, noDevices: true);
applyMocksToCommand(command);
return createTestCommandRunner(command).run(['listen']).then((int code) {
expect(code, equals(1));
});
......
......@@ -7,9 +7,13 @@ import 'dart:io';
import 'package:flutter_tools/src/base/context.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/doctor.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';
/// Return the test logger. This assumes that the current Logger is a BufferLogger.
......@@ -38,6 +42,15 @@ void testUsingContext(String description, dynamic testMethod(), {
if (!overrides.containsKey(Doctor))
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 (!overrides.containsKey(XCode))
testContext[XCode] = new XCode();
......@@ -76,3 +89,13 @@ class MockDoctor extends Doctor {
// True for testing.
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';
import 'package:flutter_tools/src/toolchain.dart';
import 'package:mockito/mockito.dart';
import 'context.dart';
class MockApplicationPackageStore extends ApplicationPackageStore {
MockApplicationPackageStore() : super(
android: new AndroidApk(localPath: '/mock/path/to/android/SkyShell.apk'),
......@@ -53,13 +51,10 @@ class MockDeviceStore extends DeviceStore {
iOSSimulator: new MockIOSSimulator());
}
void applyMocksToCommand(FlutterCommand command, { bool noDevices: false }) {
void applyMocksToCommand(FlutterCommand command) {
command
..applicationPackages = new MockApplicationPackageStore()
..toolchain = new MockToolchain()
..devices = new MockDeviceStore()
..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