Commit 12192d00 authored by Ian Fischer's avatar Ian Fischer

Beginning implementation of IOSDevice. Implements list and install.

Also update tests to be compatible with the presence of iOS and add tests for list and install.
parent 0364590b
......@@ -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';
......@@ -27,7 +29,7 @@ defineTests() {
test('init flutter-simple', () async {
InitCommand command = new InitCommand();
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
..addCommand(command);
await runner.run(['init', '--out', temp.path])
.then((int code) => expect(code, equals(0)));
......
......@@ -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)));
..addCommand(command);
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