Repository: cordova-medic Updated Branches: refs/heads/master f1662f346 -> 9e3d872a3
CB-10510: Reapply Android Emulator retry logic with process fix This brings back commits 90d06a39cbaf9313edae0fa3cc63a63003b65553 and 7682cd7205f153bc252ab8e1ef445169d34e11c2 which were reverted and adds a fix for the errors they were causing in buildbot. Project: http://git-wip-us.apache.org/repos/asf/cordova-medic/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-medic/commit/9e3d872a Tree: http://git-wip-us.apache.org/repos/asf/cordova-medic/tree/9e3d872a Diff: http://git-wip-us.apache.org/repos/asf/cordova-medic/diff/9e3d872a Branch: refs/heads/master Commit: 9e3d872a3fccd483b977b7199f0900146369d1c7 Parents: f1662f3 Author: riknoll <richard.b.kn...@gmail.com> Authored: Thu Feb 11 14:39:53 2016 -0800 Committer: riknoll <richard.b.kn...@gmail.com> Committed: Wed Feb 17 17:23:36 2016 -0800 ---------------------------------------------------------------------- lib/start-android-emulator.js | 91 ++++++++++++++++++++++++++++++ medic/medic-kill.js | 34 +++++++----- medic/medic-run.js | 111 +++++++++++++++++++++++++------------ 3 files changed, 187 insertions(+), 49 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-medic/blob/9e3d872a/lib/start-android-emulator.js ---------------------------------------------------------------------- diff --git a/lib/start-android-emulator.js b/lib/start-android-emulator.js new file mode 100644 index 0000000..b10530a --- /dev/null +++ b/lib/start-android-emulator.js @@ -0,0 +1,91 @@ +/* + * 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. + */ + +var medicKill = require("../medic/medic-kill"); +var util = require("./util"); +var path = require("path"); +var optimist = require("optimist"); + +var ANDROID_EMU_START_MAX_ATTEMPTS = 3; +var ANDROID_EMU_START_TIMEOUT = 180000; // in milliseconds (3 minutes) + + +/* + * 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, timeout) { + // 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, 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); + } + }); +} + + +function main() { + var argv = optimist + .usage("Usage: $0 {options}") + .demand("app") + .default("attempts", ANDROID_EMU_START_MAX_ATTEMPTS) + .default("timeout", ANDROID_EMU_START_TIMEOUT) + .argv; + + var workingDir = process.cwd(); + var appPath = path.isAbsolute(argv.app) ? argv.app : path.resolve(workingDir, argv.app); + + startAndroidEmulator(appPath, argv.attempts, argv.timeout) + .done(function(emulatorId) { + if (!emulatorId) { + process.exit(1); + } + }); +} + +main(); http://git-wip-us.apache.org/repos/asf/cordova-medic/blob/9e3d872a/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/9e3d872a/medic/medic-run.js ---------------------------------------------------------------------- diff --git a/medic/medic-run.js b/medic/medic-run.js index faba135..53c89c8 100644 --- a/medic/medic-run.js +++ b/medic/medic-run.js @@ -25,6 +25,7 @@ var fs = require("fs"); var path = require("path"); +var child_process = require("child_process"); var shelljs = require("shelljs"); var optimist = require("optimist"); @@ -263,7 +264,7 @@ function failedBecauseNoDevice(output) { function tryConnect(couchdbURI, pendingNumberOfTries, callback) { util.medicLog("checking if " + couchdbURI + " is up."); - + // check if results server is up request({ uri: couchdbURI, @@ -283,10 +284,36 @@ function tryConnect(couchdbURI, pendingNumberOfTries, callback) { } else { callback(); - } + } }); } +/* 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 +340,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 +365,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 +375,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 +383,57 @@ 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 is started separately, 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"); - } - }); - } else { - if (cordovaReturnedError(returnCode, output)) { - util.fatal("running on device 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). Buildbot doesn't like the child process that the + // emulator script launches because of how it sets stdio to + // "inherit". For that reason, we need to spawn a separate + // process and factor it out into a separate script. + // See https://nodejs.org/api/child_process.html#child_process_options_detached + util.medicLog("Attempting to start Android emulator"); + + var startEmuScript = path.resolve(__dirname, "..", "lib", "start-android-emulator.js"); + var absoluteAppPath = path.isAbsolute(appPath) ? appPath : path.resolve(workingDir, appPath); + + var startEmuResult = child_process.spawnSync("node", [startEmuScript, "--app", absoluteAppPath], {stdio: "ignore"}); + + if (startEmuResult.status > 0) { + util.fatal("Could not start Android emulator"); + } else { + util.medicLog("Android emulator started"); + runOnEmulator(); } + } else { + 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: commits-unsubscr...@cordova.apache.org For additional commands, e-mail: commits-h...@cordova.apache.org