Merge pull request #67 from iansf/ios_basics
Beginning implementation of IOSDevice. Implements list and install.
This commit is contained in:
commit
a9cbe436c4
@ -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)));
|
||||
..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)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user