CB-9172 Improved emulator deploy stability. This closes #188.

- Use UUID to distinguish between launched emulators
- Wait for android.process.acore instead of init.svc.bootanim on emulator boot
- Increased retry timeout when installing app to the emulator
- If there is already a started/starting emulator, wait for it's boot instead of trying to deploy to it right away
This commit is contained in:
alsorokin 2015-07-03 11:26:11 +03:00 committed by Dmitry Blotsky
parent 1ad0665eb5
commit 64f89c5eda
2 changed files with 39 additions and 35 deletions

View File

@ -33,7 +33,8 @@ var child_process = require('child_process');
// constants // constants
var ONE_SECOND = 1000; // in milliseconds var ONE_SECOND = 1000; // in milliseconds
var INSTALL_COMMAND_TIMEOUT = 120 * ONE_SECOND; // in milliseconds var ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
var INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds
var NUM_INSTALL_RETRIES = 3; var NUM_INSTALL_RETRIES = 3;
var EXEC_KILL_SIGNAL = 'SIGKILL'; var EXEC_KILL_SIGNAL = 'SIGKILL';
@ -146,19 +147,18 @@ module.exports.list_targets = function() {
/* /*
* Starts an emulator with the given ID, * Starts an emulator with the given ID,
* and returns the started ID of that emulator. * and returns the started ID of that emulator.
* If no ID is given it will used the first image available, * If no ID is given it will use the first image available,
* if no image is available it will error out (maybe create one?). * if no image is available it will error out (maybe create one?).
* *
* Returns a promise. * Returns a promise.
*/ */
module.exports.start = function(emulator_ID) { module.exports.start = function(emulator_ID) {
var self = this; var self = this;
var emulator_id, num_started, started_emulators; var now = new Date();
var uuid = 'cordova_emulator_' + now.getTime();
var emulator_id;
return self.list_started() return Q().then(function(list) {
.then(function(list) {
started_emulators = list;
num_started = started_emulators.length;
if (!emulator_ID) { if (!emulator_ID) {
return self.list_images() return self.list_images()
.then(function(emulator_list) { .then(function(emulator_list) {
@ -182,24 +182,17 @@ module.exports.start = function(emulator_ID) {
} }
}).then(function() { }).then(function() {
var cmd = 'emulator'; var cmd = 'emulator';
var args = ['-avd', emulator_ID]; var uuidProp = 'emu.uuid=' + uuid;
var args = ['-avd', emulator_ID, '-prop', uuidProp];
var proc = child_process.spawn(cmd, args, { stdio: 'inherit', detached: true }); var proc = child_process.spawn(cmd, args, { stdio: 'inherit', detached: true });
proc.unref(); // Don't wait for it to finish, since the emulator will probably keep running for a long time. proc.unref(); // Don't wait for it to finish, since the emulator will probably keep running for a long time.
}).then(function() { }).then(function() {
// wait for emulator to start // wait for emulator to start
console.log('Waiting for emulator...'); console.log('Waiting for emulator...');
return self.wait_for_emulator(num_started); return self.wait_for_emulator(uuid);
}).then(function(new_started) { }).then(function(emId) {
if (new_started.length > 1) { emulator_id = emId;
for (var i in new_started) { if (!emulator_id) return Q.reject('ERROR : Failed to start emulator');
if (started_emulators.indexOf(new_started[i]) < 0) {
emulator_id = new_started[i];
}
}
} else {
emulator_id = new_started[0];
}
if (!emulator_id) return Q.reject('ERROR : Failed to start emulator, could not find new emulator');
//wait for emulator to boot up //wait for emulator to boot up
process.stdout.write('Booting up emulator (this may take a while)...'); process.stdout.write('Booting up emulator (this may take a while)...');
@ -216,31 +209,40 @@ module.exports.start = function(emulator_ID) {
}; };
/* /*
* Waits for the new emulator to apear on the started-emulator list. * Waits for an emulator with given uuid to apear on the started-emulator list.
* Returns a promise with a list of newly started emulators' IDs. * Returns a promise with this emulator's ID.
*/ */
module.exports.wait_for_emulator = function(num_running) { module.exports.wait_for_emulator = function(uuid) {
var self = this; var self = this;
return self.list_started() return self.list_started()
.then(function(new_started) { .then(function(new_started) {
if (new_started.length > num_running) { var emulator_id = null;
return new_started; var promises = [];
} else {
return Q.delay(1000).then(function() { new_started.forEach(function (emulator) {
return self.wait_for_emulator(num_running); promises.push(exec('adb -s ' + emulator + ' shell getprop emu.uuid', os.tmpdir())
}); .then(function (output) {
if (output.indexOf(uuid) >= 0) {
emulator_id = emulator;
} }
})
);
});
return Q.all(promises).then(function () {
return emulator_id || self.wait_for_emulator(uuid);
});
}); });
}; };
/* /*
* Waits for the boot animation property of the emulator to switch to 'stopped' * Waits for the core android process of the emulator to start
*/ */
module.exports.wait_for_boot = function(emulator_id) { module.exports.wait_for_boot = function(emulator_id) {
var self = this; var self = this;
return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim', os.tmpdir()) return exec('adb -s ' + emulator_id + ' shell ps', os.tmpdir())
.then(function(output) { .then(function(output) {
if (output.match(/stopped/)) { if (output.match(/android\.process\.acore/)) {
return; return;
} else { } else {
process.stdout.write('.'); process.stdout.write('.');

View File

@ -139,7 +139,9 @@ var path = require('path'),
}).then(function(resolvedTarget) { }).then(function(resolvedTarget) {
return build.run(buildFlags, resolvedTarget).then(function(buildResults) { return build.run(buildFlags, resolvedTarget).then(function(buildResults) {
if (resolvedTarget.isEmulator) { if (resolvedTarget.isEmulator) {
return emulator.wait_for_boot(resolvedTarget.target).then(function () {
return emulator.install(resolvedTarget, buildResults); return emulator.install(resolvedTarget, buildResults);
});
} }
return device.install(resolvedTarget, buildResults); return device.install(resolvedTarget, buildResults);
}); });