You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by GitBox <gi...@apache.org> on 2018/07/03 04:21:45 UTC

[GitHub] dpogue closed pull request #463: Emulator: Add unit tests and remove Q

dpogue closed pull request #463: Emulator: Add unit tests and remove Q
URL: https://github.com/apache/cordova-android/pull/463
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/bin/templates/cordova/lib/emulator.js b/bin/templates/cordova/lib/emulator.js
index 243b1fb53..24d91f9d2 100644
--- a/bin/templates/cordova/lib/emulator.js
+++ b/bin/templates/cordova/lib/emulator.js
@@ -32,7 +32,6 @@ var shelljs = require('shelljs');
 var android_sdk = require('./android_sdk');
 var check_reqs = require('./check_reqs');
 
-var Q = require('q');
 var os = require('os');
 var fs = require('fs');
 var child_process = require('child_process');
@@ -168,15 +167,13 @@ module.exports.list_images_using_android = function () {
    }
  */
 module.exports.list_images = function () {
-    return Q.fcall(function () {
+    return Promise.resolve().then(function () {
         if (forgivingWhichSync('avdmanager')) {
             return module.exports.list_images_using_avdmanager();
         } else if (forgivingWhichSync('android')) {
             return module.exports.list_images_using_android();
         } else {
-            return Q().then(function () {
-                throw new CordovaError('Could not find either `android` or `avdmanager` on your $PATH! Are you sure the Android SDK is installed and available?');
-            });
+            return Promise.reject(new CordovaError('Could not find either `android` or `avdmanager` on your $PATH! Are you sure the Android SDK is installed and available?'));
         }
     }).then(function (avds) {
         // In case we're missing the Android OS version string from the target description, add it.
@@ -275,8 +272,8 @@ module.exports.get_available_port = function () {
 module.exports.start = function (emulator_ID, boot_timeout) {
     var self = this;
 
-    return Q().then(function () {
-        if (emulator_ID) return Q(emulator_ID);
+    return Promise.resolve().then(function () {
+        if (emulator_ID) return Promise.resolve(emulator_ID);
 
         return self.best_image().then(function (best) {
             if (best && best.name) {
@@ -285,7 +282,7 @@ module.exports.start = function (emulator_ID, boot_timeout) {
             }
 
             var androidCmd = check_reqs.getAbsoluteAndroidCmd();
-            return Q.reject(new CordovaError('No emulator images (avds) found.\n' +
+            return Promise.reject(new CordovaError('No emulator images (avds) found.\n' +
                 '1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
                 '2. Create an AVD by running: ' + androidCmd + ' avd\n' +
                 'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n'));
@@ -306,7 +303,7 @@ module.exports.start = function (emulator_ID, boot_timeout) {
             return self.wait_for_emulator(port);
         });
     }).then(function (emulatorId) {
-        if (!emulatorId) { return Q.reject(new CordovaError('Failed to start emulator')); }
+        if (!emulatorId) { return Promise.reject(new CordovaError('Failed to start emulator')); }
 
         // wait for emulator to boot up
         process.stdout.write('Waiting for emulator to boot (this may take a while)...');
@@ -332,7 +329,7 @@ module.exports.start = function (emulator_ID, boot_timeout) {
  */
 module.exports.wait_for_emulator = function (port) {
     var self = this;
-    return Q().then(function () {
+    return Promise.resolve().then(function () {
         var emulator_id = 'emulator-' + port;
         return Adb.shell(emulator_id, 'getprop dev.bootcomplete').then(function (output) {
             if (output.indexOf('1') >= 0) {
@@ -369,10 +366,13 @@ module.exports.wait_for_boot = function (emulator_id, time_remaining) {
         } else {
             process.stdout.write('.');
 
-            // Check at regular intervals
-            return Q.delay(time_remaining < CHECK_BOOTED_INTERVAL ? time_remaining : CHECK_BOOTED_INTERVAL).then(function () {
-                var updated_time = time_remaining >= 0 ? Math.max(time_remaining - CHECK_BOOTED_INTERVAL, 0) : time_remaining;
-                return self.wait_for_boot(emulator_id, updated_time);
+            return new Promise(resolve => {
+                const delay = time_remaining < CHECK_BOOTED_INTERVAL ? time_remaining : CHECK_BOOTED_INTERVAL;
+
+                setTimeout(() => {
+                    const updated_time = time_remaining >= 0 ? Math.max(time_remaining - CHECK_BOOTED_INTERVAL, 0) : time_remaining;
+                    resolve(self.wait_for_boot(emulator_id, updated_time));
+                }, delay);
             });
         }
     });
@@ -398,7 +398,7 @@ module.exports.create_image = function (name, target) {
             // TODO: This seems like another error case, even though it always happens.
             console.error('ERROR : Unable to create an avd emulator, no targets found.');
             console.error('Ensure you have targets available by running the "android" command');
-            return Q.reject();
+            return Promise.reject(new CordovaError());
         }, function (error) {
             console.error('ERROR : Failed to create emulator image : ');
             console.error(error);
@@ -409,13 +409,13 @@ module.exports.create_image = function (name, target) {
 module.exports.resolveTarget = function (target) {
     return this.list_started().then(function (emulator_list) {
         if (emulator_list.length < 1) {
-            return Q.reject('No running Android emulators found, please start an emulator before deploying your project.');
+            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 Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.');
+            return Promise.reject(new CordovaError('Unable to find target \'' + target + '\'. Failed to deploy to emulator.'));
         }
 
         return build.detectArchitecture(target).then(function (arch) {
@@ -442,7 +442,7 @@ module.exports.install = function (givenTarget, buildResults) {
     var pkgName = manifest.getPackageId();
 
     // resolve the target emulator
-    return Q().then(function () {
+    return Promise.resolve().then(function () {
         if (givenTarget && typeof givenTarget === 'object') {
             return givenTarget;
         } else {
@@ -457,7 +457,7 @@ module.exports.install = function (givenTarget, buildResults) {
     }).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 Q.when().then(function () {
+        return Promise.resolve().then(function () {
 
             var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
             var execOptions = {
@@ -477,7 +477,7 @@ module.exports.install = function (givenTarget, buildResults) {
                 events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...');
 
                 var command = 'adb -s ' + target + ' install -r "' + apk + '"';
-                return Q.promise(function (resolve, reject) {
+                return new Promise(function (resolve, reject) {
                     child_process.exec(command, opts, function (err, stdout, stderr) {
                         if (err) reject(new CordovaError('Error executing "' + command + '": ' + stderr));
                         // adb does not return an error code even if installation fails. Instead it puts a specific
diff --git a/bin/templates/cordova/lib/install-emulator b/bin/templates/cordova/lib/install-emulator
index 6103b1824..2d46dbe93 100755
--- a/bin/templates/cordova/lib/install-emulator
+++ b/bin/templates/cordova/lib/install-emulator
@@ -32,7 +32,7 @@ if (args.length > 2) {
     }
 }
 
-emulator.install(install_target).done(null, function (err) {
+emulator.install(install_target).catch(function (err) {
     console.error('ERROR: ' + err);
     process.exit(2);
 });
diff --git a/bin/templates/cordova/lib/list-emulator-images b/bin/templates/cordova/lib/list-emulator-images
index ded97945a..03cfb190c 100755
--- a/bin/templates/cordova/lib/list-emulator-images
+++ b/bin/templates/cordova/lib/list-emulator-images
@@ -23,7 +23,7 @@ var emulators = require('./emulator');
 
 // Usage support for when args are given
 require('./check_reqs').check_android().then(function () {
-    emulators.list_images().done(function (emulator_list) {
+    emulators.list_images().then(function (emulator_list) {
         emulator_list && emulator_list.forEach(function (emu) {
             console.log(emu.name);
         });
diff --git a/bin/templates/cordova/lib/list-started-emulators b/bin/templates/cordova/lib/list-started-emulators
index 80c52f3a2..2a83e03a1 100755
--- a/bin/templates/cordova/lib/list-started-emulators
+++ b/bin/templates/cordova/lib/list-started-emulators
@@ -23,7 +23,7 @@ var emulators = require('./emulator');
 
 // Usage support for when args are given
 require('./check_reqs').check_android().then(function () {
-    emulators.list_started().done(function (emulator_list) {
+    emulators.list_started().then(function (emulator_list) {
         emulator_list && emulator_list.forEach(function (emu) {
             console.log(emu);
         });
diff --git a/bin/templates/cordova/lib/start-emulator b/bin/templates/cordova/lib/start-emulator
index a9c241013..20c92b70b 100755
--- a/bin/templates/cordova/lib/start-emulator
+++ b/bin/templates/cordova/lib/start-emulator
@@ -32,7 +32,7 @@ if (args.length > 2) {
     }
 }
 
-emulator.start(install_target).done(null, function (err) {
+emulator.start(install_target).catch(function (err) {
     console.error('ERROR: ' + err);
     process.exit(2);
 });
diff --git a/spec/unit/emulator.spec.js b/spec/unit/emulator.spec.js
index 60d7a8ac4..0efcee747 100644
--- a/spec/unit/emulator.spec.js
+++ b/spec/unit/emulator.spec.js
@@ -17,45 +17,50 @@
     under the License.
 */
 
-var emu = require('../../bin/templates/cordova/lib/emulator');
-var check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
-var superspawn = require('cordova-common').superspawn;
-var Q = require('q');
-var fs = require('fs');
-var path = require('path');
-var shelljs = require('shelljs');
-
-describe('emulator', function () {
-    describe('list_images_using_avdmanager', function () {
-        it('should properly parse details of SDK Tools 25.3.1 `avdmanager` output', function (done) {
-            var deferred = Q.defer();
-            spyOn(superspawn, 'spawn').and.returnValue(deferred.promise);
-            deferred.resolve(fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.3-avdmanager_list_avd.txt'), 'utf-8'));
-            return emu.list_images_using_avdmanager().then(function (list) {
+const fs = require('fs');
+const path = require('path');
+const rewire = require('rewire');
+const shelljs = require('shelljs');
+
+const CordovaError = require('cordova-common').CordovaError;
+const check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
+const superspawn = require('cordova-common').superspawn;
+
+describe('emulator', () => {
+    const EMULATOR_LIST = ['emulator-5555', 'emulator-5556', 'emulator-5557'];
+    let emu;
+
+    beforeEach(() => {
+        emu = rewire('../../bin/templates/cordova/lib/emulator');
+    });
+
+    describe('list_images_using_avdmanager', () => {
+        it('should properly parse details of SDK Tools 25.3.1 `avdmanager` output', () => {
+            const avdList = fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.3-avdmanager_list_avd.txt'), 'utf-8');
+            spyOn(superspawn, 'spawn').and.returnValue(Promise.resolve(avdList));
+
+            return emu.list_images_using_avdmanager().then(list => {
                 expect(list).toBeDefined();
                 expect(list[0].name).toEqual('nexus5-5.1');
                 expect(list[0].target).toEqual('Android 5.1 (API level 22)');
                 expect(list[1].device).toEqual('pixel (Google)');
                 expect(list[2].abi).toEqual('default/x86_64');
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-            }).fin(function () {
-                done();
             });
         });
     });
-    describe('list_images_using_android', function () {
-        it('should invoke `android` with the `list avd` command and _not_ the `list avds` command, as the plural form is not supported in some Android SDK Tools versions', function () {
-            var deferred = Q.defer();
-            spyOn(superspawn, 'spawn').and.returnValue(deferred.promise);
+
+    describe('list_images_using_android', () => {
+        it('should invoke `android` with the `list avd` command and _not_ the `list avds` command, as the plural form is not supported in some Android SDK Tools versions', () => {
+            spyOn(superspawn, 'spawn').and.returnValue(new Promise(() => {}, () => {}));
             emu.list_images_using_android();
             expect(superspawn.spawn).toHaveBeenCalledWith('android', ['list', 'avd']);
         });
-        it('should properly parse details of SDK Tools pre-25.3.1 `android list avd` output', function (done) {
-            var deferred = Q.defer();
-            spyOn(superspawn, 'spawn').and.returnValue(deferred.promise);
-            deferred.resolve(fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.2-android_list_avd.txt'), 'utf-8'));
-            return emu.list_images_using_android().then(function (list) {
+
+        it('should properly parse details of SDK Tools pre-25.3.1 `android list avd` output', () => {
+            const avdList = fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.2-android_list_avd.txt'), 'utf-8');
+            spyOn(superspawn, 'spawn').and.returnValue(Promise.resolve(avdList));
+
+            return emu.list_images_using_android().then(list => {
                 expect(list).toBeDefined();
                 expect(list[0].name).toEqual('QWR');
                 expect(list[0].device).toEqual('Nexus 5 (Google)');
@@ -63,68 +68,38 @@ describe('emulator', function () {
                 expect(list[0].target).toEqual('Android 7.1.1 (API level 25)');
                 expect(list[0].abi).toEqual('google_apis/x86_64');
                 expect(list[0].skin).toEqual('1080x1920');
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-            }).fin(function () {
-                done();
             });
         });
     });
-    describe('list_images', function () {
-        beforeEach(function () {
-            spyOn(fs, 'realpathSync').and.callFake(function (cmd) {
-                return cmd;
-            });
+
+    describe('list_images', () => {
+        beforeEach(() => {
+            spyOn(fs, 'realpathSync').and.callFake(cmd => cmd);
         });
-        it('should try to parse AVD information using `avdmanager` first', function (done) {
-            spyOn(shelljs, 'which').and.callFake(function (cmd) {
-                if (cmd === 'avdmanager') {
-                    return true;
-                } else {
-                    return false;
-                }
-            });
-            var deferred = Q.defer();
-            var avdmanager_spy = spyOn(emu, 'list_images_using_avdmanager').and.returnValue(deferred.promise);
-            deferred.resolve([]);
-            emu.list_images().then(function () {
+
+        it('should try to parse AVD information using `avdmanager` first', () => {
+            spyOn(shelljs, 'which').and.callFake(cmd => cmd === 'avdmanager');
+
+            const avdmanager_spy = spyOn(emu, 'list_images_using_avdmanager').and.returnValue(Promise.resolve([]));
+
+            return emu.list_images().then(() => {
                 expect(avdmanager_spy).toHaveBeenCalled();
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-            }).fin(function () {
-                done();
             });
         });
-        it('should delegate to `android` if `avdmanager` cant be found and `android` can', function (done) {
-            spyOn(shelljs, 'which').and.callFake(function (cmd) {
-                if (cmd === 'avdmanager') {
-                    return false;
-                } else {
-                    return true;
-                }
-            });
-            var deferred = Q.defer();
-            var android_spy = spyOn(emu, 'list_images_using_android').and.returnValue(deferred.promise);
-            deferred.resolve([]);
-            emu.list_images().then(function () {
+
+        it('should delegate to `android` if `avdmanager` cant be found and `android` can', () => {
+            spyOn(shelljs, 'which').and.callFake(cmd => cmd !== 'avdmanager');
+
+            const android_spy = spyOn(emu, 'list_images_using_android').and.returnValue(Promise.resolve([]));
+
+            return emu.list_images().then(() => {
                 expect(android_spy).toHaveBeenCalled();
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-            }).fin(function () {
-                done();
             });
         });
-        it('should correct api level information and fill in the blanks about api level if exists', function (done) {
-            spyOn(shelljs, 'which').and.callFake(function (cmd) {
-                if (cmd === 'avdmanager') {
-                    return true;
-                } else {
-                    return false;
-                }
-            });
-            var deferred = Q.defer();
-            spyOn(emu, 'list_images_using_avdmanager').and.returnValue(deferred.promise);
-            deferred.resolve([
+
+        it('should correct api level information and fill in the blanks about api level if exists', () => {
+            spyOn(shelljs, 'which').and.callFake(cmd => cmd === 'avdmanager');
+            spyOn(emu, 'list_images_using_avdmanager').and.returnValue(Promise.resolve([
                 {
                     name: 'Pixel_7.0',
                     device: 'pixel (Google)',
@@ -138,87 +113,576 @@ describe('emulator', function () {
                     abi: 'google_apis/x86',
                     target: 'Android API 26'
                 }
-            ]);
-            emu.list_images().then(function (avds) {
+            ]));
+
+            return emu.list_images().then(avds => {
                 expect(avds[1].target).toContain('Android 8');
                 expect(avds[1].target).toContain('API level 26');
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-            }).fin(function () {
-                done();
             });
         });
-        it('should throw an error if neither `avdmanager` nor `android` are able to be found', function (done) {
+
+        it('should throw an error if neither `avdmanager` nor `android` are able to be found', () => {
             spyOn(shelljs, 'which').and.returnValue(false);
-            return emu.list_images().catch(function (err) {
-                expect(err).toBeDefined();
-                expect(err.message).toContain('Could not find either `android` or `avdmanager`');
-                done();
-            });
+
+            return emu.list_images().then(
+                () => fail('Unexpectedly resolved'),
+                err => {
+                    expect(err).toBeDefined();
+                    expect(err.message).toContain('Could not find either `android` or `avdmanager`');
+                }
+            );
         });
     });
-    describe('best_image', function () {
-        var avds_promise;
-        var target_mock;
-        beforeEach(function () {
-            avds_promise = Q.defer();
-            spyOn(emu, 'list_images').and.returnValue(avds_promise.promise);
+
+    describe('best_image', () => {
+        let target_mock;
+
+        beforeEach(() => {
+            spyOn(emu, 'list_images');
             target_mock = spyOn(check_reqs, 'get_target').and.returnValue('android-26');
         });
-        it('should return undefined if there are no defined AVDs', function (done) {
-            avds_promise.resolve([]);
-            emu.best_image().then(function (best_avd) {
+
+        it('should return undefined if there are no defined AVDs', () => {
+            emu.list_images.and.returnValue(Promise.resolve([]));
+
+            return emu.best_image().then(best_avd => {
                 expect(best_avd).toBeUndefined();
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-            }).fin(done);
-        });
-        it('should return the first available image if there is no available target information for existing AVDs', function (done) {
-            var fake_avd = { name: 'MyFakeAVD' };
-            var second_fake_avd = { name: 'AnotherAVD' };
-            avds_promise.resolve([fake_avd, second_fake_avd]);
-            emu.best_image().then(function (best_avd) {
+            });
+        });
+
+        it('should return the first available image if there is no available target information for existing AVDs', () => {
+            const fake_avd = { name: 'MyFakeAVD' };
+            const second_fake_avd = { name: 'AnotherAVD' };
+            emu.list_images.and.returnValue(Promise.resolve([fake_avd, second_fake_avd]));
+
+            return emu.best_image().then(best_avd => {
                 expect(best_avd).toBe(fake_avd);
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-            }).fin(done);
+            });
         });
-        it('should return the first AVD for the API level that matches the project target', function (done) {
+
+        it('should return the first AVD for the API level that matches the project target', () => {
             target_mock.and.returnValue('android-25');
-            var fake_avd = { name: 'MyFakeAVD', target: 'Android 7.0 (API level 24)' };
-            var second_fake_avd = { name: 'AnotherAVD', target: 'Android 7.1 (API level 25)' };
-            var third_fake_avd = { name: 'AVDThree', target: 'Android 8.0 (API level 26)' };
-            avds_promise.resolve([fake_avd, second_fake_avd, third_fake_avd]);
-            emu.best_image().then(function (best_avd) {
+            const fake_avd = { name: 'MyFakeAVD', target: 'Android 7.0 (API level 24)' };
+            const second_fake_avd = { name: 'AnotherAVD', target: 'Android 7.1 (API level 25)' };
+            const third_fake_avd = { name: 'AVDThree', target: 'Android 8.0 (API level 26)' };
+            emu.list_images.and.returnValue(Promise.resolve([fake_avd, second_fake_avd, third_fake_avd]));
+
+            return emu.best_image().then(best_avd => {
                 expect(best_avd).toBe(second_fake_avd);
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-            }).fin(done);
+            });
         });
-        it('should return the AVD with API level that is closest to the project target API level, without going over', function (done) {
+
+        it('should return the AVD with API level that is closest to the project target API level, without going over', () => {
             target_mock.and.returnValue('android-26');
-            var fake_avd = { name: 'MyFakeAVD', target: 'Android 7.0 (API level 24)' };
-            var second_fake_avd = { name: 'AnotherAVD', target: 'Android 7.1 (API level 25)' };
-            var third_fake_avd = { name: 'AVDThree', target: 'Android 99.0 (API level 134)' };
-            avds_promise.resolve([fake_avd, second_fake_avd, third_fake_avd]);
-            emu.best_image().then(function (best_avd) {
+            const fake_avd = { name: 'MyFakeAVD', target: 'Android 7.0 (API level 24)' };
+            const second_fake_avd = { name: 'AnotherAVD', target: 'Android 7.1 (API level 25)' };
+            const third_fake_avd = { name: 'AVDThree', target: 'Android 99.0 (API level 134)' };
+            emu.list_images.and.returnValue(Promise.resolve([fake_avd, second_fake_avd, third_fake_avd]));
+
+            return emu.best_image().then(best_avd => {
                 expect(best_avd).toBe(second_fake_avd);
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-            }).fin(done);
+            });
         });
-        it('should not try to compare API levels when an AVD definition is missing API level info', function (done) {
-            avds_promise.resolve([ { name: 'Samsung_S8_API_26',
+
+        it('should not try to compare API levels when an AVD definition is missing API level info', () => {
+            emu.list_images.and.returnValue(Promise.resolve([{
+                name: 'Samsung_S8_API_26',
                 device: 'Samsung S8+ (User)',
                 path: '/Users/daviesd/.android/avd/Samsung_S8_API_26.avd',
                 abi: 'google_apis/x86',
                 target: 'Android 8.0'
-            }]);
-            emu.best_image().then(function (best_avd) {
+            }]));
+
+            return emu.best_image().then(best_avd => {
                 expect(best_avd).toBeDefined();
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-            }).fin(done);
+            });
+        });
+    });
+
+    describe('list_started', () => {
+        it('should call adb devices with the emulators flag', () => {
+            const AdbSpy = jasmine.createSpyObj('Adb', ['devices']);
+            AdbSpy.devices.and.returnValue(Promise.resolve());
+            emu.__set__('Adb', AdbSpy);
+
+            return emu.list_started().then(() => {
+                expect(AdbSpy.devices).toHaveBeenCalledWith({emulators: true});
+            });
+        });
+    });
+
+    describe('get_available_port', () => {
+        let emus;
+
+        beforeEach(() => {
+            emus = [];
+            spyOn(emu, 'list_started').and.returnValue(Promise.resolve(emus));
+        });
+
+        it('should find the closest available port below 5584 for the emulator', () => {
+            const lowestUsedPort = 5565;
+            for (let i = 5584; i >= lowestUsedPort; i--) {
+                emus.push(`emulator-${i}`);
+            }
+
+            return emu.get_available_port().then(port => {
+                expect(port).toBe(lowestUsedPort - 1);
+            });
+        });
+
+        it('should throw an error if no port is available between 5554 and 5584', () => {
+            for (let i = 5584; i >= 5554; i--) {
+                emus.push(`emulator-${i}`);
+            }
+
+            return emu.get_available_port().then(
+                () => fail('Unexpectedly resolved'),
+                err => {
+                    expect(err).toEqual(jasmine.any(CordovaError));
+                }
+            );
+        });
+    });
+
+    describe('start', () => {
+        const port = 5555;
+        let emulator;
+        let AdbSpy;
+        let checkReqsSpy;
+        let childProcessSpy;
+        let shellJsSpy;
+
+        beforeEach(() => {
+            emulator = {
+                name: 'Samsung_S8_API_26',
+                device: 'Samsung S8+ (User)',
+                path: '/Users/daviesd/.android/avd/Samsung_S8_API_26.avd',
+                abi: 'google_apis/x86',
+                target: 'Android 8.0'
+            };
+
+            AdbSpy = jasmine.createSpyObj('Adb', ['shell']);
+            AdbSpy.shell.and.returnValue(Promise.resolve());
+            emu.__set__('Adb', AdbSpy);
+
+            checkReqsSpy = jasmine.createSpyObj('create_reqs', ['getAbsoluteAndroidCmd']);
+            emu.__set__('check_reqs', checkReqsSpy);
+
+            childProcessSpy = jasmine.createSpyObj('child_process', ['spawn']);
+            childProcessSpy.spawn.and.returnValue(jasmine.createSpyObj('spawnFns', ['unref']));
+            emu.__set__('child_process', childProcessSpy);
+
+            spyOn(emu, 'get_available_port').and.returnValue(Promise.resolve(port));
+            spyOn(emu, 'wait_for_emulator').and.returnValue(Promise.resolve('randomname'));
+            spyOn(emu, 'wait_for_boot').and.returnValue(Promise.resolve());
+
+            // Prevent pollution of the test logs
+            const proc = emu.__get__('process');
+            spyOn(proc.stdout, 'write').and.stub();
+
+            shellJsSpy = jasmine.createSpyObj('shelljs', ['which']);
+            shellJsSpy.which.and.returnValue('/dev/android-sdk/tools');
+            emu.__set__('shelljs', shellJsSpy);
+        });
+
+        it('should find an emulator if an id is not specified', () => {
+            spyOn(emu, 'best_image').and.returnValue(Promise.resolve(emulator));
+
+            return emu.start().then(() => {
+                // This is the earliest part in the code where we can hook in and check
+                // the emulator that has been selected.
+                const spawnArgs = childProcessSpy.spawn.calls.argsFor(0);
+                expect(spawnArgs[1]).toContain(emulator.name);
+            });
+        });
+
+        it('should use the specified emulator', () => {
+            spyOn(emu, 'best_image');
+
+            return emu.start(emulator.name).then(() => {
+                expect(emu.best_image).not.toHaveBeenCalled();
+
+                const spawnArgs = childProcessSpy.spawn.calls.argsFor(0);
+                expect(spawnArgs[1]).toContain(emulator.name);
+            });
+        });
+
+        it('should throw an error if no emulator is specified and no default is found', () => {
+            spyOn(emu, 'best_image').and.returnValue(Promise.resolve());
+
+            return emu.start().then(
+                () => fail('Unexpectedly resolved'),
+                err => {
+                    expect(err).toEqual(jasmine.any(CordovaError));
+                }
+            );
+        });
+
+        it('should unlock the screen after the emulator has booted', () => {
+            emu.wait_for_emulator.and.returnValue(Promise.resolve(emulator.name));
+            emu.wait_for_boot.and.returnValue(Promise.resolve(true));
+
+            return emu.start(emulator.name).then(() => {
+                expect(emu.wait_for_emulator).toHaveBeenCalledBefore(AdbSpy.shell);
+                expect(emu.wait_for_boot).toHaveBeenCalledBefore(AdbSpy.shell);
+                expect(AdbSpy.shell).toHaveBeenCalledWith(emulator.name, 'input keyevent 82');
+            });
+        });
+
+        it('should resolve with the emulator id after the emulator has started', () => {
+            emu.wait_for_emulator.and.returnValue(Promise.resolve(emulator.name));
+            emu.wait_for_boot.and.returnValue(Promise.resolve(true));
+
+            return emu.start(emulator.name).then(emulatorId => {
+                expect(emulatorId).toBe(emulator.name);
+            });
+        });
+
+        it('should resolve with null if starting the emulator times out', () => {
+            emu.wait_for_emulator.and.returnValue(Promise.resolve(emulator.name));
+            emu.wait_for_boot.and.returnValue(Promise.resolve(false));
+
+            return emu.start(emulator.name).then(emulatorId => {
+                expect(emulatorId).toBe(null);
+            });
+        });
+    });
+
+    describe('wait_for_emulator', () => {
+        const port = 5656;
+        const expectedEmulatorId = `emulator-${port}`;
+        let AdbSpy;
+
+        beforeEach(() => {
+            AdbSpy = jasmine.createSpyObj('Adb', ['shell']);
+            AdbSpy.shell.and.returnValue(Promise.resolve());
+            emu.__set__('Adb', AdbSpy);
+
+            spyOn(emu, 'wait_for_emulator').and.callThrough();
+        });
+
+        it('should resolve with the emulator id if the emulator has completed boot', () => {
+            AdbSpy.shell.and.callFake((emulatorId, shellArgs) => {
+                expect(emulatorId).toBe(expectedEmulatorId);
+                expect(shellArgs).toContain('getprop dev.bootcomplete');
+
+                return Promise.resolve('1'); // 1 means boot is complete
+            });
+
+            return emu.wait_for_emulator(port).then(emulatorId => {
+                expect(emulatorId).toBe(expectedEmulatorId);
+            });
+        });
+
+        it('should call itself again if the emulator is not ready', () => {
+            AdbSpy.shell.and.returnValues(
+                Promise.resolve('0'),
+                Promise.resolve('0'),
+                Promise.resolve('1')
+            );
+
+            return emu.wait_for_emulator(port).then(() => {
+                expect(emu.wait_for_emulator).toHaveBeenCalledTimes(3);
+            });
+        });
+
+        it('should call itself again if shell fails for a known reason', () => {
+            AdbSpy.shell.and.returnValues(
+                Promise.reject({message: 'device not found'}),
+                Promise.reject({message: 'device offline'}),
+                Promise.reject({message: 'device still connecting'}),
+                Promise.resolve('1')
+            );
+
+            return emu.wait_for_emulator(port).then(() => {
+                expect(emu.wait_for_emulator).toHaveBeenCalledTimes(4);
+            });
+        });
+
+        it('should throw an error if shell fails for an unknown reason', () => {
+            const errorMessage = { message: 'Some unknown error' };
+            AdbSpy.shell.and.returnValue(Promise.reject(errorMessage));
+
+            return emu.wait_for_emulator(port).then(
+                () => fail('Unexpectedly resolved'),
+                err => {
+                    expect(err).toBe(errorMessage);
+                }
+            );
+        });
+    });
+
+    describe('wait_for_boot', () => {
+        const port = 5656;
+        const emulatorId = `emulator-${port}`;
+        const psOutput = `
+            root      1     0     8504   1512  SyS_epoll_ 00000000 S /init
+            u0_a1     2044  1350  1423452 47256 SyS_epoll_ 00000000 S android.process.acore
+            u0_a51    2963  1350  1417724 37492 SyS_epoll_ 00000000 S com.google.process.gapps
+        `;
+
+        let AdbSpy;
+
+        beforeEach(() => {
+            // If we use Jasmine's fake clock, we need to re-require the target module,
+            // or else it will not work.
+            jasmine.clock().install();
+            emu = rewire('../../bin/templates/cordova/lib/emulator');
+
+            AdbSpy = jasmine.createSpyObj('Adb', ['shell']);
+            emu.__set__('Adb', AdbSpy);
+
+            spyOn(emu, 'wait_for_boot').and.callThrough();
+
+            // Stop the logs from being polluted
+            const proc = emu.__get__('process');
+            spyOn(proc.stdout, 'write').and.stub();
+        });
+
+        afterEach(() => {
+            jasmine.clock().uninstall();
+        });
+
+        it('should resolve with true if the system has booted', () => {
+            AdbSpy.shell.and.callFake((emuId, shellArgs) => {
+                expect(emuId).toBe(emulatorId);
+                expect(shellArgs).toContain('ps');
+
+                return Promise.resolve(psOutput);
+            });
+
+            return emu.wait_for_boot(emulatorId).then(isReady => {
+                expect(isReady).toBe(true);
+            });
+        });
+
+        it('should should check boot status at regular intervals until ready', () => {
+            const retryInterval = emu.__get__('CHECK_BOOTED_INTERVAL');
+            const RETRIES = 10;
+
+            let shellPromise = Promise.resolve('');
+            AdbSpy.shell.and.returnValue(shellPromise);
+
+            const waitPromise = emu.wait_for_boot(emulatorId);
+
+            let attempts = 0;
+            function tickTimer () {
+                shellPromise.then(() => {
+                    if (attempts + 1 === RETRIES) {
+                        AdbSpy.shell.and.returnValue(Promise.resolve(psOutput));
+                        jasmine.clock().tick(retryInterval);
+                    } else {
+                        attempts++;
+                        shellPromise = Promise.resolve('');
+                        AdbSpy.shell.and.returnValue(shellPromise);
+                        jasmine.clock().tick(retryInterval);
+                        tickTimer();
+                    }
+                });
+            }
+
+            tickTimer();
+
+            // After all the retries and eventual success, this is called
+            return waitPromise.then(isReady => {
+                expect(isReady).toBe(true);
+                expect(emu.wait_for_boot).toHaveBeenCalledTimes(RETRIES + 1);
+            });
+        });
+
+        it('should should check boot status at regular intervals until timeout', () => {
+            const retryInterval = emu.__get__('CHECK_BOOTED_INTERVAL');
+            const TIMEOUT = 9000;
+            const expectedRetries = Math.floor(TIMEOUT / retryInterval);
+
+            let shellPromise = Promise.resolve('');
+            AdbSpy.shell.and.returnValue(shellPromise);
+
+            const waitPromise = emu.wait_for_boot(emulatorId, TIMEOUT);
+
+            let attempts = 0;
+            function tickTimer () {
+                shellPromise.then(() => {
+                    attempts++;
+                    shellPromise = Promise.resolve('');
+                    AdbSpy.shell.and.returnValue(shellPromise);
+                    jasmine.clock().tick(retryInterval);
+
+                    if (attempts < expectedRetries) {
+                        tickTimer();
+                    }
+                });
+            }
+
+            tickTimer();
+
+            // After all the retries and eventual success, this is called
+            return waitPromise.then(isReady => {
+                expect(isReady).toBe(false);
+                expect(emu.wait_for_boot).toHaveBeenCalledTimes(expectedRetries + 1);
+            });
+        });
+    });
+
+    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 });
+            });
+        });
+    });
+
+    describe('install', () => {
+        let AndroidManifestSpy;
+        let AndroidManifestFns;
+        let AndroidManifestGetActivitySpy;
+        let AdbSpy;
+        let buildSpy;
+        let childProcessSpy;
+        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', 'uninstall']);
+            AdbSpy.shell.and.returnValue(Promise.resolve());
+            AdbSpy.start.and.returnValue(Promise.resolve());
+            AdbSpy.uninstall.and.returnValue(Promise.resolve());
+            emu.__set__('Adb', AdbSpy);
+
+            childProcessSpy = jasmine.createSpyObj('child_process', ['exec']);
+            childProcessSpy.exec.and.callFake((cmd, opts, callback) => callback());
+            emu.__set__('child_process', childProcessSpy);
+        });
+
+        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(() => {
+                const execCmd = childProcessSpy.exec.calls.argsFor(0)[0];
+                expect(execCmd).toContain(`-s ${target.target} install`);
+            });
+        });
+
+        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);
+
+                const execCmd = childProcessSpy.exec.calls.argsFor(0)[0];
+                expect(execCmd).toMatch(new RegExp(`install.*${apkPath}`));
+            });
+        });
+
+        it('should uninstall and reinstall app if failure is due to different certificates', () => {
+            let execAlreadyCalled;
+            childProcessSpy.exec.and.callFake((cmd, opts, callback) => {
+                if (!execAlreadyCalled) {
+                    execAlreadyCalled = true;
+                    callback(null, 'Failure: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES');
+                } else {
+                    callback();
+                }
+            });
+
+            return emu.install(target, {}).then(() => {
+                expect(childProcessSpy.exec).toHaveBeenCalledTimes(2);
+                expect(AdbSpy.uninstall).toHaveBeenCalled();
+            });
+        });
+
+        it('should throw any error not caused by different certificates', () => {
+            const errorMsg = 'Failure: Failed to install';
+            childProcessSpy.exec.and.callFake((cmd, opts, callback) => {
+                callback(null, 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}`);
+            });
         });
     });
 });


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org