refactor: unify target resolution for devices & emulators (#1101)

* refactor: unify target resolution for devices & emulators
* fix: use unified target methods in platform-centric bins
This commit is contained in:
Raphael von der Grün 2021-04-09 08:37:56 +02:00 committed by GitHub
parent c774bf3311
commit c04ea9b1c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 339 additions and 466 deletions

View File

@ -1,48 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var build = require('./build');
var Adb = require('./Adb');
var CordovaError = require('cordova-common').CordovaError;
/**
* Returns a promise for the list of the device ID's found
*/
module.exports.list = async () => {
return (await Adb.devices())
.filter(id => !id.startsWith('emulator-'));
};
module.exports.resolveTarget = function (target) {
return this.list().then(function (device_list) {
if (!device_list || !device_list.length) {
return Promise.reject(new CordovaError('Failed to deploy to device, no devices found.'));
}
// default device
target = target || device_list[0];
if (device_list.indexOf(target) < 0) {
return Promise.reject(new CordovaError('ERROR: Unable to find target \'' + target + '\'.'));
}
return build.detectArchitecture(target).then(function (arch) {
return { target: target, arch: arch, isEmulator: false };
});
});
};

View File

@ -20,7 +20,6 @@
const execa = require('execa');
const fs = require('fs-extra');
var android_versions = require('android-versions');
var build = require('./build');
var path = require('path');
var Adb = require('./Adb');
var events = require('cordova-common').events;
@ -349,21 +348,3 @@ module.exports.wait_for_boot = function (emulator_id, time_remaining) {
}
});
};
module.exports.resolveTarget = function (target) {
return this.list_started().then(function (emulator_list) {
if (emulator_list.length < 1) {
return Promise.reject(new CordovaError('No running Android emulators found, please start an emulator before deploying your project.'));
}
// default emulator
target = target || emulator_list[0];
if (emulator_list.indexOf(target) < 0) {
return Promise.reject(new CordovaError('Unable to find target \'' + target + '\'. Failed to deploy to emulator.'));
}
return build.detectArchitecture(target).then(function (arch) {
return { target: target, arch: arch, isEmulator: true };
});
});
};

View File

