Commit 677e63b7 authored by Yegor Jbanov's avatar Yegor Jbanov

decouple `flutter drive` from `flutter start`

flutter start's method of finding devices to run the app on is not suitable for flutter drive.

This commit also refactors several tool services to allow mocking in unit tests.
parent 0c05666e
......@@ -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,31 +434,44 @@ Future buildAll(
continue;
// TODO(mpcomplete): Temporary hack. We only support the apk builder atm.
if (package == applicationPackages.android) {
// TODO(devoncarew): Remove this warning after a few releases.
if (FileSystemEntity.isDirectorySync('apk') && !FileSystemEntity.isDirectorySync('android')) {
// Tell people the android directory location changed.
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;
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.
printStatus(
"Warning: Flutter now looks for Android resources in the android/ directory; "
"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.');
return 0;
}
int result = await buildAndroid(
toolchain: toolchain,
configs: configs,
enginePath: enginePath,
force: false,
target: target
);
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(() {
return new Future<int>.value(0);
}),
runTestsFn: expectAsync((List<String> testArgs) {
expect(testArgs, [testFile]);
return new Future<Null>.value();
}),
stopAppFn: expectAsync(() {
return new Future<int>.value(0);
})
);
applyMocksToCommand(command);
appStarter = expectAsync((_) {
return new Future<int>.value(0);
});
testRunner = expectAsync((List<String> testArgs) {
expect(testArgs, [testFile]);
return new Future<Null>.value();
});
appStopper = expectAsync((_) {
return new Future<int>.value(0);
});
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