Commit 43aaf50e authored by Ian Fischer's avatar Ian Fischer

Merge pull request #78 from iansf/ios_start

Add implementation of start and stop commands for iOS.
parents 72cc4d6f 844678dd
......@@ -56,6 +56,12 @@ abstract class _Device {
/// Check if the current version of the given app is already installed
bool isAppInstalled(ApplicationPackage app);
/// Start an app package on the current device
Future<bool> startApp(ApplicationPackage app);
/// Stop an app package on the current device
Future<bool> stopApp(ApplicationPackage app);
}
class IOSDevice extends _Device {
......@@ -80,6 +86,9 @@ class IOSDevice extends _Device {
String _informerPath;
String get informerPath => _informerPath;
String _debuggerPath;
String get debuggerPath => _debuggerPath;
String _name;
String get name => _name;
......@@ -93,6 +102,7 @@ class IOSDevice extends _Device {
_installerPath = _checkForCommand('ideviceinstaller');
_listerPath = _checkForCommand('idevice_id');
_informerPath = _checkForCommand('ideviceinfo');
_debuggerPath = _checkForCommand('idevicedebug');
}
static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) {
......@@ -160,6 +170,37 @@ class IOSDevice extends _Device {
@override
bool isAppInstalled(ApplicationPackage app) {
try {
String apps = runCheckedSync([installerPath, '-l']);
if (new RegExp(app.appPackageID, multiLine: true).hasMatch(apps)) {
return true;
}
} catch (e) {
return false;
}
return false;
}
@override
Future<bool> startApp(ApplicationPackage app) async {
if (!isAppInstalled(app)) {
return false;
}
// idevicedebug hangs forever after launching the app, so kill it after
// giving it plenty of time to send the launch command.
return runAndKill(
[debuggerPath, 'run', app.appPackageID], new Duration(seconds: 3)).then(
(_) {
return true;
}, onError: (e) {
_logging.info('Failure running $debuggerPath: ', e);
return false;
});
}
@override
Future<bool> stopApp(ApplicationPackage app) async {
// Currently we don't have a way to stop an app running on iOS.
return false;
}
}
......@@ -458,7 +499,14 @@ class AndroidDevice extends _Device {
return true;
}
bool stop(AndroidApk apk) {
@override
Future<bool> startApp(AndroidApk apk) async {
// Android currently has to be started with startServer(...).
assert(false);
return false;
}
Future<bool> stopApp(AndroidApk apk) async {
// Turn off reverse port forwarding
runSync([adbPath, 'reverse', '--remove', 'tcp:$_serverPort']);
// Stop the app
......
......@@ -28,6 +28,18 @@ Future<int> runCommandAndStreamOutput(List<String> cmd,
return proc.exitCode;
}
Future runAndKill(List<String> cmd, Duration timeout) async {
_logging.info(cmd.join(' '));
Future<Process> proc = Process.start(
cmd[0], cmd.getRange(1, cmd.length).toList(),
mode: ProcessStartMode.DETACHED);
return new Future.delayed(timeout, () async {
_logging.info('Intentionally killing ${cmd[0]}');
Process.killPid((await proc).pid);
});
}
/// Run cmd and return stdout.
/// Throws an error if cmd exits with a non-zero value.
String runCheckedSync(List<String> cmd) =>
......
......@@ -41,22 +41,26 @@ class StartCommand extends Command {
if (android == null) {
android = new AndroidDevice();
}
if (ios == null) {
ios = new IOSDevice();
}
bool startedSomewhere = false;
bool poke = argResults['poke'];
if (!poke) {
StopCommand stopper = new StopCommand(android);
StopCommand stopper = new StopCommand(android: android, ios: ios);
stopper.stop();
// Only install if the user did not specify a poke
InstallCommand installer = new InstallCommand(android: android, ios: ios);
startedSomewhere = installer.install();
installer.install();
}
bool startedOnAndroid = false;
if (android.isConnected()) {
Map<BuildPlatform, ApplicationPackage> packages =
ApplicationPackageFactory.getAvailableApplicationPackages();
bool startedOnAndroid = false;
if (android.isConnected()) {
ApplicationPackage androidApp = packages[BuildPlatform.android];
String target = path.absolute(argResults['target']);
......@@ -64,6 +68,12 @@ class StartCommand extends Command {
target, poke, argResults['checked'], androidApp);
}
if (ios.isConnected()) {
ApplicationPackage iosApp = packages[BuildPlatform.iOS];
startedSomewhere = await ios.startApp(iosApp) || startedSomewhere;
}
if (startedSomewhere || startedOnAndroid) {
return 0;
} else {
......
......@@ -17,29 +17,39 @@ class StopCommand extends Command {
final name = 'stop';
final description = 'Stop your Flutter app on all attached devices.';
AndroidDevice android = null;
IOSDevice ios = null;
StopCommand([this.android]);
StopCommand({this.android, this.ios});
@override
Future<int> run() async {
if (android == null) {
android = new AndroidDevice();
}
if (stop()) {
if (await stop()) {
return 0;
} else {
return 2;
}
}
bool stop() {
Future<bool> stop() async {
if (android == null) {
android = new AndroidDevice();
}
if (ios == null) {
ios = new IOSDevice();
}
bool stoppedSomething = false;
if (android.isConnected()) {
Map<BuildPlatform, ApplicationPackage> packages =
ApplicationPackageFactory.getAvailableApplicationPackages();
if (android.isConnected()) {
ApplicationPackage androidApp = packages[BuildPlatform.android];
stoppedSomething = android.stop(androidApp) || stoppedSomething;
stoppedSomething = await android.stopApp(androidApp) || stoppedSomething;
}
if (ios.isConnected()) {
ApplicationPackage iosApp = packages[BuildPlatform.iOS];
stoppedSomething = await ios.stopApp(iosApp) || stoppedSomething;
}
return stoppedSomething;
......
......@@ -21,11 +21,36 @@ defineTests() {
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(true);
when(android.installApp(any)).thenReturn(true);
when(android.stop(any)).thenReturn(true);
when(android.startServer(any, any, any, any)).thenReturn(true);
when(android.stopApp(any)).thenReturn(true);
MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(false);
when(ios.installApp(any)).thenReturn(false);
when(ios.startApp(any)).thenReturn(false);
when(ios.startApp(any)).thenReturn(false);
StartCommand command = new StartCommand(android: android, ios: ios);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['start']).then((int code) => expect(code, equals(0)));
});
test('returns 0 when iOS is connected and ready to be started', () {
applicationPackageSetup();
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(false);
when(android.installApp(any)).thenReturn(false);
when(android.startServer(any, any, any, any)).thenReturn(false);
when(android.stopApp(any)).thenReturn(false);
MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(true);
when(ios.installApp(any)).thenReturn(true);
when(ios.startApp(any)).thenReturn(true);
when(ios.stopApp(any)).thenReturn(false);
StartCommand command = new StartCommand(android: android, ios: ios);
......
......@@ -20,8 +20,31 @@ defineTests() {
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(true);
when(android.stop(any)).thenReturn(true);
StopCommand command = new StopCommand(android);
when(android.stopApp(any)).thenReturn(true);
MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(false);
when(ios.stopApp(any)).thenReturn(false);
StopCommand command = new StopCommand(android: android, ios: ios);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['stop']).then((int code) => expect(code, equals(0)));
});
test('returns 0 when iOS is connected and ready to be stopped', () {
applicationPackageSetup();
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(false);
when(android.stopApp(any)).thenReturn(false);
MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(true);
when(ios.stopApp(any)).thenReturn(true);
StopCommand command = new StopCommand(android: android, ios: ios);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
......
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