diff --git a/bin/templates/cordova/lib/device.js b/bin/templates/cordova/lib/device.js index dedaf717ac..245fe7123c 100644 --- a/bin/templates/cordova/lib/device.js +++ b/bin/templates/cordova/lib/device.js @@ -18,11 +18,8 @@ */ var build = require('./build'); -var path = require('path'); var Adb = require('./Adb'); -var AndroidManifest = require('./AndroidManifest'); var CordovaError = require('cordova-common').CordovaError; -var events = require('cordova-common').events; /** * Returns a promise for the list of the device ID's found @@ -48,46 +45,3 @@ module.exports.resolveTarget = function (target) { }); }); }; - -/* - * Installs a previously built application on the device - * and launches it. - * Returns a promise. - */ -module.exports.install = function (target, buildResults) { - return Promise.resolve().then(function () { - if (target && typeof target === 'object') { - return target; - } - return module.exports.resolveTarget(target); - }).then(function (resolvedTarget) { - var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch); - var manifest = new AndroidManifest(path.join(__dirname, '../../app/src/main/AndroidManifest.xml')); - var pkgName = manifest.getPackageId(); - var launchName = pkgName + '/.' + manifest.getActivity().getName(); - events.emit('log', 'Using apk: ' + apk_path); - events.emit('log', 'Package name: ' + pkgName); - - return Adb.install(resolvedTarget.target, apk_path, { replace: true }).catch(function (error) { - // CB-9557 CB-10157 only uninstall and reinstall app if the one that - // is already installed on device was signed w/different certificate - if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) { throw error; } - - events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' + - 'installed app already signed with different key'); - - // This promise is always resolved, even if 'adb uninstall' fails to uninstall app - // or the app doesn't installed at all, so no error catching needed. - return Adb.uninstall(resolvedTarget.target, pkgName).then(function () { - return Adb.install(resolvedTarget.target, apk_path, { replace: true }); - }); - }).then(function () { - // unlock screen - return Adb.shell(resolvedTarget.target, 'input keyevent 82'); - }).then(function () { - return Adb.start(resolvedTarget.target, launchName); - }).then(function () { - events.emit('log', 'LAUNCH SUCCESS'); - }); - }); -}; diff --git a/bin/templates/cordova/lib/emulator.js b/bin/templates/cordova/lib/emulator.js index e7b2491985..6e4d42d82c 100644 --- a/bin/templates/cordova/lib/emulator.js +++ b/bin/templates/cordova/lib/emulator.js @@ -20,11 +20,9 @@ const execa = require('execa'); const fs = require('fs-extra'); var android_versions = require('android-versions'); -var retry = require('./retry'); var build = require('./build'); var path = require('path'); var Adb = require('./Adb'); -var AndroidManifest = require('./AndroidManifest'); var events = require('cordova-common').events; var CordovaError = require('cordova-common').CordovaError; var android_sdk = require('./android_sdk'); @@ -33,11 +31,7 @@ var which = require('which'); // constants const ONE_SECOND = 1000; // in milliseconds -const ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds -const INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds -const NUM_INSTALL_RETRIES = 3; const CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds -const EXEC_KILL_SIGNAL = 'SIGKILL'; function forgivingWhichSync (cmd) { const whichResult = which.sync(cmd, { nothrow: true }); @@ -373,85 +367,3 @@ module.exports.resolveTarget = function (target) { }); }); }; - -/* - * Installs a previously built application on the emulator and launches it. - * If no target is specified, then it picks one. - * If no started emulators are found, error out. - * Returns a promise. - */ -module.exports.install = function (givenTarget, buildResults) { - var target; - // We need to find the proper path to the Android Manifest - const manifestPath = path.join(__dirname, '..', '..', 'app', 'src', 'main', 'AndroidManifest.xml'); - const manifest = new AndroidManifest(manifestPath); - const pkgName = manifest.getPackageId(); - - // resolve the target emulator - return Promise.resolve().then(function () { - if (givenTarget && typeof givenTarget === 'object') { - return givenTarget; - } else { - return module.exports.resolveTarget(givenTarget); - } - - // set the resolved target - }).then(function (resolvedTarget) { - target = resolvedTarget; - - // install the app - }).then(function () { - // This promise is always resolved, even if 'adb uninstall' fails to uninstall app - // or the app doesn't installed at all, so no error catching needed. - return Promise.resolve().then(function () { - var apk_path = build.findBestApkForArchitecture(buildResults, target.arch); - - events.emit('log', 'Using apk: ' + apk_path); - events.emit('log', 'Package name: ' + pkgName); - events.emit('verbose', 'Installing app on emulator...'); - - // A special function to call adb install in specific environment w/ specific options. - // Introduced as a part of fix for http://issues.apache.org/jira/browse/CB-9119 - // to workaround sporadic emulator hangs - function adbInstallWithOptions (...args) { - return Adb.install(...args, { - replace: true, - execOptions: { - timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds - killSignal: EXEC_KILL_SIGNAL - } - }); - } - - function installPromise () { - return adbInstallWithOptions(target.target, apk_path).catch(function (error) { - // CB-9557 CB-10157 only uninstall and reinstall app if the one that - // is already installed on device was signed w/different certificate - if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) { throw error; } - - events.emit('warn', 'Uninstalling app from device and reinstalling it because the ' + - 'currently installed app was signed with different key'); - - // This promise is always resolved, even if 'adb uninstall' fails to uninstall app - // or the app doesn't installed at all, so no error catching needed. - return Adb.uninstall(target.target, pkgName).then(function () { - return adbInstallWithOptions(target.target, apk_path); - }); - }); - } - - return retry.retryPromise(NUM_INSTALL_RETRIES, installPromise).then(function (output) { - events.emit('log', 'INSTALL SUCCESS'); - }); - }); - // unlock screen - }).then(function () { - events.emit('verbose', 'Unlocking screen...'); - return Adb.shell(target.target, 'input keyevent 82'); - }).then(function () { - Adb.start(target.target, pkgName + '/.' + manifest.getActivity().getName()); - // report success or failure - }).then(function (output) { - events.emit('log', 'LAUNCH SUCCESS'); - }); -}; diff --git a/bin/templates/cordova/lib/install-device b/bin/templates/cordova/lib/install-device index 03873883ed..2d5a8b1787 100755 --- a/bin/templates/cordova/lib/install-device +++ b/bin/templates/cordova/lib/install-device @@ -19,6 +19,7 @@ under the License. */ +const { install } = require('./target'); var device = require('./device'); var args = process.argv; @@ -26,17 +27,13 @@ if (args.length > 2) { var install_target; if (args[2].substring(0, 9) === '--target=') { install_target = args[2].substring(9, args[2].length); - device.install(install_target).catch(function (err) { - console.error('ERROR: ' + err); - process.exit(2); - }); } else { console.error('ERROR : argument \'' + args[2] + '\' not recognized.'); process.exit(2); } -} else { - device.install().catch(function (err) { - console.error('ERROR: ' + err); - process.exit(2); - }); } + +device.resolveTarget(install_target).then(install).catch(err => { + console.error('ERROR: ' + err); + process.exit(2); +}); diff --git a/bin/templates/cordova/lib/install-emulator b/bin/templates/cordova/lib/install-emulator index 2d46dbe936..d67997ed6e 100755 --- a/bin/templates/cordova/lib/install-emulator +++ b/bin/templates/cordova/lib/install-emulator @@ -19,6 +19,7 @@ under the License. */ +const { install } = require('./target'); var emulator = require('./emulator'); var args = process.argv; @@ -32,7 +33,7 @@ if (args.length > 2) { } } -emulator.install(install_target).catch(function (err) { +emulator.resolveTarget(install_target).then(install).catch(err => { console.error('ERROR: ' + err); process.exit(2); }); diff --git a/bin/templates/cordova/lib/run.js b/bin/templates/cordova/lib/run.js index c0a44d99ea..05ff775f2a 100644 --- a/bin/templates/cordova/lib/run.js +++ b/bin/templates/cordova/lib/run.js @@ -20,6 +20,7 @@ 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'); @@ -110,14 +111,12 @@ module.exports.run = function (runOptions) { } resolve(self._builder.fetchBuildResults(buildOptions.buildType, buildOptions.arch)); - }).then(function (buildResults) { + }).then(async function (buildResults) { if (resolvedTarget && resolvedTarget.isEmulator) { - return emulator.wait_for_boot(resolvedTarget.target).then(function () { - return emulator.install(resolvedTarget, buildResults); - }); + await emulator.wait_for_boot(resolvedTarget.id); } - return device.install(resolvedTarget, buildResults); + return target.install(resolvedTarget, buildResults); }); }); }; diff --git a/bin/templates/cordova/lib/target.js b/bin/templates/cordova/lib/target.js new file mode 100644 index 0000000000..1494144af1 --- /dev/null +++ b/bin/templates/cordova/lib/target.js @@ -0,0 +1,75 @@ +/* + 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 path = require('path'); +const Adb = require('./Adb'); +const build = require('./build'); +const AndroidManifest = require('./AndroidManifest'); +const { retryPromise } = require('./retry'); +const { events } = 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) { + const apk_path = build.findBestApkForArchitecture(buildResults, arch); + const manifest = new AndroidManifest(path.join(__dirname, '../../app/src/main/AndroidManifest.xml')); + const pkgName = manifest.getPackageId(); + const launchName = pkgName + '/.' + manifest.getActivity().getName(); + + events.emit('log', 'Using apk: ' + apk_path); + events.emit('log', 'Package name: ' + pkgName); + events.emit('verbose', `Installing app on target ${target}`); + + async function doInstall (execOptions = {}) { + try { + await Adb.install(target, apk_path, { replace: true, execOptions }); + } catch (error) { + // CB-9557 CB-10157 only uninstall and reinstall app if the one that + // is already installed on device was signed w/different certificate + if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) throw error; + + events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' + + 'installed app already signed with different key'); + + // This promise is always resolved, even if 'adb uninstall' fails to uninstall app + // or the app doesn't installed at all, so no error catching needed. + await Adb.uninstall(target, pkgName); + await Adb.install(target, apk_path, { replace: true }); + } + } + + if (isEmulator) { + // Work around sporadic emulator hangs: http://issues.apache.org/jira/browse/CB-9119 + await retryPromise(NUM_INSTALL_RETRIES, () => doInstall({ + timeout: INSTALL_COMMAND_TIMEOUT, + killSignal: EXEC_KILL_SIGNAL + })); + } else { + await doInstall(); + } + events.emit('log', 'INSTALL SUCCESS'); + + events.emit('verbose', 'Unlocking screen...'); + await Adb.shell(target, 'input keyevent 82'); + + await Adb.start(target, launchName); + events.emit('log', 'LAUNCH SUCCESS'); +}; diff --git a/spec/unit/device.spec.js b/spec/unit/device.spec.js index 6340d86b9e..6f19b0583d 100644 --- a/spec/unit/device.spec.js +++ b/spec/unit/device.spec.js @@ -103,106 +103,4 @@ describe('device', () => { }); }); }); - - describe('install', () => { - let AndroidManifestSpy; - let AndroidManifestFns; - let AndroidManifestGetActivitySpy; - let buildSpy; - let target; - - beforeEach(() => { - target = { target: DEVICE_LIST[0], arch: 'arm7', isEmulator: false }; - - buildSpy = jasmine.createSpyObj('build', ['findBestApkForArchitecture']); - device.__set__('build', buildSpy); - - AndroidManifestFns = jasmine.createSpyObj('AndroidManifestFns', ['getPackageId', 'getActivity']); - AndroidManifestGetActivitySpy = jasmine.createSpyObj('getActivity', ['getName']); - AndroidManifestFns.getActivity.and.returnValue(AndroidManifestGetActivitySpy); - AndroidManifestSpy = jasmine.createSpy('AndroidManifest').and.returnValue(AndroidManifestFns); - device.__set__('AndroidManifest', AndroidManifestSpy); - - AdbSpy.install.and.returnValue(Promise.resolve()); - AdbSpy.shell.and.returnValue(Promise.resolve()); - AdbSpy.start.and.returnValue(Promise.resolve()); - }); - - it('should get the full target object if only id is specified', () => { - const targetId = DEVICE_LIST[0]; - spyOn(device, 'resolveTarget').and.returnValue(Promise.resolve(target)); - - return device.install(targetId).then(() => { - expect(device.resolveTarget).toHaveBeenCalledWith(targetId); - }); - }); - - it('should install to the passed target', () => { - return device.install(target).then(() => { - expect(AdbSpy.install).toHaveBeenCalledWith(target.target, undefined, jasmine.anything()); - }); - }); - - it('should install the correct apk based on the architecture and build results', () => { - const buildResults = { - apkPaths: 'path/to/apks', - buildType: 'debug', - buildMethod: 'foo' - }; - - const apkPath = 'my/apk/path/app.apk'; - buildSpy.findBestApkForArchitecture.and.returnValue(apkPath); - - return device.install(target, buildResults).then(() => { - expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, target.arch); - expect(AdbSpy.install).toHaveBeenCalledWith(jasmine.anything(), apkPath, jasmine.anything()); - }); - }); - - it('should uninstall and reinstall app if failure is due to different certificates', () => { - AdbSpy.install.and.returnValues( - Promise.reject('Failed to install: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES'), - Promise.resolve() - ); - - AdbSpy.uninstall.and.callFake(() => { - expect(AdbSpy.install).toHaveBeenCalledTimes(1); - return Promise.resolve(); - }); - - return device.install(target).then(() => { - expect(AdbSpy.install).toHaveBeenCalledTimes(2); - expect(AdbSpy.uninstall).toHaveBeenCalled(); - }); - }); - - it('should throw any error not caused by different certificates', () => { - const errorMsg = new CordovaError('Failed to install'); - AdbSpy.install.and.returnValues(Promise.reject(errorMsg)); - - return device.install(target).then( - () => fail('Unexpectedly resolved'), - err => { - expect(err).toBe(errorMsg); - } - ); - }); - - it('should unlock the screen on device', () => { - return device.install(target).then(() => { - expect(AdbSpy.shell).toHaveBeenCalledWith(target.target, 'input keyevent 82'); - }); - }); - - it('should start the newly installed app on the device', () => { - const packageId = 'unittestapp'; - const activityName = 'TestActivity'; - AndroidManifestFns.getPackageId.and.returnValue(packageId); - AndroidManifestGetActivitySpy.getName.and.returnValue(activityName); - - return device.install(target).then(() => { - expect(AdbSpy.start).toHaveBeenCalledWith(target.target, `${packageId}/.${activityName}`); - }); - }); - }); }); diff --git a/spec/unit/emulator.spec.js b/spec/unit/emulator.spec.js index 70ae5c2fa0..b6eaa06adb 100644 --- a/spec/unit/emulator.spec.js +++ b/spec/unit/emulator.spec.js @@ -575,107 +575,4 @@ describe('emulator', () => { }); }); }); - - describe('install', () => { - let AndroidManifestSpy; - let AndroidManifestFns; - let AndroidManifestGetActivitySpy; - let AdbSpy; - let buildSpy; - let target; - - beforeEach(() => { - target = { target: EMULATOR_LIST[1], arch: 'arm7', isEmulator: true }; - - buildSpy = jasmine.createSpyObj('build', ['findBestApkForArchitecture']); - emu.__set__('build', buildSpy); - - AndroidManifestFns = jasmine.createSpyObj('AndroidManifestFns', ['getPackageId', 'getActivity']); - AndroidManifestGetActivitySpy = jasmine.createSpyObj('getActivity', ['getName']); - AndroidManifestFns.getActivity.and.returnValue(AndroidManifestGetActivitySpy); - AndroidManifestSpy = jasmine.createSpy('AndroidManifest').and.returnValue(AndroidManifestFns); - emu.__set__('AndroidManifest', AndroidManifestSpy); - - AdbSpy = jasmine.createSpyObj('Adb', ['shell', 'start', 'install', 'uninstall']); - AdbSpy.shell.and.returnValue(Promise.resolve()); - AdbSpy.start.and.returnValue(Promise.resolve()); - AdbSpy.install.and.returnValue(Promise.resolve()); - AdbSpy.uninstall.and.returnValue(Promise.resolve()); - emu.__set__('Adb', AdbSpy); - }); - - it('should get the full target object if only id is specified', () => { - const targetId = target.target; - spyOn(emu, 'resolveTarget').and.returnValue(Promise.resolve(target)); - - return emu.install(targetId, {}).then(() => { - expect(emu.resolveTarget).toHaveBeenCalledWith(targetId); - }); - }); - - it('should install to the passed target', () => { - return emu.install(target, {}).then(() => { - expect(AdbSpy.install.calls.argsFor(0)[0]).toBe(target.target); - }); - }); - - it('should install the correct apk based on the architecture and build results', () => { - const buildResults = { - apkPaths: 'path/to/apks', - buildType: 'debug', - buildMethod: 'foo' - }; - - const apkPath = 'my/apk/path/app.apk'; - buildSpy.findBestApkForArchitecture.and.returnValue(apkPath); - - return emu.install(target, buildResults).then(() => { - expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, target.arch); - - expect(AdbSpy.install.calls.argsFor(0)[1]).toBe(apkPath); - }); - }); - - it('should uninstall and reinstall app if failure is due to different certificates', () => { - AdbSpy.install.and.returnValues( - Promise.reject('Failure: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES'), - Promise.resolve() - ); - - return emu.install(target, {}).then(() => { - expect(AdbSpy.install).toHaveBeenCalledTimes(2); - expect(AdbSpy.uninstall).toHaveBeenCalled(); - }); - }); - - it('should throw any error not caused by different certificates', () => { - const errorMsg = 'Failure: Failed to install'; - AdbSpy.install.and.rejectWith(new CordovaError(errorMsg)); - - return emu.install(target, {}).then( - () => fail('Unexpectedly resolved'), - err => { - expect(err).toEqual(jasmine.any(CordovaError)); - expect(err.message).toContain(errorMsg); - } - ); - }); - - it('should unlock the screen on device', () => { - return emu.install(target, {}).then(() => { - expect(AdbSpy.shell).toHaveBeenCalledWith(target.target, 'input keyevent 82'); - }); - }); - - it('should start the newly installed app on the device', () => { - const packageId = 'unittestapp'; - const activityName = 'TestActivity'; - AndroidManifestFns.getPackageId.and.returnValue(packageId); - AndroidManifestGetActivitySpy.getName.and.returnValue(activityName); - - return emu.install(target, {}).then(() => { - expect(AdbSpy.start).toHaveBeenCalledWith(target.target, `${packageId}/.${activityName}`); - }); - }); - }); }); diff --git a/spec/unit/run.spec.js b/spec/unit/run.spec.js index 78104c7c78..287905abfb 100644 --- a/spec/unit/run.spec.js +++ b/spec/unit/run.spec.js @@ -45,18 +45,23 @@ describe('run', () => { describe('run method', () => { let deviceSpyObj; let emulatorSpyObj; + let targetSpyObj; let eventsSpyObj; let getInstallTargetSpy; beforeEach(() => { - deviceSpyObj = jasmine.createSpyObj('deviceSpy', ['install', 'list', 'resolveTarget']); - emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', ['install', 'list_images', 'list_started', 'resolveTarget', 'start', 'wait_for_boot']); + 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'); + targetSpyObj = jasmine.createSpyObj('target', ['install']); + targetSpyObj.install.and.resolveTo(); + run.__set__({ device: deviceSpyObj, emulator: emulatorSpyObj, + target: targetSpyObj, events: eventsSpyObj, getInstallTarget: getInstallTargetSpy }); @@ -187,7 +192,7 @@ describe('run', () => { deviceSpyObj.resolveTarget.and.returnValue(deviceTarget); return run.run().then(() => { - expect(deviceSpyObj.install).toHaveBeenCalledWith(deviceTarget, { apkPaths: [], buildType: 'debug' }); + expect(targetSpyObj.install).toHaveBeenCalledWith(deviceTarget, { apkPaths: [], buildType: 'debug' }); }); }); @@ -199,7 +204,7 @@ describe('run', () => { emulatorSpyObj.wait_for_boot.and.returnValue(Promise.resolve()); return run.run().then(() => { - expect(emulatorSpyObj.install).toHaveBeenCalledWith(emulatorTarget, { apkPaths: [], buildType: 'debug' }); + expect(targetSpyObj.install).toHaveBeenCalledWith(emulatorTarget, { apkPaths: [], buildType: 'debug' }); }); }); diff --git a/spec/unit/target.spec.js b/spec/unit/target.spec.js new file mode 100644 index 0000000000..345cda1ad7 --- /dev/null +++ b/spec/unit/target.spec.js @@ -0,0 +1,126 @@ +/** + 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'); + +describe('target', () => { + let target; + + beforeEach(() => { + target = rewire('../../bin/templates/cordova/lib/target'); + }); + + describe('install', () => { + let AndroidManifestSpy; + let AndroidManifestFns; + let AndroidManifestGetActivitySpy; + let AdbSpy; + let buildSpy; + let installTarget; + + beforeEach(() => { + installTarget = { target: 'emulator-5556', isEmulator: true, arch: 'atari' }; + + buildSpy = jasmine.createSpyObj('build', ['findBestApkForArchitecture']); + target.__set__('build', buildSpy); + + AndroidManifestFns = jasmine.createSpyObj('AndroidManifestFns', ['getPackageId', 'getActivity']); + AndroidManifestGetActivitySpy = jasmine.createSpyObj('getActivity', ['getName']); + AndroidManifestFns.getActivity.and.returnValue(AndroidManifestGetActivitySpy); + AndroidManifestSpy = jasmine.createSpy('AndroidManifest').and.returnValue(AndroidManifestFns); + target.__set__('AndroidManifest', AndroidManifestSpy); + + AdbSpy = jasmine.createSpyObj('Adb', ['shell', 'start', 'install', 'uninstall']); + AdbSpy.shell.and.returnValue(Promise.resolve()); + AdbSpy.start.and.returnValue(Promise.resolve()); + AdbSpy.install.and.returnValue(Promise.resolve()); + AdbSpy.uninstall.and.returnValue(Promise.resolve()); + target.__set__('Adb', AdbSpy); + + // Silence output during test + spyOn(target.__get__('events'), 'emit'); + }); + + it('should install to the passed target', () => { + return target.install(installTarget, {}).then(() => { + expect(AdbSpy.install.calls.argsFor(0)[0]).toBe(installTarget.target); + }); + }); + + it('should install the correct apk based on the architecture and build results', () => { + const buildResults = { + apkPaths: 'path/to/apks', + buildType: 'debug', + buildMethod: 'foo' + }; + + const apkPath = 'my/apk/path/app.apk'; + buildSpy.findBestApkForArchitecture.and.returnValue(apkPath); + + return target.install(installTarget, buildResults).then(() => { + expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, installTarget.arch); + + expect(AdbSpy.install.calls.argsFor(0)[1]).toBe(apkPath); + }); + }); + + it('should uninstall and reinstall app if failure is due to different certificates', () => { + AdbSpy.install.and.returnValues( + Promise.reject('Failure: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES'), + Promise.resolve() + ); + + return target.install(installTarget, {}).then(() => { + expect(AdbSpy.install).toHaveBeenCalledTimes(2); + expect(AdbSpy.uninstall).toHaveBeenCalled(); + }); + }); + + it('should throw any error not caused by different certificates', () => { + const errorMsg = 'Failure: Failed to install'; + AdbSpy.install.and.rejectWith(new CordovaError(errorMsg)); + + return target.install(installTarget, {}).then( + () => fail('Unexpectedly resolved'), + err => { + expect(err).toEqual(jasmine.any(CordovaError)); + expect(err.message).toContain(errorMsg); + } + ); + }); + + it('should unlock the screen on device', () => { + return target.install(installTarget, {}).then(() => { + expect(AdbSpy.shell).toHaveBeenCalledWith(installTarget.target, 'input keyevent 82'); + }); + }); + + it('should start the newly installed app on the device', () => { + const packageId = 'unittestapp'; + const activityName = 'TestActivity'; + AndroidManifestFns.getPackageId.and.returnValue(packageId); + AndroidManifestGetActivitySpy.getName.and.returnValue(activityName); + + return target.install(installTarget, {}).then(() => { + expect(AdbSpy.start).toHaveBeenCalledWith(installTarget.target, `${packageId}/.${activityName}`); + }); + }); + }); +});