From 8167646621d00d754f57b25d6645c32450656c0d Mon Sep 17 00:00:00 2001 From: Gearoid M Date: Mon, 18 Jun 2018 15:15:24 +0900 Subject: [PATCH] (android) Add unit tests for run and retryPromise --- bin/templates/cordova/lib/retry.js | 18 ++- bin/templates/cordova/lib/run.js | 2 +- spec/unit/retry.spec.js | 66 +++++++++ spec/unit/run.spec.js | 219 +++++++++++++++++++++++++++-- 4 files changed, 282 insertions(+), 23 deletions(-) create mode 100644 spec/unit/retry.spec.js diff --git a/bin/templates/cordova/lib/retry.js b/bin/templates/cordova/lib/retry.js index 3ea8f1436e..00d413a917 100644 --- a/bin/templates/cordova/lib/retry.js +++ b/bin/templates/cordova/lib/retry.js @@ -27,21 +27,20 @@ var events = require('cordova-common').events; * Retry a promise-returning function a number of times, propagating its * results on success or throwing its error on a failed final attempt. * - * @arg {Number} attemts_left - The number of times to retry the passed call. + * @arg {Number} attempts_left - The number of times to retry the passed call. * @arg {Function} promiseFunction - A function that returns a promise. * @arg {...} - Arguments to pass to promiseFunction. * * @returns {Promise} */ -module.exports.retryPromise = function (attemts_left, promiseFunction) { +module.exports.retryPromise = function (attempts_left, promiseFunction) { // NOTE: - // get all trailing arguments, by skipping the first two (attemts_left and + // get all trailing arguments, by skipping the first two (attempts_left and // promiseFunction) because they shouldn't get passed to promiseFunction var promiseFunctionArguments = Array.prototype.slice.call(arguments, 2); return promiseFunction.apply(undefined, promiseFunctionArguments).then( - // on success pass results through function onFulfilled (value) { return value; @@ -49,17 +48,16 @@ module.exports.retryPromise = function (attemts_left, promiseFunction) { // on rejection either retry, or throw the error function onRejected (error) { + attempts_left -= 1; - attemts_left -= 1; - - if (attemts_left < 1) { + if (attempts_left < 1) { throw error; } - events.emit('verbose', 'A retried call failed. Retrying ' + attemts_left + ' more time(s).'); + events.emit('verbose', 'A retried call failed. Retrying ' + attempts_left + ' more time(s).'); - // retry call self again with the same arguments, except attemts_left is now lower - var fullArguments = [attemts_left, promiseFunction].concat(promiseFunctionArguments); + // retry call self again with the same arguments, except attempts_left is now lower + var fullArguments = [attempts_left, promiseFunction].concat(promiseFunctionArguments); return module.exports.retryPromise.apply(undefined, fullArguments); } ); diff --git a/bin/templates/cordova/lib/run.js b/bin/templates/cordova/lib/run.js index 183fc9224c..37d91a692a 100644 --- a/bin/templates/cordova/lib/run.js +++ b/bin/templates/cordova/lib/run.js @@ -106,7 +106,7 @@ module.exports.run = function (runOptions) { // format than emulator.install expects. // TODO: Update emulator/device.install to handle this change return build.run.call(self, runOptions, resolvedTarget).then(function (buildResults) { - if (resolvedTarget.isEmulator) { + if (resolvedTarget && resolvedTarget.isEmulator) { return emulator.wait_for_boot(resolvedTarget.target).then(function () { return emulator.install(resolvedTarget, buildResults); }); diff --git a/spec/unit/retry.spec.js b/spec/unit/retry.spec.js new file mode 100644 index 0000000000..cb1f3cb235 --- /dev/null +++ b/spec/unit/retry.spec.js @@ -0,0 +1,66 @@ +/** + 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 retry = require('../../bin/templates/cordova/lib/retry'); +const Q = require('q'); + +describe('retry', () => { + describe('retryPromise method', () => { + let deferred; + let promiseFn; + + beforeEach(() => { + deferred = Q.defer(); + promiseFn = jasmine.createSpy().and.returnValue(deferred.promise); + }); + + it('should pass all extra arguments the the promise', () => { + retry.retryPromise(0, promiseFn, 'test1', 'test2', 'test3'); + expect(promiseFn).toHaveBeenCalledWith('test1', 'test2', 'test3'); + }); + + it('should retry the function up to specified number of times', done => { + const attempts = 3; + + retry.retryPromise(attempts, promiseFn) + .then(() => { + done.fail('Unexpectedly resolved'); + }) + .fail(() => { + expect(promiseFn).toHaveBeenCalledTimes(attempts); + done(); + }); + + for (let i = 0; i < attempts + 1; i++) { + deferred.reject(); + } + }); + + it('should not call the function again if it succeeds', () => { + retry.retryPromise(1, promiseFn); + promiseFn.calls.reset(); + + deferred.resolve(); + + return deferred.promise.then(() => { + expect(promiseFn).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/unit/run.spec.js b/spec/unit/run.spec.js index 881cc4a0f4..45ad6cb109 100644 --- a/spec/unit/run.spec.js +++ b/spec/unit/run.spec.js @@ -17,22 +17,217 @@ under the License. */ -var rewire = require('rewire'); -var run = rewire('../../bin/templates/cordova/lib/run'); -var getInstallTarget = run.__get__('getInstallTarget'); - -describe('run', function () { - describe('getInstallTarget', function () { - var targetOpts = { target: 'emu' }; - var deviceOpts = { device: true }; - var emulatorOpts = { emulator: true }; - var emptyOpts = {}; - - it('Test#001 : should select correct target based on the run opts', function () { +const rewire = require('rewire'); +const Q = require('q'); + +describe('run', () => { + let run; + + beforeEach(() => { + run = rewire('../../bin/templates/cordova/lib/run'); + }); + + describe('getInstallTarget', () => { + const targetOpts = { target: 'emu' }; + const deviceOpts = { device: true }; + const emulatorOpts = { emulator: true }; + const emptyOpts = {}; + + 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(); }); }); + + describe('run method', () => { + let buildSpyObj; + let deviceSpyObj; + let emulatorSpyObj; + let eventsSpyObj; + let getInstallTargetSpy; + + beforeEach(() => { + buildSpyObj = jasmine.createSpyObj('buildSpy', ['run']); + deviceSpyObj = jasmine.createSpyObj('deviceSpy', ['install', 'list', 'resolveTarget']); + emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', ['install', 'list_images', 'list_started', 'resolveTarget', 'start', 'wait_for_boot']); + eventsSpyObj = jasmine.createSpyObj('eventsSpy', ['emit']); + getInstallTargetSpy = jasmine.createSpy('getInstallTargetSpy'); + + buildSpyObj.run.and.returnValue(Q.resolve()); + + run.__set__({ + build: buildSpyObj, + device: deviceSpyObj, + emulator: emulatorSpyObj, + events: eventsSpyObj, + getInstallTarget: getInstallTargetSpy + }); + }); + + it('should run on default device when no target arguments are specified', () => { + const deviceList = ['testDevice1', 'testDevice2']; + + getInstallTargetSpy.and.returnValue(null); + deviceSpyObj.list.and.returnValue(Q.resolve(deviceList)); + + 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(Q.resolve(deviceList)); + emulatorSpyObj.list_started.and.returnValue(Q.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(Q.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(Q.resolve(emulatorList)); + + const startDeferred = Q.defer(); + startDeferred.resolve(defaultEmulator); + emulatorSpyObj.start.and.returnValue(startDeferred.promise); + + 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(Q.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(Q.resolve([])); + emulatorSpyObj.list_started.and.returnValue(Q.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(Q.resolve([])); + emulatorSpyObj.list_started.and.returnValue(Q.resolve([])); + emulatorSpyObj.list_images.and.returnValue(Q.resolve(emulatorList)); + emulatorSpyObj.start.and.returnValue(Q.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', done => { + const emulatorList = [{ name: 'emu1', id: 1 }]; + const deviceList = ['device1']; + const target = 'nonexistentdevice'; + getInstallTargetSpy.and.returnValue(target); + + deviceSpyObj.list.and.returnValue(Q.resolve(deviceList)); + emulatorSpyObj.list_started.and.returnValue(Q.resolve([])); + emulatorSpyObj.list_images.and.returnValue(Q.resolve(emulatorList)); + + run.run().then(() => { + done.fail('Expected error to be thrown'); + }) + .fail(err => { + expect(err).toContain(target); + done(); + }); + }); + + it('should build the app if a target has been found', () => { + getInstallTargetSpy.and.returnValue('--device'); + deviceSpyObj.resolveTarget.and.returnValue({ target: 'device1', isEmulator: false }); + + return run.run().then(() => { + expect(buildSpyObj.run).toHaveBeenCalled(); + }); + }); + + 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(deviceSpyObj.install).toHaveBeenCalledWith(deviceTarget, undefined); + }); + }); + + it('should install on emulator after build', () => { + const emulatorTarget = { target: 'emu1', isEmulator: true }; + getInstallTargetSpy.and.returnValue('--emulator'); + emulatorSpyObj.list_started.and.returnValue(Q.resolve([emulatorTarget.target])); + emulatorSpyObj.resolveTarget.and.returnValue(emulatorTarget); + emulatorSpyObj.wait_for_boot.and.returnValue(Q.resolve()); + + return run.run().then(() => { + expect(emulatorSpyObj.install).toHaveBeenCalledWith(emulatorTarget, undefined); + }); + }); + }); + + describe('help', () => { + it('should print out usage and help', () => { + let message = ''; + spyOn(console, 'log').and.callFake(msg => { message += msg; }); + spyOn(process, 'exit'); + + run.help(); + + expect(message.indexOf('Usage:') > -1); + }); + }); });