Repository: cordova-medic Updated Branches: refs/heads/master cb2c7d530 -> 90d06a39c
CB-10510: Retry starting the Android emulator if it hangs on boot Project: http://git-wip-us.apache.org/repos/asf/cordova-medic/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-medic/commit/90d06a39 Tree: http://git-wip-us.apache.org/repos/asf/cordova-medic/tree/90d06a39 Diff: http://git-wip-us.apache.org/repos/asf/cordova-medic/diff/90d06a39 Branch: refs/heads/master Commit: 90d06a39cbaf9313edae0fa3cc63a63003b65553 Parents: cb2c7d5 Author: riknoll <[email protected]> Authored: Thu Feb 11 14:39:53 2016 -0800 Committer: riknoll <[email protected]> Committed: Tue Feb 16 14:34:27 2016 -0800 ---------------------------------------------------------------------- medic/medic-kill.js | 34 ++++++----- medic/medic-run.js | 143 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 132 insertions(+), 45 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-medic/blob/90d06a39/medic/medic-kill.js ---------------------------------------------------------------------- diff --git a/medic/medic-kill.js b/medic/medic-kill.js index 72b957f..2a77cde 100644 --- a/medic/medic-kill.js +++ b/medic/medic-kill.js @@ -37,9 +37,9 @@ function tasksOnPlatform(platformName) { return ["iOS Simulator"]; case util.ANDROID: if (util.isWindows()) { - return ["emulator-arm.exe", "adb.exe"]; + return ["emulator-arm.exe"]; } else { - return ["emulator64-x86", "emulator64-arm", "adb"]; + return ["emulator64-x86", "emulator64-arm"]; } break; case util.BLACKBERRY: @@ -83,21 +83,11 @@ function killTasks(taskNames) { }); } -// main -function main() { - +function killTasksForPlatform(platform) { // shell config shelljs.config.fatal = false; shelljs.config.silent = false; - // get args - var argv = optimist - .usage("Usage: $0 --platform {platform}") - .demand("platform") - .argv; - - var platform = argv.platform; - // get platform tasks var platformTasks = tasksOnPlatform(platform); @@ -109,4 +99,20 @@ function main() { killTasks(platformTasks); } -main(); +// main +function main() { + // get args + var argv = optimist + .usage("Usage: $0 --platform {platform}") + .demand("platform") + .argv; + + killTasksForPlatform(argv.platform); +} + +module.exports = killTasksForPlatform; + +// This script can be required or run directly +if (require.main === module) { + main(); +} http://git-wip-us.apache.org/repos/asf/cordova-medic/blob/90d06a39/medic/medic-run.js ---------------------------------------------------------------------- diff --git a/medic/medic-run.js b/medic/medic-run.js index faba135..e7d9482 100644 --- a/medic/medic-run.js +++ b/medic/medic-run.js @@ -25,6 +25,7 @@ var fs = require("fs"); var path = require("path"); +var process = require("process"); var shelljs = require("shelljs"); var optimist = require("optimist"); @@ -33,6 +34,8 @@ var request = require("request"); var util = require("../lib/util"); var testwait = require("../lib/testwait"); +var medicKill = require("./medic-kill"); + // constants var CORDOVA_MEDIC_DIR = "cordova-medic"; var DEFAULT_APP_PATH = "mobilespec"; @@ -47,6 +50,8 @@ var DEFAULT_TIMEOUT = 600; // in seconds var SERVER_RESPONSE_TIMEOUT = 15000; // in milliseconds var MAX_NUMBER_OF_TRIES = 3; var WAIT_TIME_TO_RETRY_CONNECTION = 15000; // in milliseconds +var ANDROID_EMU_START_MAX_ATTEMPTS = 3; +var ANDROID_EMU_START_TIMEOUT = 180000; // in milliseconds (3 minutes) // helpers function currentMillisecond() { @@ -287,6 +292,74 @@ function tryConnect(couchdbURI, pendingNumberOfTries, callback) { }); } +/* + * Attempts to start the Android emulator by calling the emulator.js script in + * the Android platform directory of the app. If the emulator fails to boot, we + * retry a specified number of times. + * + * @param {string} appPath An ABSOLUTE path to the app's project folder + * @param {number} numberOfTries Number of times to attempt to start the emulator + * + * @returns {promise} A promise that resolves to the ID of the emulator or + * null if it failed to start + */ +function startAndroidEmulator(appPath, numberOfTries) { + // We need to get the emulator script from within the Android platforms folder + var emuPath = path.join(appPath, "platforms", "android", "cordova", "lib", "emulator"); + var emulator = require(emuPath); + + var tryStart = function(numberTriesRemaining) { + return emulator.start(null, ANDROID_EMU_START_TIMEOUT) + .then(function(emulatorId) { + if (emulatorId) { + return emulatorId; + } else if (numberTriesRemaining > 0) { + // Emulator must have hung while booting, so we need to kill it + medicKill(util.ANDROID); + return tryStart(numberTriesRemaining - 1); + } else { + return null; + } + }); + }; + + // Check if the emulator has already been started + return emulator.list_started() + .then(function(started) { + if (started && started.length > 0) { + return started[0]; + } else { + return tryStart(numberOfTries); + } + }); +} + +/* Starts periodic polling to check for the mobilespec test results in CouchDB. + * After it finishes polling, it will terminate the process returning a 0 if + * results were found or 1 if they were not. + * + * @param {string} couchdbURI The URL for the couchdb instance + * @param {string} buildId The build ID to query the coudchdb for + * @param {number} timeout The amount of time in seconds to continue polling + */ +function startPollingForTestResults(couchdbURI, buildId, timeout) { + testwait.init(couchdbURI); + + // NOTE: + // timeout needs to be in milliseconds, but it's + // given in seconds, so we multiply by 1000 + testwait.waitTestsCompleted(buildId, timeout * 1000, false).then( + function onFulfilled(value) { + util.medicLog("Successfully found test results"); + process.exit(0); + }, + function onRejected(error) { + util.fatal("Could not find test results. Check the output of medic-log to see if the app crashed before it could upload them to couchdb."); + } + ); + util.medicLog("started waiting for test results"); +} + // main function main() { @@ -313,6 +386,8 @@ function main() { var entryPoint = argv.entry; var timeout = argv.timeout; + var workingDir = process.cwd(); + var cli = getLocalCLI(); // check that the app exists @@ -336,23 +411,6 @@ function main() { platformArgs = windowsSpecificPreparation(argv); } - // start waiting for test results - // NOTE: - // timeout needs to be in milliseconds, but it's - // given in seconds, so we multiply by 1000 - testwait.init(couchdbURI); - testwait.waitTestsCompleted(buildId, timeout * 1000, false).then( - function onFulfilled(value) { - util.medicLog("Successfully found test results"); - process.exit(0); - }, - function onRejected(error) { - console.error("Could not find test results. Check the output of medic-log to see if the app crashed before it could upload them to couchdb."); - process.exit(1); - } - ); - util.medicLog("started waiting for test results"); - // enter the app directory util.medicLog("moving into " + appPath); shelljs.pushd(appPath); @@ -363,8 +421,6 @@ function main() { var runCommandDevice = cli + " run --device " + platform + " -- " + platformArgs; // build the code - // NOTE: - // this is SYNCHRONOUS util.medicLog("running:"); util.medicLog(" " + buildCommand); var result = shelljs.exec(buildCommand, {silent: false, async: false}); @@ -373,26 +429,51 @@ function main() { } // run the code - // NOTE: - // this is ASYNCHRONOUS util.medicLog("running:"); util.medicLog(" " + runCommandDevice); - shelljs.exec(runCommandDevice, {silent: false, async: true}, function (returnCode, output) { - if (failedBecauseNoDevice(output)) { - util.medicLog("no device found, so switching to emulator"); + var runDeviceResult = shelljs.exec(runCommandDevice, {silent: false, async: false}); + + if (failedBecauseNoDevice(runDeviceResult.output)) { + util.medicLog("no device found, so switching to emulator"); + + // Because the Android emulator script uses promises, we need to + // abstract the run step into a function + var runOnEmulator = function() { util.medicLog("running:"); util.medicLog(" " + runCommandEmulator); - shelljs.exec(runCommandEmulator, {silent: false, async: true}, function (returnCode, output) { - if (cordovaReturnedError(returnCode, output)) { - util.fatal("running on emulator failed"); + + var runEmulatorResult = shelljs.exec(runCommandEmulator, {silent: false, async: false}); + if (cordovaReturnedError(runEmulatorResult.code, runEmulatorResult.output)) { + util.fatal("running on emulator failed"); + } else { + startPollingForTestResults(couchdbURI, buildId, timeout); + } + }; + + if (platform === util.ANDROID) { + // We need to start the emulator first. We can't use "cordova run" + // because sometimes the Android emulator hangs on Windows + // (CB-10510) and shelljs won't let us timeout + util.medicLog("Attempting to start Android emulator"); + startAndroidEmulator(path.isAbsolute(appPath) ? appPath : path.resolve(workingDir, appPath), ANDROID_EMU_START_MAX_ATTEMPTS) + .done(function(emulatorId) { + if (emulatorId) { + // Once the emulator is started, "cordova run" will use + // that emulator and not start another + runOnEmulator(); + } else { + util.fatal("Could not start the Android emulator"); } }); } else { - if (cordovaReturnedError(returnCode, output)) { - util.fatal("running on device failed"); - } + runOnEmulator(); } - }); + } else if (cordovaReturnedError(runDeviceResult.code, runDeviceResult.output)) { + util.fatal("running on device failed"); + } else { + util.medicLog("Finished waiting for run command"); + startPollingForTestResults(couchdbURI, buildId, timeout); + } }); } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
