cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dblot...@apache.org
Subject android commit: CB-9119 Adding lib/retry.js for retrying promise-returning functions. Retrying 'adb install' in emulator.js because it sometimes hangs.
Date Fri, 12 Jun 2015 18:53:01 GMT
Repository: cordova-android
Updated Branches:
  refs/heads/master eb70f0516 -> c0312f9b5


CB-9119 Adding lib/retry.js for retrying promise-returning functions. Retrying 'adb install'
in emulator.js because it sometimes hangs.


Project: http://git-wip-us.apache.org/repos/asf/cordova-android/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-android/commit/c0312f9b
Tree: http://git-wip-us.apache.org/repos/asf/cordova-android/tree/c0312f9b
Diff: http://git-wip-us.apache.org/repos/asf/cordova-android/diff/c0312f9b

Branch: refs/heads/master
Commit: c0312f9b502c5b947040244b0f4acfb05741e6c5
Parents: eb70f05
Author: Dmitry Blotsky <dmitry.blotsky@gmail.com>
Authored: Mon Jun 8 17:17:12 2015 -0700
Committer: Dmitry Blotsky <dmitry.blotsky@gmail.com>
Committed: Fri Jun 12 11:50:15 2015 -0700

----------------------------------------------------------------------
 bin/templates/cordova/lib/emulator.js | 98 +++++++++++++++++++++---------
 bin/templates/cordova/lib/exec.js     | 39 ++++++++++--
 bin/templates/cordova/lib/retry.js    | 66 ++++++++++++++++++++
 3 files changed, 167 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/c0312f9b/bin/templates/cordova/lib/emulator.js
----------------------------------------------------------------------
diff --git a/bin/templates/cordova/lib/emulator.js b/bin/templates/cordova/lib/emulator.js
index 5e79152..e81dd67 100644
--- a/bin/templates/cordova/lib/emulator.js
+++ b/bin/templates/cordova/lib/emulator.js
@@ -21,14 +21,22 @@
 
 /* jshint sub:true */
 
-var exec  = require('./exec'),
-    Q     = require('q'),
-    os    = require('os'),
-    appinfo = require('./appinfo'),
-    build = require('./build'),
-    child_process = require('child_process');
+var exec       = require('./exec');
+var appinfo    = require('./appinfo');
+var retry      = require('./retry');
+var build      = require('./build');
 var check_reqs = require('./check_reqs');
 
+var Q             = require('q');
+var os            = require('os');
+var child_process = require('child_process');
+
+// constants
+var ONE_SECOND              = 1000; // in milliseconds
+var INSTALL_COMMAND_TIMEOUT = 120 * ONE_SECOND; // in milliseconds
+var NUM_INSTALL_RETRIES     = 3;
+var EXEC_KILL_SIGNAL        = 'SIGKILL';
+
 /**
  * Returns a Promise for a list of emulator images in the form of objects
  * {
@@ -298,37 +306,67 @@ module.exports.resolveTarget = function(target) {
  * If no started emulators are found, error out.
  * Returns a promise.
  */
