Commit a6a3f212 authored by Ian Fischer's avatar Ian Fischer

IOSSimulator implementation.

Also fixes some minor bugs with iOS and Android interactions.
parent 4191ed49
...@@ -74,6 +74,7 @@ class ApplicationPackageFactory { ...@@ -74,6 +74,7 @@ class ApplicationPackageFactory {
static List<BuildPlatform> defaultBuildPlatforms = [ static List<BuildPlatform> defaultBuildPlatforms = [
BuildPlatform.android, BuildPlatform.android,
BuildPlatform.iOS, BuildPlatform.iOS,
BuildPlatform.iOSSimulator,
]; ];
static Map<BuildPlatform, ApplicationPackage> getAvailableApplicationPackages( static Map<BuildPlatform, ApplicationPackage> getAvailableApplicationPackages(
...@@ -95,6 +96,9 @@ class ApplicationPackageFactory { ...@@ -95,6 +96,9 @@ class ApplicationPackageFactory {
case BuildPlatform.iOS: case BuildPlatform.iOS:
packages[platform] = new IOSApp(buildPath); packages[platform] = new IOSApp(buildPath);
break; break;
case BuildPlatform.iOSSimulator:
packages[platform] = new IOSApp(buildPath);
break;
default: default:
// TODO(iansf): Add other platforms // TODO(iansf): Add other platforms
assert(false); assert(false);
......
...@@ -15,27 +15,38 @@ class InstallCommand extends Command { ...@@ -15,27 +15,38 @@ class InstallCommand extends Command {
final name = 'install'; final name = 'install';
final description = 'Install your Flutter app on attached devices.'; final description = 'Install your Flutter app on attached devices.';
AndroidDevice android = null; AndroidDevice android;
IOSDevice ios; IOSDevice ios;
IOSSimulator iosSim;
InstallCommand({this.android, this.ios}); InstallCommand({this.android, this.ios, this.iosSim}) {
argParser.addFlag('boot',
help: 'Boot the iOS Simulator if it isn\'t already running.');
}
@override @override
Future<int> run() async { Future<int> run() async {
if (install()) { if (install(argResults['boot'])) {
return 0; return 0;
} else { } else {
return 2; return 2;
} }
} }
bool install() { bool install([bool boot = false]) {
if (android == null) { if (android == null) {
android = new AndroidDevice(); android = new AndroidDevice();
} }
if (ios == null) { if (ios == null) {
ios = new IOSDevice(); ios = new IOSDevice();
} }
if (iosSim == null) {
iosSim = new IOSSimulator();
}
if (boot) {
iosSim.boot();
}
bool installedSomewhere = false; bool installedSomewhere = false;
...@@ -43,6 +54,7 @@ class InstallCommand extends Command { ...@@ -43,6 +54,7 @@ class InstallCommand extends Command {
ApplicationPackageFactory.getAvailableApplicationPackages(); ApplicationPackageFactory.getAvailableApplicationPackages();
ApplicationPackage androidApp = packages[BuildPlatform.android]; ApplicationPackage androidApp = packages[BuildPlatform.android];
ApplicationPackage iosApp = packages[BuildPlatform.iOS]; ApplicationPackage iosApp = packages[BuildPlatform.iOS];
ApplicationPackage iosSimApp = packages[BuildPlatform.iOSSimulator];
if (androidApp != null && android.isConnected()) { if (androidApp != null && android.isConnected()) {
installedSomewhere = android.installApp(androidApp) || installedSomewhere; installedSomewhere = android.installApp(androidApp) || installedSomewhere;
...@@ -52,6 +64,10 @@ class InstallCommand extends Command { ...@@ -52,6 +64,10 @@ class InstallCommand extends Command {
installedSomewhere = ios.installApp(iosApp) || installedSomewhere; installedSomewhere = ios.installApp(iosApp) || installedSomewhere;
} }
if (iosSimApp != null && iosSim.isConnected()) {
installedSomewhere = iosSim.installApp(iosSimApp) || installedSomewhere;
}
return installedSomewhere; return installedSomewhere;
} }
} }
...@@ -18,8 +18,9 @@ class ListCommand extends Command { ...@@ -18,8 +18,9 @@ class ListCommand extends Command {
final description = 'List all connected devices.'; final description = 'List all connected devices.';
AndroidDevice android; AndroidDevice android;
IOSDevice ios; IOSDevice ios;
IOSSimulator iosSim;
ListCommand({this.android, this.ios}) { ListCommand({this.android, this.ios, this.iosSim}) {
argParser.addFlag('details', argParser.addFlag('details',
abbr: 'd', abbr: 'd',
negatable: false, negatable: false,
...@@ -54,6 +55,17 @@ class ListCommand extends Command { ...@@ -54,6 +55,17 @@ class ListCommand extends Command {
} }
} }
if (details) {
print('iOS Simulators:');
}
for (IOSSimulator device in IOSSimulator.getAttachedDevices(iosSim)) {
if (details) {
print('${device.id}\t${device.name}');
} else {
print(device.id);
}
}
return 0; return 0;
} }
} }
...@@ -22,22 +22,25 @@ class ListenCommand extends Command { ...@@ -22,22 +22,25 @@ class ListenCommand extends Command {
'on all connected devices.'; 'on all connected devices.';
AndroidDevice android; AndroidDevice android;
IOSDevice ios; IOSDevice ios;
IOSSimulator iosSim;
List<String> watchCommand; List<String> watchCommand;
/// Only run once. Used for testing. /// Only run once. Used for testing.
bool singleRun; bool singleRun;
ListenCommand({this.android, this.ios, this.singleRun: false}) {} ListenCommand({this.android, this.ios, this.iosSim, this.singleRun: false}) {}
@override @override
Future<int> run() async { Future<int> run() async {
if (android == null) { if (android == null) {
android = new AndroidDevice(); android = new AndroidDevice();
} }
if (ios == null) { if (ios == null) {
ios = new IOSDevice(); ios = new IOSDevice();
} }
if (iosSim == null) {
iosSim = new IOSSimulator();
}
if (argResults.rest.length > 0) { if (argResults.rest.length > 0) {
watchCommand = _initWatchCommand(argResults.rest); watchCommand = _initWatchCommand(argResults.rest);
...@@ -49,6 +52,7 @@ class ListenCommand extends Command { ...@@ -49,6 +52,7 @@ class ListenCommand extends Command {
ApplicationPackageFactory.getAvailableApplicationPackages(); ApplicationPackageFactory.getAvailableApplicationPackages();
ApplicationPackage androidApp = packages[BuildPlatform.android]; ApplicationPackage androidApp = packages[BuildPlatform.android];
ApplicationPackage iosApp = packages[BuildPlatform.iOS]; ApplicationPackage iosApp = packages[BuildPlatform.iOS];
ApplicationPackage iosSimApp = packages[BuildPlatform.iOSSimulator];
while (true) { while (true) {
_logging.info('Updating running Sky apps...'); _logging.info('Updating running Sky apps...');
...@@ -66,7 +70,7 @@ class ListenCommand extends Command { ...@@ -66,7 +70,7 @@ class ListenCommand extends Command {
// TODO(iansf): Don't rely on sky-src-path for the snapshotter. // TODO(iansf): Don't rely on sky-src-path for the snapshotter.
'--compiler', '--compiler',
'${globalResults['sky-src-path']}' '${globalResults['sky-src-path']}'
'/out/ios_Debug/clang_x64/sky_snapshot' '/out/ios_sim_Debug/clang_x64/sky_snapshot'
]); ]);
} }
} catch (e) {} } catch (e) {}
...@@ -79,6 +83,10 @@ class ListenCommand extends Command { ...@@ -79,6 +83,10 @@ class ListenCommand extends Command {
await ios.pushFile(iosApp, localFLXPath, remoteFLXPath); await ios.pushFile(iosApp, localFLXPath, remoteFLXPath);
} }
if (iosSim.isConnected()) {
await iosSim.pushFile(iosSimApp, localFLXPath, remoteFLXPath);
}
if (android.isConnected()) { if (android.isConnected()) {
await android.startServer( await android.startServer(
argResults['target'], true, argResults['checked'], androidApp); argResults['target'], true, argResults['checked'], androidApp);
......
...@@ -18,8 +18,9 @@ class LogsCommand extends Command { ...@@ -18,8 +18,9 @@ class LogsCommand extends Command {
final description = 'Show logs for running Sky apps.'; final description = 'Show logs for running Sky apps.';
AndroidDevice android; AndroidDevice android;
IOSDevice ios; IOSDevice ios;
IOSSimulator iosSim;
LogsCommand({this.android, this.ios}) { LogsCommand({this.android, this.ios, this.iosSim}) {
argParser.addFlag('clear', argParser.addFlag('clear',
negatable: false, negatable: false,
help: 'Clear log history before reading from logs (Android only).'); help: 'Clear log history before reading from logs (Android only).');
...@@ -33,6 +34,9 @@ class LogsCommand extends Command { ...@@ -33,6 +34,9 @@ class LogsCommand extends Command {
if (ios == null) { if (ios == null) {
ios = new IOSDevice(); ios = new IOSDevice();
} }
if (iosSim == null) {
iosSim = new IOSSimulator();
}
Future<int> androidLogProcess = null; Future<int> androidLogProcess = null;
if (android.isConnected()) { if (android.isConnected()) {
...@@ -44,6 +48,11 @@ class LogsCommand extends Command { ...@@ -44,6 +48,11 @@ class LogsCommand extends Command {
iosLogProcess = ios.logs(clear: argResults['clear']); iosLogProcess = ios.logs(clear: argResults['clear']);
} }
Future<int> iosSimLogProcess = null;
if (iosSim.isConnected()) {
iosSimLogProcess = iosSim.logs(clear: argResults['clear']);
}
if (androidLogProcess != null) { if (androidLogProcess != null) {
await androidLogProcess; await androidLogProcess;
} }
...@@ -52,6 +61,10 @@ class LogsCommand extends Command { ...@@ -52,6 +61,10 @@ class LogsCommand extends Command {
await iosLogProcess; await iosLogProcess;
} }
if (iosSimLogProcess != null) {
await iosSimLogProcess;
}
return 0; return 0;
} }
} }
...@@ -20,10 +20,11 @@ final Logger _logging = new Logger('sky_tools.start'); ...@@ -20,10 +20,11 @@ final Logger _logging = new Logger('sky_tools.start');
class StartCommand extends Command { class StartCommand extends Command {
final name = 'start'; final name = 'start';
final description = 'Start your Flutter app on attached devices.'; final description = 'Start your Flutter app on attached devices.';
AndroidDevice android = null; AndroidDevice android;
IOSDevice ios = null; IOSDevice ios;
IOSSimulator iosSim;
StartCommand({this.android, this.ios}) { StartCommand({this.android, this.ios, this.iosSim}) {
argParser.addFlag('poke', argParser.addFlag('poke',
negatable: false, negatable: false,
help: 'Restart the connection to the server (Android only).'); help: 'Restart the connection to the server (Android only).');
...@@ -35,6 +36,8 @@ class StartCommand extends Command { ...@@ -35,6 +36,8 @@ class StartCommand extends Command {
defaultsTo: '.', defaultsTo: '.',
abbr: 't', abbr: 't',
help: 'Target app path or filename to start.'); help: 'Target app path or filename to start.');
argParser.addFlag('boot',
help: 'Boot the iOS Simulator if it isn\'t already running.');
} }
@override @override
...@@ -45,6 +48,9 @@ class StartCommand extends Command { ...@@ -45,6 +48,9 @@ class StartCommand extends Command {
if (ios == null) { if (ios == null) {
ios = new IOSDevice(); ios = new IOSDevice();
} }
if (iosSim == null) {
iosSim = new IOSSimulator();
}
bool startedSomewhere = false; bool startedSomewhere = false;
bool poke = argResults['poke']; bool poke = argResults['poke'];
...@@ -53,28 +59,32 @@ class StartCommand extends Command { ...@@ -53,28 +59,32 @@ class StartCommand extends Command {
stopper.stop(); stopper.stop();
// Only install if the user did not specify a poke // Only install if the user did not specify a poke
InstallCommand installer = new InstallCommand(android: android, ios: ios); InstallCommand installer =
installer.install(); new InstallCommand(android: android, ios: ios, iosSim: iosSim);
installer.install(argResults['boot']);
} }
Map<BuildPlatform, ApplicationPackage> packages = Map<BuildPlatform, ApplicationPackage> packages =
ApplicationPackageFactory.getAvailableApplicationPackages(); ApplicationPackageFactory.getAvailableApplicationPackages();
ApplicationPackage androidApp = packages[BuildPlatform.android];
ApplicationPackage iosApp = packages[BuildPlatform.iOS];
ApplicationPackage iosSimApp = packages[BuildPlatform.iOSSimulator];
bool startedOnAndroid = false; bool startedOnAndroid = false;
if (android.isConnected()) { if (androidApp != null && android.isConnected()) {
ApplicationPackage androidApp = packages[BuildPlatform.android];
String target = path.absolute(argResults['target']); String target = path.absolute(argResults['target']);
startedOnAndroid = await android.startServer( startedOnAndroid = await android.startServer(
target, poke, argResults['checked'], androidApp); target, poke, argResults['checked'], androidApp);
} }
if (ios.isConnected()) { if (iosApp != null && ios.isConnected()) {
ApplicationPackage iosApp = packages[BuildPlatform.iOS];
startedSomewhere = await ios.startApp(iosApp) || startedSomewhere; startedSomewhere = await ios.startApp(iosApp) || startedSomewhere;
} }
if (iosSimApp != null && iosSim.isConnected()) {
startedSomewhere = await iosSim.startApp(iosSimApp) || startedSomewhere;
}
if (startedSomewhere || startedOnAndroid) { if (startedSomewhere || startedOnAndroid) {
return 0; return 0;
} else { } else {
......
...@@ -17,10 +17,11 @@ final Logger _logging = new Logger('sky_tools.stop'); ...@@ -17,10 +17,11 @@ final Logger _logging = new Logger('sky_tools.stop');
class StopCommand extends Command { class StopCommand extends Command {
final name = 'stop'; final name = 'stop';
final description = 'Stop your Flutter app on all attached devices.'; final description = 'Stop your Flutter app on all attached devices.';
AndroidDevice android = null; AndroidDevice android;
IOSDevice ios = null; IOSDevice ios;
IOSSimulator iosSim;
StopCommand({this.android, this.ios}); StopCommand({this.android, this.ios, this.iosSim});
@override @override
Future<int> run() async { Future<int> run() async {
...@@ -38,6 +39,9 @@ class StopCommand extends Command { ...@@ -38,6 +39,9 @@ class StopCommand extends Command {
if (ios == null) { if (ios == null) {
ios = new IOSDevice(); ios = new IOSDevice();
} }
if (iosSim == null) {
iosSim = new IOSSimulator();
}
bool stoppedSomething = false; bool stoppedSomething = false;
Map<BuildPlatform, ApplicationPackage> packages = Map<BuildPlatform, ApplicationPackage> packages =
...@@ -53,6 +57,11 @@ class StopCommand extends Command { ...@@ -53,6 +57,11 @@ class StopCommand extends Command {
stoppedSomething = await ios.stopApp(iosApp) || stoppedSomething; stoppedSomething = await ios.stopApp(iosApp) || stoppedSomething;
} }
if (iosSim.isConnected()) {
ApplicationPackage iosApp = packages[BuildPlatform.iOSSimulator];
stoppedSomething = await iosSim.stopApp(iosApp) || stoppedSomething;
}
return stoppedSomething; return stoppedSomething;
} }
} }
...@@ -21,7 +21,7 @@ class TraceCommand extends Command { ...@@ -21,7 +21,7 @@ class TraceCommand extends Command {
'To start a trace, wait, and then stop the trace, don\'t set any flags ' 'To start a trace, wait, and then stop the trace, don\'t set any flags '
'except (optionally) duration.\n' 'except (optionally) duration.\n'
'Otherwise, specify either start or stop to manually control the trace.'; 'Otherwise, specify either start or stop to manually control the trace.';
AndroidDevice android = null; AndroidDevice android;
TraceCommand([this.android]) { TraceCommand([this.android]) {
argParser.addFlag('start', negatable: false, help: 'Start tracing.'); argParser.addFlag('start', negatable: false, help: 'Start tracing.');
......
...@@ -26,6 +26,8 @@ abstract class _Device { ...@@ -26,6 +26,8 @@ abstract class _Device {
id = AndroidDevice.defaultDeviceID; id = AndroidDevice.defaultDeviceID;
} else if (className == IOSDevice.className) { } else if (className == IOSDevice.className) {
id = IOSDevice.defaultDeviceID; id = IOSDevice.defaultDeviceID;
} else if (className == IOSSimulator.className) {
id = IOSSimulator.defaultDeviceID;
} else { } else {
throw 'Attempted to create a Device of unknown type $className'; throw 'Attempted to create a Device of unknown type $className';
} }
...@@ -40,6 +42,10 @@ abstract class _Device { ...@@ -40,6 +42,10 @@ abstract class _Device {
final device = new IOSDevice._(id); final device = new IOSDevice._(id);
_deviceCache[id] = device; _deviceCache[id] = device;
return device; return device;
} else if (className == IOSSimulator.className) {
final device = new IOSSimulator._(id);
_deviceCache[id] = device;
return device;
} else { } else {
throw 'Attempted to create a Device of unknown type $className'; throw 'Attempted to create a Device of unknown type $className';
} }
...@@ -128,10 +134,13 @@ class IOSDevice extends _Device { ...@@ -128,10 +134,13 @@ class IOSDevice extends _Device {
return devices; return devices;
} }
static List<String> _getAttachedDeviceIDs([IOSDevice mockIOS]) { static Iterable<String> _getAttachedDeviceIDs([IOSDevice mockIOS]) {
String listerPath = String listerPath =
(mockIOS != null) ? mockIOS.listerPath : _checkForCommand('idevice_id'); (mockIOS != null) ? mockIOS.listerPath : _checkForCommand('idevice_id');
return runSync([listerPath, '-l']).trim().split('\n'); return runSync([listerPath, '-l'])
.trim()
.split('\n')
.where((String s) => s != null && s.length > 0);
} }
static String _getDeviceName(String deviceID, [IOSDevice mockIOS]) { static String _getDeviceName(String deviceID, [IOSDevice mockIOS]) {
...@@ -163,17 +172,22 @@ class IOSDevice extends _Device { ...@@ -163,17 +172,22 @@ class IOSDevice extends _Device {
@override @override
bool installApp(ApplicationPackage app) { bool installApp(ApplicationPackage app) {
if (id == defaultDeviceID) { try {
runCheckedSync([installerPath, '-i', app.appPath]); if (id == defaultDeviceID) {
} else { runCheckedSync([installerPath, '-i', app.appPath]);
runCheckedSync([installerPath, '-u', id, '-i', app.appPath]); } else {
runCheckedSync([installerPath, '-u', id, '-i', app.appPath]);
}
return true;
} catch (e) {
return false;
} }
return false; return false;
} }
@override @override
bool isConnected() { bool isConnected() {
List<String> ids = _getAttachedDeviceIDs(); Iterable<String> ids = _getAttachedDeviceIDs();
for (String id in ids) { for (String id in ids) {
if (id == this.id || this.id == defaultDeviceID) { if (id == this.id || this.id == defaultDeviceID) {
return true; return true;
...@@ -245,12 +259,234 @@ class IOSDevice extends _Device { ...@@ -245,12 +259,234 @@ class IOSDevice extends _Device {
} }
/// Note that clear is not supported on iOS at this time. /// Note that clear is not supported on iOS at this time.
Future<int> logs({bool clear: false}) { Future<int> logs({bool clear: false}) async {
if (!isConnected()) {
return 2;
}
return runCommandAndStreamOutput([loggerPath], return runCommandAndStreamOutput([loggerPath],
prefix: 'IOS DEV: ', filter: new RegExp(r'.*SkyShell.*')); prefix: 'IOS DEV: ', filter: new RegExp(r'.*SkyShell.*'));
} }
} }
class IOSSimulator extends _Device {
static const String className = 'IOSSimulator';
static final String defaultDeviceID = 'default_ios_sim_id';
static const String _macInstructions =
'To work with iOS devices, please install ideviceinstaller. '
'If you use homebrew, you can install it with '
'"\$ brew install ideviceinstaller".';
static String _xcrunPath = path.join('/usr', 'bin', 'xcrun');
String _iOSSimPath;
String get iOSSimPath => _iOSSimPath;
String get xcrunPath => _xcrunPath;
String _name;
String get name => _name;
factory IOSSimulator({String id, String name, String iOSSimulatorPath}) {
IOSSimulator device = new _Device(className, id);
device._name = name;
if (iOSSimulatorPath == null) {
iOSSimulatorPath = path.join('/Applications', 'iOS Simulator.app',
'Contents', 'MacOS', 'iOS Simulator');
}
device._iOSSimPath = iOSSimulatorPath;
return device;
}
IOSSimulator._(String id) : super._(id) {}
static String _getRunningSimulatorID([IOSSimulator mockIOS]) {
String xcrunPath = mockIOS != null ? mockIOS.xcrunPath : _xcrunPath;
String output = runCheckedSync([xcrunPath, 'simctl', 'list', 'devices']);
Match match;
Iterable<Match> matches = new RegExp(r'[^\(]+\(([^\)]+)\) \(Booted\)',
multiLine: true).allMatches(output);
if (matches.length > 1) {
// More than one simulator is listed as booted, which is not allowed but
// sometimes happens erroneously. Kill them all because we don't know
// which one is actually running.
_logging.warning('Multiple running simulators were detected, '
'which is not supposed to happen.');
for (Match m in matches) {
if (m.groupCount > 0) {
_logging.warning('Killing simulator ${m.group(1)}');
runSync([xcrunPath, 'simctl', 'shutdown', m.group(1)]);
}
}
} else if (matches.length == 1) {
match = matches.first;
}
if (match != null && match.groupCount > 0) {
return match.group(1);
} else {
_logging.info('No running simulators found');
return null;
}
}
String _getSimulatorPath() {
String deviceID = id == defaultDeviceID ? _getRunningSimulatorID() : id;
String homeDirectory = path.absolute(Platform.environment['HOME']);
if (deviceID == null) {
return null;
}
return path.join(homeDirectory, 'Library', 'Developer', 'CoreSimulator',
'Devices', deviceID);
}
String _getSimulatorAppHomeDirectory(ApplicationPackage app) {
String simulatorPath = _getSimulatorPath();
if (simulatorPath == null) {
return null;
}
return path.join(simulatorPath, 'data');
}
static List<IOSSimulator> getAttachedDevices([IOSSimulator mockIOS]) {
List<IOSSimulator> devices = [];
String id = _getRunningSimulatorID(mockIOS);
if (id != null) {
// TODO(iansf): get the simulator's name
// String name = _getDeviceName(id, mockIOS);
devices.add(new IOSSimulator(id: id));
}
return devices;
}
Future<bool> boot() async {
if (!Platform.isMacOS) {
return false;
}
if (isConnected()) {
return true;
}
if (id == defaultDeviceID) {
runDetached([iOSSimPath]);
Future<bool> checkConnection([int attempts = 20]) async {
if (attempts == 0) {
_logging.info('Timed out waiting for iOS Simulator $id to boot.');
return false;
}
if (!isConnected()) {
_logging.info('Waiting for iOS Simulator $id to boot...');
return new Future.delayed(new Duration(milliseconds: 500),
() => checkConnection(attempts - 1));
}
return true;
}
return checkConnection();
} else {
try {
runCheckedSync([xcrunPath, 'simctl', 'boot', id]);
} catch (e) {
_logging.warning('Unable to boot iOS Simulator $id: ', e);
return false;
}
}
return false;
}
@override
bool installApp(ApplicationPackage app) {
if (!isConnected()) {
return false;
}
try {
if (id == defaultDeviceID) {
runCheckedSync([xcrunPath, 'simctl', 'install', 'booted', app.appPath]);
} else {
runCheckedSync([xcrunPath, 'simctl', 'install', id, app.appPath]);
}
return true;
} catch (e) {
return false;
}
}
@override
bool isConnected() {
if (!Platform.isMacOS) {
return false;
}
String simulatorID = _getRunningSimulatorID();
if (simulatorID == null) {
return false;
} else if (id == defaultDeviceID) {
return true;
} else {
return _getRunningSimulatorID() == id;
}
}
@override
bool isAppInstalled(ApplicationPackage app) {
try {
String simulatorHomeDirectory = _getSimulatorAppHomeDirectory(app);
return FileSystemEntity.isDirectorySync(simulatorHomeDirectory);
} catch (e) {
return false;
}
}
@override
Future<bool> startApp(ApplicationPackage app) async {
if (!isAppInstalled(app)) {
return false;
}
try {
if (id == defaultDeviceID) {
runCheckedSync(
[xcrunPath, 'simctl', 'launch', 'booted', app.appPackageID]);
} else {
runCheckedSync([xcrunPath, 'simctl', 'launch', id, app.appPackageID]);
}
return true;
} catch (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;
}
Future<bool> pushFile(
ApplicationPackage app, String localFile, String targetFile) async {
if (Platform.isMacOS) {
String simulatorHomeDirectory = _getSimulatorAppHomeDirectory(app);
runCheckedSync(
['cp', localFile, path.join(simulatorHomeDirectory, targetFile)]);
return true;
}
return false;
}
Future<int> logs({bool clear: false}) async {
if (!isConnected()) {
return 2;
}
String homeDirectory = path.absolute(Platform.environment['HOME']);
String simulatorDeviceID = _getRunningSimulatorID();
String logFilePath = path.join(homeDirectory, 'Library', 'Logs',
'CoreSimulator', simulatorDeviceID, 'system.log');
if (clear) {
runSync(['rm', logFilePath]);
}
return runCommandAndStreamOutput(['tail', '-f', logFilePath],
prefix: 'IOS SIM: ', filter: new RegExp(r'.*SkyShell.*'));
}
}
class AndroidDevice extends _Device { class AndroidDevice extends _Device {
static const String _ADB_PATH = 'adb'; static const String _ADB_PATH = 'adb';
static const String _observatoryPort = '8181'; static const String _observatoryPort = '8181';
...@@ -576,7 +812,11 @@ class AndroidDevice extends _Device { ...@@ -576,7 +812,11 @@ class AndroidDevice extends _Device {
runSync([adbPath, 'logcat', '-c']); runSync([adbPath, 'logcat', '-c']);
} }
Future<int> logs({bool clear: false}) { Future<int> logs({bool clear: false}) async {
if (!isConnected()) {
return 2;
}
if (clear) { if (clear) {
clearLogs(); clearLogs();
} }
......
...@@ -22,7 +22,7 @@ Future<int> runCommandAndStreamOutput(List<String> cmd, ...@@ -22,7 +22,7 @@ Future<int> runCommandAndStreamOutput(List<String> cmd,
proc.stdout.transform(UTF8.decoder).listen((String data) { proc.stdout.transform(UTF8.decoder).listen((String data) {
List<String> dataLines = data.trimRight().split('\n'); List<String> dataLines = data.trimRight().split('\n');
if (filter != null) { if (filter != null) {
dataLines = dataLines.where((String s) => filter.hasMatch(s)); dataLines = dataLines.where((String s) => filter.hasMatch(s)).toList();
} }
if (dataLines.length > 0) { if (dataLines.length > 0) {
stdout.write('$prefix${dataLines.join('\n$prefix')}\n'); stdout.write('$prefix${dataLines.join('\n$prefix')}\n');
...@@ -41,17 +41,21 @@ Future<int> runCommandAndStreamOutput(List<String> cmd, ...@@ -41,17 +41,21 @@ Future<int> runCommandAndStreamOutput(List<String> cmd,
} }
Future runAndKill(List<String> cmd, Duration timeout) async { Future runAndKill(List<String> cmd, Duration timeout) async {
_logging.info(cmd.join(' ')); Future<Process> proc = runDetached(cmd);
Future<Process> proc = Process.start(
cmd[0], cmd.getRange(1, cmd.length).toList(),
mode: ProcessStartMode.DETACHED);
return new Future.delayed(timeout, () async { return new Future.delayed(timeout, () async {
_logging.info('Intentionally killing ${cmd[0]}'); _logging.info('Intentionally killing ${cmd[0]}');
Process.killPid((await proc).pid); Process.killPid((await proc).pid);
}); });
} }
Future<Process> runDetached(List<String> cmd) async {
_logging.info(cmd.join(' '));
Future<Process> proc = Process.start(
cmd[0], cmd.getRange(1, cmd.length).toList(),
mode: ProcessStartMode.DETACHED);
return proc;
}
/// Run cmd and return stdout. /// Run cmd and return stdout.
/// Throws an error if cmd exits with a non-zero value. /// Throws an error if cmd exits with a non-zero value.
String runCheckedSync(List<String> cmd) => String runCheckedSync(List<String> cmd) =>
......
...@@ -26,6 +26,10 @@ defineTests() { ...@@ -26,6 +26,10 @@ defineTests() {
when(ios.isConnected()).thenReturn(false); when(ios.isConnected()).thenReturn(false);
when(ios.installApp(any)).thenReturn(false); when(ios.installApp(any)).thenReturn(false);
MockIOSSimulator iosSim = new MockIOSSimulator();
when(iosSim.isConnected()).thenReturn(false);
when(iosSim.installApp(any)).thenReturn(false);
InstallCommand command = new InstallCommand(android: android, ios: ios); InstallCommand command = new InstallCommand(android: android, ios: ios);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
...@@ -44,7 +48,12 @@ defineTests() { ...@@ -44,7 +48,12 @@ defineTests() {
when(ios.isConnected()).thenReturn(true); when(ios.isConnected()).thenReturn(true);
when(ios.installApp(any)).thenReturn(true); when(ios.installApp(any)).thenReturn(true);
InstallCommand command = new InstallCommand(android: android, ios: ios); MockIOSSimulator iosSim = new MockIOSSimulator();
when(iosSim.isConnected()).thenReturn(false);
when(iosSim.installApp(any)).thenReturn(false);
InstallCommand command =
new InstallCommand(android: android, ios: ios, iosSim: iosSim);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
......
...@@ -30,7 +30,13 @@ defineTests() { ...@@ -30,7 +30,13 @@ defineTests() {
when(ios.installerPath).thenReturn('echo'); when(ios.installerPath).thenReturn('echo');
when(ios.listerPath).thenReturn('echo'); when(ios.listerPath).thenReturn('echo');
ListCommand command = new ListCommand(android: android, ios: ios); MockIOSSimulator iosSim = new MockIOSSimulator();
// Avoid relying on xcrun being installed on the test system.
// Instead, cause the test to run the echo command.
when(iosSim.xcrunPath).thenReturn('echo');
ListCommand command =
new ListCommand(android: android, ios: ios, iosSim: iosSim);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
runner.run(['list']).then((int code) => expect(code, equals(0))); runner.run(['list']).then((int code) => expect(code, equals(0)));
......
...@@ -22,8 +22,10 @@ defineTests() { ...@@ -22,8 +22,10 @@ defineTests() {
when(android.isConnected()).thenReturn(false); when(android.isConnected()).thenReturn(false);
MockIOSDevice ios = new MockIOSDevice(); MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(false); when(ios.isConnected()).thenReturn(false);
ListenCommand command = MockIOSSimulator iosSim = new MockIOSSimulator();
new ListenCommand(android: android, ios: ios, singleRun: true); when(iosSim.isConnected()).thenReturn(false);
ListenCommand command = new ListenCommand(
android: android, ios: ios, iosSim: iosSim, singleRun: true);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
......
...@@ -22,8 +22,11 @@ defineTests() { ...@@ -22,8 +22,11 @@ defineTests() {
when(android.isConnected()).thenReturn(false); when(android.isConnected()).thenReturn(false);
MockIOSDevice ios = new MockIOSDevice(); MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(false); when(ios.isConnected()).thenReturn(false);
MockIOSSimulator iosSim = new MockIOSSimulator();
when(iosSim.isConnected()).thenReturn(false);
LogsCommand command = new LogsCommand(android: android, ios: ios); LogsCommand command =
new LogsCommand(android: android, ios: ios, iosSim: iosSim);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
......
...@@ -16,10 +16,17 @@ class MockIOSDevice extends Mock implements IOSDevice { ...@@ -16,10 +16,17 @@ class MockIOSDevice extends Mock implements IOSDevice {
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
} }
class MockIOSSimulator extends Mock implements IOSSimulator {
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
void applicationPackageSetup() { void applicationPackageSetup() {
ApplicationPackageFactory.srcPath = './'; ApplicationPackageFactory.srcPath = './';
ApplicationPackageFactory.setBuildPath( ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.android, './'); BuildType.prebuilt, BuildPlatform.android, './');
ApplicationPackageFactory.setBuildPath( ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.iOS, './'); BuildType.prebuilt, BuildPlatform.iOS, './');
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.iOSSimulator, './');
} }
...@@ -28,9 +28,16 @@ defineTests() { ...@@ -28,9 +28,16 @@ defineTests() {
when(ios.isConnected()).thenReturn(false); when(ios.isConnected()).thenReturn(false);
when(ios.installApp(any)).thenReturn(false); when(ios.installApp(any)).thenReturn(false);
when(ios.startApp(any)).thenReturn(false); when(ios.startApp(any)).thenReturn(false);
when(ios.startApp(any)).thenReturn(false); when(ios.stopApp(any)).thenReturn(false);
StartCommand command = new StartCommand(android: android, ios: ios); MockIOSSimulator iosSim = new MockIOSSimulator();
when(iosSim.isConnected()).thenReturn(false);
when(iosSim.installApp(any)).thenReturn(false);
when(iosSim.startApp(any)).thenReturn(false);
when(iosSim.stopApp(any)).thenReturn(false);
StartCommand command =
new StartCommand(android: android, ios: ios, iosSim: iosSim);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
...@@ -52,7 +59,14 @@ defineTests() { ...@@ -52,7 +59,14 @@ defineTests() {
when(ios.startApp(any)).thenReturn(true); when(ios.startApp(any)).thenReturn(true);
when(ios.stopApp(any)).thenReturn(false); when(ios.stopApp(any)).thenReturn(false);
StartCommand command = new StartCommand(android: android, ios: ios); MockIOSSimulator iosSim = new MockIOSSimulator();
when(iosSim.isConnected()).thenReturn(false);
when(iosSim.installApp(any)).thenReturn(false);
when(iosSim.startApp(any)).thenReturn(false);
when(iosSim.stopApp(any)).thenReturn(false);
StartCommand command =
new StartCommand(android: android, ios: ios, iosSim: iosSim);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
......
...@@ -26,7 +26,12 @@ defineTests() { ...@@ -26,7 +26,12 @@ defineTests() {
when(ios.isConnected()).thenReturn(false); when(ios.isConnected()).thenReturn(false);
when(ios.stopApp(any)).thenReturn(false); when(ios.stopApp(any)).thenReturn(false);
StopCommand command = new StopCommand(android: android, ios: ios); MockIOSSimulator iosSim = new MockIOSSimulator();
when(iosSim.isConnected()).thenReturn(false);
when(iosSim.stopApp(any)).thenReturn(false);
StopCommand command =
new StopCommand(android: android, ios: ios, iosSim: iosSim);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
...@@ -44,7 +49,12 @@ defineTests() { ...@@ -44,7 +49,12 @@ defineTests() {
when(ios.isConnected()).thenReturn(true); when(ios.isConnected()).thenReturn(true);
when(ios.stopApp(any)).thenReturn(true); when(ios.stopApp(any)).thenReturn(true);
StopCommand command = new StopCommand(android: android, ios: ios); MockIOSSimulator iosSim = new MockIOSSimulator();
when(iosSim.isConnected()).thenReturn(false);
when(iosSim.stopApp(any)).thenReturn(false);
StopCommand command =
new StopCommand(android: android, ios: ios, iosSim: iosSim);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..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