@ -19,21 +19,21 @@
under the License.
*/
const { install } = require('./target');
var device = require('./device');
const { resolve, install } = require('./target');
var args = process.argv;
const targetSpec = { type: 'device' };
if (args.length > 2) {
var install_target;
if (args[2].substring(0, 9) === '--target=') {
install_target = args[2].substring(9, args[2].length);
targetSpec.id = args[2].substring(9, args[2].length);
} else {
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
process.exit(2);
}
}
device.resolveTarget(install_target).then(install).catch(err => {
resolve(targetSpec).then(install).catch(err => {
console.error('ERROR: ' + err);
process.exit(2);
});

View File

@ -19,21 +19,21 @@
under the License.
*/
const { install } = require('./target');
var emulator = require('./emulator');
var args = process.argv;
const { resolve, install } = require('./target');
var args = process.argv;
const targetSpec = { type: 'emulator' };
var install_target;
if (args.length > 2) {
if (args[2].substring(0, 9) === '--target=') {
install_target = args[2].substring(9, args[2].length);
targetSpec.id = args[2].substring(9, args[2].length);
} else {
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
process.exit(2);
}
}
emulator.resolveTarget(install_target).then(install).catch(err => {
resolve(targetSpec).then(install).catch(err => {
console.error('ERROR: ' + err);
process.exit(2);
});

View File

@ -19,14 +19,16 @@
under the License.
*/
var devices = require('./device');
const { list } = require('./target');
// Usage support for when args are given
require('./check_reqs').check_android().then(function () {
devices.list().then(function (device_list) {
device_list && device_list.forEach(function (dev) {
console.log(dev);
});
list().then(targets => {
const deviceIds = targets
.filter(({ type }) => type === 'device')
.map(({ id }) => id);
console.log(deviceIds.join('\n'));
}, function (err) {
console.error('ERROR: ' + err);
process.exit(2);

View File

@ -19,22 +19,30 @@
var path = require('path');
var emulator = require('./emulator');
var device = require('./device');
const target = require('./target');
var PackageType = require('./PackageType');
const { CordovaError, events } = require('cordova-common');
const { events } = require('cordova-common');
function getInstallTarget (runOptions) {
var install_target;
/**
* Builds a target spec from a runOptions object
*
* @param {{target?: string, device?: boolean, emulator?: boolean}} runOptions
* @return {target.TargetSpec}
*/
function buildTargetSpec (runOptions) {
const spec = {};
if (runOptions.target) {
install_target = runOptions.target;
spec.id = runOptions.target;
} else if (runOptions.device) {
install_target = '--device';
spec.type = 'device';
} else if (runOptions.emulator) {
install_target = '--emulator';
spec.type = 'emulator';
}
return spec;
}
return install_target;
function formatResolvedTarget ({ id, type }) {
return `${type} ${id}`;
}
/**
@ -51,55 +59,11 @@ module.exports.run = function (runOptions) {
runOptions = runOptions || {};
var self = this;
var install_target = getInstallTarget(runOptions);
const spec = buildTargetSpec(runOptions);
return target.resolve(spec).then(function (resolvedTarget) {
events.emit('log', `Deploying to ${formatResolvedTarget(resolvedTarget)}`);
return Promise.resolve().then(function () {
if (!install_target) {
// no target given, deploy to device if available, otherwise use the emulator.
return device.list().then(function (device_list) {
if (device_list.length > 0) {
events.emit('warn', 'No target specified, deploying to device \'' + device_list[0] + '\'.');
install_target = device_list[0];
} else {
events.emit('warn', 'No target specified and no devices found, deploying to emulator');
install_target = '--emulator';
}
});
}
}).then(function () {
if (install_target === '--device') {
return device.resolveTarget(null);
} else if (install_target === '--emulator') {
// Give preference to any already started emulators. Else, start one.
return emulator.list_started().then(function (started) {
return started && started.length > 0 ? started[0] : emulator.start();
}).then(function (emulatorId) {
return emulator.resolveTarget(emulatorId);
});
}
// They specified a specific device/emulator ID.
return device.list().then(function (devices) {
if (devices.indexOf(install_target) > -1) {
return device.resolveTarget(install_target);
}
return emulator.list_started().then(function (started_emulators) {
if (started_emulators.indexOf(install_target) > -1) {
return emulator.resolveTarget(install_target);
}
return emulator.list_images().then(function (avds) {
// if target emulator isn't started, then start it.
for (var avd in avds) {
if (avds[avd].name === install_target) {
return emulator.start(install_target).then(function (emulatorId) {
return emulator.resolveTarget(emulatorId);
});
}
}
return Promise.reject(new CordovaError(`Target '${install_target}' not found, unable to run project`));
});
});
});
}).then(function (resolvedTarget) {
return new Promise((resolve) => {
const buildOptions = require('./build').parseBuildOptions(runOptions, null, self.root);
@ -112,7 +76,7 @@ module.exports.run = function (runOptions) {
resolve(self._builder.fetchBuildResults(buildOptions.buildType, buildOptions.arch));
}).then(async function (buildResults) {
if (resolvedTarget && resolvedTarget.isEmulator) {
if (resolvedTarget.type === 'emulator') {
await emulator.wait_for_boot(resolvedTarget.id);
}

View File

@ -18,17 +18,97 @@
*/
const path = require('path');
const { inspect } = require('util');
const Adb = require('./Adb');
const build = require('./build');
const emulator = require('./emulator');
const AndroidManifest = require('./AndroidManifest');
const { compareBy } = require('./utils');
const { retryPromise } = require('./retry');
const { events } = require('cordova-common');
const { events, CordovaError } = require('cordova-common');
const INSTALL_COMMAND_TIMEOUT = 5 * 60 * 1000;
const NUM_INSTALL_RETRIES = 3;
const EXEC_KILL_SIGNAL = 'SIGKILL';
exports.install = async function ({ target, arch, isEmulator }, buildResults) {
/**
* @typedef { 'device' | 'emulator' } TargetType
* @typedef { { id: string, type: TargetType } } Target
* @typedef { { id?: string, type?: TargetType } } TargetSpec
*/
/**
* Returns a list of available targets (connected devices & started emulators)
*
* @return {Promise<Target[]>}
*/
exports.list = async () => {
return (await Adb.devices())
.map(id => ({
id,
type: id.startsWith('emulator-') ? 'emulator' : 'device'
}));
};
/**
* @param {TargetSpec?} spec
* @return {Promise<Target>}
*/
async function resolveToOnlineTarget (spec = {}) {
const targetList = await exports.list();
if (targetList.length === 0) return null;
// Sort by type: devices first, then emulators.
targetList.sort(compareBy(t => t.type));
// Find first matching target for spec. {} matches any target.
return targetList.find(target =>
Object.keys(spec).every(k => spec[k] === target[k])
) || null;
}
async function isEmulatorName (name) {
const emus = await emulator.list_images();
return emus.some(avd => avd.name === name);
}
/**
* @param {TargetSpec?} spec
* @return {Promise<Target>}
*/
async function resolveToOfflineEmulator (spec = {}) {
if (spec.type === 'device') return null;
if (spec.id && !(await isEmulatorName(spec.id))) return null;
// try to start an emulator with name spec.id
// if spec.id is undefined, picks best match regarding target API
const emulatorId = await emulator.start(spec.id);
return { id: emulatorId, type: 'emulator' };
}
/**
* @param {TargetSpec?} spec
* @return {Promise<Target & {arch: string}>}
*/
exports.resolve = async (spec = {}) => {
events.emit('verbose', `Trying to find target matching ${inspect(spec)}`);
const resolvedTarget =
(await resolveToOnlineTarget(spec)) ||
(await resolveToOfflineEmulator(spec));
if (!resolvedTarget) {
throw new CordovaError(`Could not find target matching ${inspect(spec)}`);
}
return {
...resolvedTarget,
arch: await build.detectArchitecture(resolvedTarget.id)
};
};
exports.install = async function ({ id: target, arch, type }, buildResults) {
const apk_path = build.findBestApkForArchitecture(buildResults, arch);
const manifest = new AndroidManifest(path.join(__dirname, '../../app/src/main/AndroidManifest.xml'));
const pkgName = manifest.getPackageId();
@ -56,7 +136,7 @@ exports.install = async function ({ target, arch, isEmulator }, buildResults) {
}
}
if (isEmulator) {
if (type === 'emulator') {
// Work around sporadic emulator hangs: http://issues.apache.org/jira/browse/CB-9119
await retryPromise(NUM_INSTALL_RETRIES, () => doInstall({
timeout: INSTALL_COMMAND_TIMEOUT,

View File

@ -1,106 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
const rewire = require('rewire');
const CordovaError = require('cordova-common').CordovaError;
describe('device', () => {
const DEVICE_LIST = ['device1', 'device2', 'device3'];
let AdbSpy;
let device;
beforeEach(() => {
device = rewire('../../bin/templates/cordova/lib/device');
AdbSpy = jasmine.createSpyObj('Adb', ['devices', 'install', 'shell', 'start', 'uninstall']);
device.__set__('Adb', AdbSpy);
});
describe('list', () => {
it('should return a list of all connected devices', () => {
AdbSpy.devices.and.resolveTo(['emulator-5556', '123a76565509e124']);
return device.list().then(list => {
expect(list).toEqual(['123a76565509e124']);
});
});
});
describe('resolveTarget', () => {
let buildSpy;
beforeEach(() => {
buildSpy = jasmine.createSpyObj('build', ['detectArchitecture']);
buildSpy.detectArchitecture.and.returnValue(Promise.resolve());
device.__set__('build', buildSpy);
spyOn(device, 'list').and.returnValue(Promise.resolve(DEVICE_LIST));
});
it('should select the first device to be the target if none is specified', () => {
return device.resolveTarget().then(deviceInfo => {
expect(deviceInfo.target).toBe(DEVICE_LIST[0]);
});
});
it('should use the given target instead of the default', () => {
return device.resolveTarget(DEVICE_LIST[2]).then(deviceInfo => {
expect(deviceInfo.target).toBe(DEVICE_LIST[2]);
});
});
it('should set emulator to false', () => {
return device.resolveTarget().then(deviceInfo => {
expect(deviceInfo.isEmulator).toBe(false);
});
});
it('should throw an error if there are no devices', () => {
device.list.and.returnValue(Promise.resolve([]));
return device.resolveTarget().then(
() => fail('Unexpectedly resolved'),
err => {
expect(err).toEqual(jasmine.any(CordovaError));
}
);
});
it('should throw an error if the specified target does not exist', () => {
return device.resolveTarget('nonexistent-target').then(
() => fail('Unexpectedly resolved'),
err => {
expect(err).toEqual(jasmine.any(CordovaError));
}
);
});
it('should detect the architecture and return it with the device info', () => {
const target = DEVICE_LIST[1];
const arch = 'unittestarch';
buildSpy.detectArchitecture.and.returnValue(Promise.resolve(arch));
return device.resolveTarget(target).then(deviceInfo => {
expect(buildSpy.detectArchitecture).toHaveBeenCalledWith(target);
expect(deviceInfo.arch).toBe(arch);
});
});
});
});

View File

@ -26,7 +26,6 @@ const CordovaError = require('cordova-common').CordovaError;
const check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
describe('emulator', () => {
const EMULATOR_LIST = ['emulator-5555', 'emulator-5556', 'emulator-5557'];
let emu;
beforeEach(() => {
@ -529,50 +528,4 @@ describe('emulator', () => {
});
});
});
describe('resolveTarget', () => {
const arch = 'arm7-test';
beforeEach(() => {
const buildSpy = jasmine.createSpyObj('build', ['detectArchitecture']);
buildSpy.detectArchitecture.and.returnValue(Promise.resolve(arch));
emu.__set__('build', buildSpy);
spyOn(emu, 'list_started').and.returnValue(Promise.resolve(EMULATOR_LIST));
});
it('should throw an error if there are no running emulators', () => {
emu.list_started.and.returnValue(Promise.resolve([]));
return emu.resolveTarget().then(
() => fail('Unexpectedly resolved'),
err => {
expect(err).toEqual(jasmine.any(CordovaError));
}
);
});
it('should throw an error if the requested emulator is not running', () => {
const targetEmulator = 'unstarted-emu';
return emu.resolveTarget(targetEmulator).then(
() => fail('Unexpectedly resolved'),
err => {
expect(err.message).toContain(targetEmulator);
}
);
});
it('should return info on the first running emulator if none is specified', () => {
return emu.resolveTarget().then(emulatorInfo => {
expect(emulatorInfo.target).toBe(EMULATOR_LIST[0]);
});
});
it('should return the emulator info', () => {
return emu.resolveTarget(EMULATOR_LIST[1]).then(emulatorInfo => {
expect(emulatorInfo).toEqual({ target: EMULATOR_LIST[1], arch, isEmulator: true });
});
});
});
});

View File

@ -25,45 +25,38 @@ describe('run', () => {
beforeEach(() => {
run = rewire('../../bin/templates/cordova/lib/run');
run.__set__({
events: jasmine.createSpyObj('eventsSpy', ['emit'])
});
});
describe('getInstallTarget', () => {
const targetOpts = { target: 'emu' };
const deviceOpts = { device: true };
const emulatorOpts = { emulator: true };
const emptyOpts = {};
describe('buildTargetSpec', () => {
it('Test#001 : should select correct target based on the run opts', () => {
const getInstallTarget = run.__get__('getInstallTarget');
expect(getInstallTarget(targetOpts)).toBe('emu');
expect(getInstallTarget(deviceOpts)).toBe('--device');
expect(getInstallTarget(emulatorOpts)).toBe('--emulator');
expect(getInstallTarget(emptyOpts)).toBeUndefined();
const buildTargetSpec = run.__get__('buildTargetSpec');
expect(buildTargetSpec({ target: 'emu' })).toEqual({ id: 'emu' });
expect(buildTargetSpec({ device: true })).toEqual({ type: 'device' });
expect(buildTargetSpec({ emulator: true })).toEqual({ type: 'emulator' });
expect(buildTargetSpec({})).toEqual({});
});
});
describe('run method', () => {
let deviceSpyObj;
let emulatorSpyObj;
let targetSpyObj;
let eventsSpyObj;
let getInstallTargetSpy;
let targetSpyObj, emulatorSpyObj, resolvedTarget;
beforeEach(() => {
deviceSpyObj = jasmine.createSpyObj('deviceSpy', ['list', 'resolveTarget']);
emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', ['list_images', 'list_started', 'resolveTarget', 'start', 'wait_for_boot']);
eventsSpyObj = jasmine.createSpyObj('eventsSpy', ['emit']);
getInstallTargetSpy = jasmine.createSpy('getInstallTargetSpy');
resolvedTarget = { id: 'dev1', type: 'device', arch: 'atari' };
targetSpyObj = jasmine.createSpyObj('target', ['install']);
targetSpyObj = jasmine.createSpyObj('deviceSpy', ['resolve', 'install']);
targetSpyObj.resolve.and.resolveTo(resolvedTarget);
targetSpyObj.install.and.resolveTo();
emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', ['wait_for_boot']);
emulatorSpyObj.wait_for_boot.and.resolveTo();
run.__set__({
device: deviceSpyObj,
emulator: emulatorSpyObj,
target: targetSpyObj,
events: eventsSpyObj,
getInstallTarget: getInstallTargetSpy
emulator: emulatorSpyObj
});
// run needs `this` to behave like an Api instance
@ -72,152 +65,19 @@ describe('run', () => {
});
});
it('should run on default device when no target arguments are specified', () => {
const deviceList = ['testDevice1', 'testDevice2'];
getInstallTargetSpy.and.returnValue(null);
deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
it('should install on target after build', () => {
return run.run().then(() => {
expect(deviceSpyObj.resolveTarget).toHaveBeenCalledWith(deviceList[0]);
});
});
it('should run on emulator when no target arguments are specified, and no devices are found', () => {
const deviceList = [];
getInstallTargetSpy.and.returnValue(null);
deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
emulatorSpyObj.list_started.and.returnValue(Promise.resolve([]));
return run.run().then(() => {
expect(emulatorSpyObj.list_started).toHaveBeenCalled();
});
});
it('should run on default device when device is requested, but none specified', () => {
getInstallTargetSpy.and.returnValue('--device');
return run.run().then(() => {
// Default device is selected by calling device.resolveTarget(null)
expect(deviceSpyObj.resolveTarget).toHaveBeenCalledWith(null);
});
});
it('should run on a running emulator if one exists', () => {
const emulatorList = ['emulator1', 'emulator2'];
getInstallTargetSpy.and.returnValue('--emulator');
emulatorSpyObj.list_started.and.returnValue(Promise.resolve(emulatorList));
return run.run().then(() => {
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(emulatorList[0]);
});
});
it('should start an emulator and run on that if none is running', () => {
const emulatorList = [];
const defaultEmulator = 'default-emu';
getInstallTargetSpy.and.returnValue('--emulator');
emulatorSpyObj.list_started.and.returnValue(Promise.resolve(emulatorList));
emulatorSpyObj.start.and.returnValue(Promise.resolve(defaultEmulator));
return run.run().then(() => {
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(defaultEmulator);
});
});
it('should run on a named device if it is specified', () => {
const deviceList = ['device1', 'device2', 'device3'];
getInstallTargetSpy.and.returnValue(deviceList[1]);
deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
return run.run().then(() => {
expect(deviceSpyObj.resolveTarget).toHaveBeenCalledWith(deviceList[1]);
});
});
it('should run on a named emulator if it is specified', () => {
const startedEmulatorList = ['emu1', 'emu2', 'emu3'];
getInstallTargetSpy.and.returnValue(startedEmulatorList[2]);
deviceSpyObj.list.and.returnValue(Promise.resolve([]));
emulatorSpyObj.list_started.and.returnValue(Promise.resolve(startedEmulatorList));
return run.run().then(() => {
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(startedEmulatorList[2]);
});
});
it('should start named emulator and then run on it if it is specified', () => {
const emulatorList = [
{ name: 'emu1', id: 1 },
{ name: 'emu2', id: 2 },
{ name: 'emu3', id: 3 }
];
getInstallTargetSpy.and.returnValue(emulatorList[2].name);
deviceSpyObj.list.and.returnValue(Promise.resolve([]));
emulatorSpyObj.list_started.and.returnValue(Promise.resolve([]));
emulatorSpyObj.list_images.and.returnValue(Promise.resolve(emulatorList));
emulatorSpyObj.start.and.returnValue(Promise.resolve(emulatorList[2].id));
return run.run().then(() => {
expect(emulatorSpyObj.start).toHaveBeenCalledWith(emulatorList[2].name);
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(emulatorList[2].id);
});
});
it('should throw an error if target is specified but does not exist', () => {
const emulatorList = [{ name: 'emu1', id: 1 }];
const deviceList = ['device1'];
const target = 'nonexistentdevice';
getInstallTargetSpy.and.returnValue(target);
deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
emulatorSpyObj.list_started.and.returnValue(Promise.resolve([]));
emulatorSpyObj.list_images.and.returnValue(Promise.resolve(emulatorList));
return run.run().then(
() => fail('Expected error to be thrown'),
err => expect(err.message).toContain(target)
);
});
it('should install on device after build', () => {
const deviceTarget = { target: 'device1', isEmulator: false };
getInstallTargetSpy.and.returnValue('--device');
deviceSpyObj.resolveTarget.and.returnValue(deviceTarget);
return run.run().then(() => {
expect(targetSpyObj.install).toHaveBeenCalledWith(deviceTarget, { apkPaths: [], buildType: 'debug' });
});
});
it('should install on emulator after build', () => {
const emulatorTarget = { target: 'emu1', isEmulator: true };
getInstallTargetSpy.and.returnValue('--emulator');
emulatorSpyObj.list_started.and.returnValue(Promise.resolve([emulatorTarget.target]));
emulatorSpyObj.resolveTarget.and.returnValue(emulatorTarget);
emulatorSpyObj.wait_for_boot.and.returnValue(Promise.resolve());
return run.run().then(() => {
expect(targetSpyObj.install).toHaveBeenCalledWith(emulatorTarget, { apkPaths: [], buildType: 'debug' });
expect(targetSpyObj.install).toHaveBeenCalledWith(
resolvedTarget,
{ apkPaths: [], buildType: 'debug' }
);
});
});
it('should fail with the error message if --packageType=bundle setting is used', () => {
const deviceList = ['testDevice1', 'testDevice2'];
getInstallTargetSpy.and.returnValue(null);
deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
return run.run({ argv: ['--packageType=bundle'] }).then(
() => fail('Expected error to be thrown'),
err => expect(err).toContain('Package type "bundle" is not supported during cordova run.')
);
targetSpyObj.resolve.and.resolveTo(resolvedTarget);
return expectAsync(run.run({ argv: ['--packageType=bundle'] }))
.toBeRejectedWith(jasmine.stringMatching(/Package type "bundle" is not supported/));
});
});

View File

@ -27,6 +27,193 @@ describe('target', () => {
target = rewire('../../bin/templates/cordova/lib/target');
});
describe('list', () => {
it('should return available targets from Adb.devices', () => {
const AdbSpy = jasmine.createSpyObj('Adb', ['devices']);
AdbSpy.devices.and.resolveTo(['emulator-5556', '123a76565509e124']);
target.__set__('Adb', AdbSpy);
return target.list().then(emus => {
expect(emus).toEqual([
{ id: 'emulator-5556', type: 'emulator' },
{ id: '123a76565509e124', type: 'device' }
]);
});
});
});
describe('resolveToOnlineTarget', () => {
let resolveToOnlineTarget, emus, devs;
beforeEach(() => {
resolveToOnlineTarget = target.__get__('resolveToOnlineTarget');
emus = [
{ id: 'emu1', type: 'emulator' },
{ id: 'emu2', type: 'emulator' }
];
devs = [
{ id: 'dev1', type: 'device' },
{ id: 'dev2', type: 'device' }
];
spyOn(target, 'list').and.returnValue([...emus, ...devs]);
});
it('should return first device when no target arguments are specified', async () => {
return resolveToOnlineTarget().then(result => {
expect(result.id).toBe('dev1');
});
});
it('should return first emulator when no target arguments are specified and no devices are found', async () => {
target.list.and.resolveTo(emus);
return resolveToOnlineTarget().then(result => {
expect(result.id).toBe('emu1');
});
});
it('should return first device when type device is specified', async () => {
return resolveToOnlineTarget({ type: 'device' }).then(result => {
expect(result.id).toBe('dev1');
});
});
it('should return first running emulator when type emulator is specified', async () => {
return resolveToOnlineTarget({ type: 'emulator' }).then(result => {
expect(result.id).toBe('emu1');
});
});
it('should return a device that matches given ID', async () => {
return resolveToOnlineTarget({ id: 'dev2' }).then(result => {
expect(result.id).toBe('dev2');
});
});
it('should return a running emulator that matches given ID', async () => {
return resolveToOnlineTarget({ id: 'emu2' }).then(result => {
expect(result.id).toBe('emu2');
});
});
it('should return null if there are no online targets', async () => {
target.list.and.resolveTo([]);
return expectAsync(resolveToOnlineTarget())
.toBeResolvedTo(null);
});
it('should return null if no target matches given ID', async () => {
return expectAsync(resolveToOnlineTarget({ id: 'foo' }))
.toBeResolvedTo(null);
});
it('should return null if no target matches given type', async () => {
target.list.and.resolveTo(devs);
return expectAsync(resolveToOnlineTarget({ type: 'emulator' }))
.toBeResolvedTo(null);
});
});
describe('resolveToOfflineEmulator', () => {
const emuId = 'emulator-5554';
let resolveToOfflineEmulator, emulatorSpyObj;
beforeEach(() => {
resolveToOfflineEmulator = target.__get__('resolveToOfflineEmulator');
emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', ['start']);
emulatorSpyObj.start.and.resolveTo(emuId);
target.__set__({
emulator: emulatorSpyObj,
isEmulatorName: name => name.startsWith('emu')
});
});
it('should start an emulator and run on that if none is running', () => {
return resolveToOfflineEmulator().then(result => {
expect(result).toEqual({ id: emuId, type: 'emulator' });
expect(emulatorSpyObj.start).toHaveBeenCalled();
});
});
it('should start named emulator and then run on it if it is specified', () => {
return resolveToOfflineEmulator({ id: 'emu3' }).then(result => {
expect(result).toEqual({ id: emuId, type: 'emulator' });
expect(emulatorSpyObj.start).toHaveBeenCalledWith('emu3');
});
});
it('should return null if given ID is not an avd name', () => {
return resolveToOfflineEmulator({ id: 'dev1' }).then(result => {
expect(result).toBe(null);
expect(emulatorSpyObj.start).not.toHaveBeenCalled();
});
});
it('should return null if given type is not emulator', () => {
return resolveToOfflineEmulator({ type: 'device' }).then(result => {
expect(result).toBe(null);
expect(emulatorSpyObj.start).not.toHaveBeenCalled();
});
});
});
describe('resolve', () => {
let resolveToOnlineTarget, resolveToOfflineEmulator;
beforeEach(() => {
resolveToOnlineTarget = jasmine.createSpy('resolveToOnlineTarget')
.and.resolveTo(null);
resolveToOfflineEmulator = jasmine.createSpy('resolveToOfflineEmulator')
.and.resolveTo(null);
target.__set__({
resolveToOnlineTarget,
resolveToOfflineEmulator,
build: { detectArchitecture: id => id + '-arch' }
});
});
it('should delegate to resolveToOnlineTarget', () => {
const spec = { type: 'device' };
resolveToOnlineTarget.and.resolveTo({ id: 'dev1', type: 'device' });
return target.resolve(spec).then(result => {
expect(result.id).toBe('dev1');
expect(resolveToOnlineTarget).toHaveBeenCalledWith(spec);
expect(resolveToOfflineEmulator).not.toHaveBeenCalled();
});
});
it('should delegate to resolveToOfflineEmulator if resolveToOnlineTarget fails', () => {
const spec = { type: 'emulator' };
resolveToOfflineEmulator.and.resolveTo({ id: 'emu1', type: 'emulator' });
return target.resolve(spec).then(result => {
expect(result.id).toBe('emu1');
expect(resolveToOnlineTarget).toHaveBeenCalledWith(spec);
expect(resolveToOfflineEmulator).toHaveBeenCalledWith(spec);
});
});
it('should add the target arch', () => {
const spec = { type: 'device' };
resolveToOnlineTarget.and.resolveTo({ id: 'dev1', type: 'device' });
return target.resolve(spec).then(result => {
expect(result.arch).toBe('dev1-arch');
});
});
it('should throw an error if target cannot be resolved', () => {
return expectAsync(target.resolve())
.toBeRejectedWithError(/Could not find target matching/);
});
});
describe('install', () => {
let AndroidManifestSpy;
let AndroidManifestFns;
@ -36,7 +223,7 @@ describe('target', () => {
let installTarget;
beforeEach(() => {
installTarget = { target: 'emulator-5556', isEmulator: true, arch: 'atari' };
installTarget = { id: 'emulator-5556', type: 'emulator', arch: 'atari' };
buildSpy = jasmine.createSpyObj('build', ['findBestApkForArchitecture']);
target.__set__('build', buildSpy);
@ -60,7 +247,7 @@ describe('target', () => {
it('should install to the passed target', () => {
return target.install(installTarget, {}).then(() => {
expect(AdbSpy.install.calls.argsFor(0)[0]).toBe(installTarget.target);
expect(AdbSpy.install.calls.argsFor(0)[0]).toBe(installTarget.id);
});
});
@ -108,7 +295,7 @@ describe('target', () => {
it('should unlock the screen on device', () => {
return target.install(installTarget, {}).then(() => {
expect(AdbSpy.shell).toHaveBeenCalledWith(installTarget.target, 'input keyevent 82');
expect(AdbSpy.shell).toHaveBeenCalledWith(installTarget.id, 'input keyevent 82');
});
});
@ -119,7 +306,7 @@ describe('target', () => {
AndroidManifestGetActivitySpy.getName.and.returnValue(activityName);
return target.install(installTarget, {}).then(() => {
expect(AdbSpy.start).toHaveBeenCalledWith(installTarget.target, `${packageId}/.${activityName}`);
expect(AdbSpy.start).toHaveBeenCalledWith(installTarget.id, `${packageId}/.${activityName}`);
});
});
});