-module.exports.install = function(target, buildResults) {
-    return Q().then(function() {
-        if (target && typeof target == 'object') {
-            return target;
+module.exports.install = function(givenTarget, buildResults) {
+
+    var target;
+
+    // resolve the target emulator
+    return Q().then(function () {
+        if (givenTarget && typeof givenTarget == 'object') {
+            return givenTarget;
+        } else {
+            return module.exports.resolveTarget(givenTarget);
         }
-        return module.exports.resolveTarget(target);
-    }).then(function(resolvedTarget) {
-        var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
+
+    // set the resolved target
+    }).then(function (resolvedTarget) {
+        target = resolvedTarget;
+
+    // install the app
+    }).then(function () {
+
+        var apk_path    = build.findBestApkForArchitecture(buildResults, target.arch);
+        var execOptions = {
+            timeout:    INSTALL_COMMAND_TIMEOUT, // in milliseconds
+            killSignal: EXEC_KILL_SIGNAL
+        };
+
         console.log('Installing app on emulator...');
         console.log('Using apk: ' + apk_path);
-        return exec('adb -s ' + resolvedTarget.target + ' install -r -d "' + apk_path + '"',
os.tmpdir())
-        .then(function(output) {
+
+        var retriedInstall = retry.retryPromise(
+            NUM_INSTALL_RETRIES,
+            exec, 'adb -s ' + target.target + ' install -r -d "' + apk_path + '"', os.tmpdir(),
execOptions
+        );
+
+        return retriedInstall.then(function (output) {
             if (output.match(/Failure/)) {
                 return Q.reject('Failed to install apk to emulator: ' + output);
+            } else {
+                console.log('INSTALL SUCCESS');
             }
-            return Q();
-        }, function(err) {
+        }, function (err) {
             return Q.reject('Failed to install apk to emulator: ' + err);
-        }).then(function() {
-            //unlock screen
-            return exec('adb -s ' + resolvedTarget.target + ' shell input keyevent 82', os.tmpdir());
-        }).then(function() {
-            // launch the application
-            console.log('Launching application...');
-            var launchName = appinfo.getActivityName();
-            var cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN
-n ' + launchName;
-            return exec(cmd, os.tmpdir());
-        }).then(function(output) {
-            console.log('LAUNCH SUCCESS');
-        }, function(err) {
-            return Q.reject('Failed to launch app on emulator: ' + err);
         });
+
+    // unlock screen
+    }).then(function () {
+
+        console.log('Unlocking screen...');
+        return exec('adb -s ' + target.target + ' shell input keyevent 82', os.tmpdir());
+
+    // launch the application
+    }).then(function () {
+
+        console.log('Launching application...');
+        var launchName = appinfo.getActivityName();
+        var cmd = 'adb -s ' + target.target + ' shell am start -W -a android.intent.action.MAIN
-n ' + launchName;
+        return exec(cmd, os.tmpdir());
+
+    // report success or failure
+    }).then(function (output) {
+        console.log('LAUNCH SUCCESS');
+    }, function (err) {
+        return Q.reject('Failed to launch app on emulator: ' + err);
     });
 };

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/c0312f9b/bin/templates/cordova/lib/exec.js
----------------------------------------------------------------------
diff --git a/bin/templates/cordova/lib/exec.js b/bin/templates/cordova/lib/exec.js
index 342b6fb..798a93b 100644
--- a/bin/templates/cordova/lib/exec.js
+++ b/bin/templates/cordova/lib/exec.js
@@ -19,23 +19,50 @@
        under the License.
 */
 
-var child_process = require('child_process'),
-    Q       = require('q');
+var child_process = require("child_process");
+var Q             = require("q");
+
+// constants
+var DEFAULT_MAX_BUFFER = 1024000;
 
 // Takes a command and optional current working directory.
 // Returns a promise that either resolves with the stdout, or
 // rejects with an error message and the stderr.
-module.exports = function(cmd, opt_cwd) {
+//
+// WARNING:
+//         opt_cwd is an artifact of an old design, and must
+//         be removed in the future; the correct solution is
+//         to pass the options object the same way that
+//         child_process.exec expects
+//
+// NOTE:
+//      exec documented here - https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
+module.exports = function(cmd, opt_cwd, options) {
+
     var d = Q.defer();
+
+    if (typeof options === "undefined") {
+        options = {};
+    }
+
+    // override cwd to preserve old opt_cwd behavior
+    options.cwd = opt_cwd;
+
+    // set maxBuffer
+    if (typeof options.maxBuffer === "undefined") {
+        options.maxBuffer = DEFAULT_MAX_BUFFER;
+    }
+
     try {
-        child_process.exec(cmd, {cwd: opt_cwd, maxBuffer: 1024000}, function(err, stdout,
stderr) {
-            if (err) d.reject('Error executing "' + cmd + '": ' + stderr);
+        child_process.exec(cmd, options, function(err, stdout, stderr) {
+            if (err) d.reject("Error executing \"" + cmd + "\": " + stderr);
             else d.resolve(stdout);
         });
     } catch(e) {
-        console.error('error caught: ' + e);
+        console.error("error caught: " + e);
         d.reject(e);
     }
+
     return d.promise;
 };
 

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/c0312f9b/bin/templates/cordova/lib/retry.js
----------------------------------------------------------------------
diff --git a/bin/templates/cordova/lib/retry.js b/bin/templates/cordova/lib/retry.js
new file mode 100644
index 0000000..dc52a7d
--- /dev/null
+++ b/bin/templates/cordova/lib/retry.js
@@ -0,0 +1,66 @@
+#!/usr/bin/env node
+
+/*
+    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.
+*/
+
+/* jshint node: true */
+
+"use strict";
+
+/*
+ * 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 {Function} promiseFunction - A function that returns a promise.
+ * @arg {...}                      - Arguments to pass to promiseFunction.
+ *
+ * @returns {Promise}
+ */
+module.exports.retryPromise = function (attemts_left, promiseFunction) {
+
+    // NOTE:
+    //      get all trailing arguments, by skipping the first two (attemts_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;
+        },
+
+        // on rejection either retry, or throw the error
+        function onRejected(error) {
+
+            attemts_left -= 1;
+
+            if (attemts_left < 1) {
+                throw error;
+            }
+
+            console.log("A retried call failed. Retrying " + attemts_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);
+            return module.exports.retryPromise.apply(undefined, fullArguments);
+        }
+    );
+};


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


Mime
View raw message