Commit a9cbe436 authored by Ian Fischer's avatar Ian Fischer

Merge pull request #67 from iansf/ios_basics

Beginning implementation of IOSDevice. Implements list and install.
parents 524d8379 12192d00
......@@ -42,6 +42,15 @@ class AndroidApk extends ApplicationPackage {
: super(path.join(appDir, 'apks'), appPackageID, appFileName);
}
class IOSApp extends ApplicationPackage {
static const String _appName = 'SkyShell.app';
static const String _packageID = 'com.google.SkyShell';
IOSApp(String appDir,
{String appPackageID: _packageID, String appFileName: _appName})
: super(appDir, appPackageID, appFileName);
}
enum BuildType { prebuilt, release, debug, }
enum BuildPlatform { android, iOS, iOSSimulator, mac, linux, }
......@@ -62,7 +71,10 @@ class ApplicationPackageFactory {
static BuildType defaultBuildType = BuildType.prebuilt;
/// Default BuildPlatforms chosen if no BuildPlatforms are specified.
static List<BuildPlatform> defaultBuildPlatforms = [BuildPlatform.android];
static List<BuildPlatform> defaultBuildPlatforms = [
BuildPlatform.android,
BuildPlatform.iOS,
];
static Map<BuildPlatform, ApplicationPackage> getAvailableApplicationPackages(
{BuildType requestedType, List<BuildPlatform> requestedPlatforms}) {
......@@ -80,6 +92,9 @@ class ApplicationPackageFactory {
case BuildPlatform.android:
packages[platform] = new AndroidApk(buildPath);
break;
case BuildPlatform.iOS:
packages[platform] = new IOSApp(buildPath);
break;
default:
// TODO(iansf): Add other platforms
assert(false);
......
......@@ -24,6 +24,8 @@ abstract class _Device {
if (id == null) {
if (className == AndroidDevice.className) {
id = AndroidDevice.defaultDeviceID;
} else if (className == IOSDevice.className) {
id = IOSDevice.defaultDeviceID;
} else {
throw 'Attempted to create a Device of unknown type $className';
}
......@@ -34,6 +36,10 @@ abstract class _Device {
final device = new AndroidDevice._(id);
_deviceCache[id] = device;
return device;
} else if (className == IOSDevice.className) {
final device = new IOSDevice._(id);
_deviceCache[id] = device;
return device;
} else {
throw 'Attempted to create a Device of unknown type $className';
}
......@@ -52,13 +58,120 @@ abstract class _Device {
bool isAppInstalled(ApplicationPackage app);
}
class IOSDevice extends _Device {
static const String className = 'IOSDevice';
static final String defaultDeviceID = 'default_ios_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 const String _linuxInstructions =
'To work with iOS devices, please install ideviceinstaller. '
'On Ubuntu or Debian, you can install it with '
'"\$ apt-get install ideviceinstaller".';
String _installerPath;
String get installerPath => _installerPath;
String _listerPath;
String get listerPath => _listerPath;
String _informerPath;
String get informerPath => _informerPath;
String _name;
String get name => _name;
factory IOSDevice({String id, String name}) {
IOSDevice device = new _Device(className, id);
device._name = name;
return device;
}
IOSDevice._(String id) : super._(id) {
_installerPath = _checkForCommand('ideviceinstaller');
_listerPath = _checkForCommand('idevice_id');
_informerPath = _checkForCommand('ideviceinfo');
}
static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) {
List<IOSDevice> devices = [];
for (String id in _getAttachedDeviceIDs(mockIOS)) {
String name = _getDeviceName(id, mockIOS);
devices.add(new IOSDevice(id: id, name: name));
}
return devices;
}
static List<String> _getAttachedDeviceIDs([IOSDevice mockIOS]) {
String listerPath =
(mockIOS != null) ? mockIOS.listerPath : _checkForCommand('idevice_id');
return runSync([listerPath, '-l']).trim().split('\n');
}
static String _getDeviceName(String deviceID, [IOSDevice mockIOS]) {
String informerPath = (mockIOS != null)
? mockIOS.informerPath
: _checkForCommand('ideviceinfo');
return runSync([informerPath, '-k', 'DeviceName', '-u', deviceID]);
}
static final Map<String, String> _commandMap = {};
static String _checkForCommand(String command,
[String macInstructions = _macInstructions,
String linuxInstructions = _linuxInstructions]) {
return _commandMap.putIfAbsent(command, () {
try {
command = runCheckedSync(['which', command]).trim();
} catch (e) {
if (Platform.isMacOS) {
_logging.severe(macInstructions);
} else if (Platform.isLinux) {
_logging.severe(linuxInstructions);
} else {
_logging.severe('$command is not available on your platform.');
}
exit(2);
}
return command;
});
}
@override
bool installApp(ApplicationPackage app) {
if (id == defaultDeviceID) {
runCheckedSync([installerPath, '-i', app.appPath]);
} else {
runCheckedSync([installerPath, '-u', id, '-i', app.appPath]);
}
return false;
}
@override
bool isConnected() {
List<String> ids = _getAttachedDeviceIDs();
for (String id in ids) {
if (id == this.id || this.id == defaultDeviceID) {
return true;
}
}
return false;
}
@override
bool isAppInstalled(ApplicationPackage app) {
return false;
}
}
class AndroidDevice extends _Device {
static const String _ADB_PATH = 'adb';
static const String _observatoryPort = '8181';
static const String _serverPort = '9888';
static const String className = 'AndroidDevice';
static final String defaultDeviceID = 'default';
static final String defaultDeviceID = 'default_android_device';
String productID;
String modelID;
......@@ -85,10 +198,8 @@ class AndroidDevice extends _Device {
/// we don't have to rely on the test setup having adb available to it.
static List<AndroidDevice> getAttachedDevices([AndroidDevice mockAndroid]) {
List<AndroidDevice> devices = [];
String adbPath = _getAdbPath();
if (mockAndroid != null) {
adbPath = mockAndroid.adbPath;
}
String adbPath =
(mockAndroid != null) ? mockAndroid.adbPath : _getAdbPath();
List<String> output =
runSync([adbPath, 'devices', '-l']).trim().split('\n');
RegExp deviceInfo = new RegExp(
......
......@@ -16,15 +16,12 @@ class InstallCommand extends Command {
final description = 'Install your Flutter app on attached devices.';
AndroidDevice android = null;
IOSDevice ios;
InstallCommand([this.android]);
InstallCommand({this.android, this.ios});
@override
Future<int> run() async {
if (android == null) {
android = new AndroidDevice();
}
if (install()) {
return 0;
} else {
......@@ -33,15 +30,28 @@ class InstallCommand extends Command {
}
bool install() {
if (android == null) {
android = new AndroidDevice();
}
if (ios == null) {
ios = new IOSDevice();
}
bool installedSomewhere = false;
Map<BuildPlatform, ApplicationPackage> packages =
ApplicationPackageFactory.getAvailableApplicationPackages();
ApplicationPackage androidApp = packages[BuildPlatform.android];
ApplicationPackage iosApp = packages[BuildPlatform.iOS];
if (androidApp != null && android.isConnected()) {
installedSomewhere = android.installApp(androidApp) || installedSomewhere;
}
if (iosApp != null && ios.isConnected()) {
installedSomewhere = ios.installApp(iosApp) || installedSomewhere;
}
return installedSomewhere;
}
}
......@@ -16,8 +16,9 @@ class ListCommand extends Command {
final name = 'list';
final description = 'List all connected devices.';
AndroidDevice android;
IOSDevice ios;
ListCommand([this.android]) {
ListCommand({this.android, this.ios}) {
argParser.addFlag('details',
abbr: 'd',
negatable: false,
......@@ -40,6 +41,18 @@ class ListCommand extends Command {
print(device.id);
}
}
if (details) {
print('iOS Devices:');
}
for (IOSDevice device in IOSDevice.getAttachedDevices(ios)) {
if (details) {
print('${device.id}\t${device.name}');
} else {
print(device.id);
}
}
return 0;
}
}
......@@ -20,8 +20,9 @@ class StartCommand extends Command {
final name = 'start';
final description = 'Start your Flutter app on attached devices.';
AndroidDevice android = null;
IOSDevice ios = null;
StartCommand([this.android]) {
StartCommand({this.android, this.ios}) {
argParser.addFlag('poke',
negatable: false,
help: 'Restart the connection to the server (Android only).');
......@@ -48,7 +49,7 @@ class StartCommand extends Command {
stopper.stop();
// Only install if the user did not specify a poke
InstallCommand installer = new InstallCommand(android);
InstallCommand installer = new InstallCommand(android: android, ios: ios);
startedSomewhere = installer.install();
}
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This test can take a while due to network requests
@Timeout(const Duration(seconds: 60))
import 'dart:io';
import 'package:args/command_runner.dart';
......
......@@ -6,7 +6,6 @@ library install_test;
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/install.dart';
import 'package:test/test.dart';
......@@ -17,19 +16,39 @@ main() => defineTests();
defineTests() {
group('install', () {
test('returns 0 when Android is connected and ready for an install', () {
ApplicationPackageFactory.srcPath = './';
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.android, './');
applicationPackageSetup();
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(true);
when(android.installApp(any)).thenReturn(true);
InstallCommand command = new InstallCommand(android);
MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(false);
when(ios.installApp(any)).thenReturn(false);
InstallCommand command = new InstallCommand(android: android, ios: ios);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['install']).then((int code) => expect(code, equals(0)));
});
test('returns 0 when iOS is connected and ready for an install', () {
applicationPackageSetup();
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(false);
when(android.installApp(any)).thenReturn(false);
MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(true);
when(ios.installApp(any)).thenReturn(true);
InstallCommand command = new InstallCommand(android: android, ios: ios);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['install'])
.then((int code) => expect(code, equals(0)));
runner.run(['install']).then((int code) => expect(code, equals(0)));
});
});
}
......@@ -16,12 +16,21 @@ main() => defineTests();
defineTests() {
group('list', () {
test('returns 0 when called', () {
applicationPackageSetup();
MockAndroidDevice android = new MockAndroidDevice();
// Avoid relying on adb being installed on the test system.
// Instead, cause the test to run the echo command.
when(android.adbPath).thenReturn('echo');
ListCommand command = new ListCommand(android);
MockIOSDevice ios = new MockIOSDevice();
// Avoid relying on idevice* being installed on the test system.
// Instead, cause the test to run the echo command.
when(ios.informerPath).thenReturn('echo');
when(ios.installerPath).thenReturn('echo');
when(ios.listerPath).thenReturn('echo');
ListCommand command = new ListCommand(android: android, ios: ios);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['list']).then((int code) => expect(code, equals(0)));
......
......@@ -6,7 +6,6 @@ library stop_test;
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/listen.dart';
import 'package:test/test.dart';
......@@ -17,9 +16,7 @@ main() => defineTests();
defineTests() {
group('listen', () {
test('returns 0 when no device is connected', () {
ApplicationPackageFactory.srcPath = './';
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.android, './');
applicationPackageSetup();
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(false);
......
......@@ -6,7 +6,6 @@ library stop_test;
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/logs.dart';
import 'package:test/test.dart';
......@@ -17,9 +16,7 @@ main() => defineTests();
defineTests() {
group('logs', () {
test('returns 0 when no device is connected', () {
ApplicationPackageFactory.srcPath = './';
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.android, './');
applicationPackageSetup();
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(false);
......
......@@ -3,9 +3,23 @@
// found in the LICENSE file.
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/device.dart';
class MockAndroidDevice extends Mock implements AndroidDevice {
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockIOSDevice extends Mock implements IOSDevice {
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
void applicationPackageSetup() {
ApplicationPackageFactory.srcPath = './';
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.android, './');
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.iOS, './');
}
......@@ -6,7 +6,6 @@ library start_test;
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/start.dart';
import 'package:test/test.dart';
......@@ -17,15 +16,18 @@ main() => defineTests();
defineTests() {
group('start', () {
test('returns 0 when Android is connected and ready to be started', () {
ApplicationPackageFactory.srcPath = './';
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.android, './');
applicationPackageSetup();
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(true);
when(android.installApp(any)).thenReturn(true);
when(android.stop(any)).thenReturn(true);
StartCommand command = new StartCommand(android);
MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(false);
when(ios.installApp(any)).thenReturn(false);
StartCommand command = new StartCommand(android: android, ios: ios);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
......
......@@ -6,7 +6,6 @@ library stop_test;
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/stop.dart';
import 'package:test/test.dart';
......@@ -17,9 +16,7 @@ main() => defineTests();
defineTests() {
group('stop', () {
test('returns 0 when Android is connected and ready to be stopped', () {
ApplicationPackageFactory.srcPath = './';
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.android, './');
applicationPackageSetup();
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(true);
......
......@@ -6,7 +6,6 @@ library trace_test;
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/trace.dart';
import 'package:test/test.dart';
......@@ -17,9 +16,7 @@ main() => defineTests();
defineTests() {
group('trace', () {
test('returns 1 when no Android device is connected', () {
ApplicationPackageFactory.srcPath = './';
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.android, './');
applicationPackageSetup();
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(false);
......
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