Skip to content

Commit

Permalink
chore: execa - replace child_process in emulator
Browse files Browse the repository at this point in the history
  • Loading branch information
raphinesse authored and erisu committed Jan 6, 2020
1 parent 60b7b77 commit 3a9d2a2
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 46 deletions.
35 changes: 15 additions & 20 deletions bin/templates/cordova/lib/emulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ var check_reqs = require('./check_reqs');

var os = require('os');
var fs = require('fs');
var child_process = require('child_process');

// constants
var ONE_SECOND = 1000; // in milliseconds
Expand Down Expand Up @@ -294,8 +293,7 @@ module.exports.start = function (emulator_ID, boot_timeout) {
var emulator_dir = path.dirname(shelljs.which('emulator'));
var args = ['-avd', emulatorId, '-port', port];
// Don't wait for it to finish, since the emulator will probably keep running for a long time.
child_process
.spawn('emulator', args, { stdio: 'inherit', detached: true, cwd: emulator_dir })
execa('emulator', args, { stdio: 'inherit', detached: true, cwd: emulator_dir })
.unref();

// wait for emulator to start
Expand Down Expand Up @@ -474,24 +472,21 @@ module.exports.install = function (givenTarget, buildResults) {
function adbInstallWithOptions (target, apk, opts) {
events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...');

var command = 'adb -s ' + target + ' install -r "' + apk + '"';
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
// message to stdout, so we have to use RegExp matching to detect installation failure.
else if (/Failure/.test(stdout)) {
if (stdout.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
stdout += 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
' or sign and deploy the unsigned apk manually using Android tools.';
} else if (stdout.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
stdout += 'You\'re trying to install apk with a lower versionCode that is already installed.' +
'\nEither uninstall an app or increment the versionCode.';
}
const args = ['-s', target, 'install', '-r', apk];
return execa('adb', args, opts).then(({ stdout }) => {
// adb does not return an error code even if installation fails. Instead it puts a specific
// message to stdout, so we have to use RegExp matching to detect installation failure.
if (/Failure/.test(stdout)) {
if (stdout.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
stdout += 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
' or sign and deploy the unsigned apk manually using Android tools.';
} else if (stdout.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
stdout += 'You\'re trying to install apk with a lower versionCode that is already installed.' +
'\nEither uninstall an app or increment the versionCode.';
}

reject(new CordovaError('Failed to install apk to emulator: ' + stdout));
} else resolve(stdout);
});
throw new CordovaError('Failed to install apk to emulator: ' + stdout);
}
});
}

Expand Down
45 changes: 19 additions & 26 deletions spec/unit/emulator.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe('emulator', () => {
let emulator;
let AdbSpy;
let checkReqsSpy;
let childProcessSpy;
let execaSpy;
let shellJsSpy;

beforeEach(() => {
Expand All @@ -273,9 +273,10 @@ describe('emulator', () => {
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);
execaSpy = jasmine.createSpy('execa').and.returnValue(
jasmine.createSpyObj('spawnFns', ['unref'])
);
emu.__set__('execa', execaSpy);

spyOn(emu, 'get_available_port').and.returnValue(Promise.resolve(port));
spyOn(emu, 'wait_for_emulator').and.returnValue(Promise.resolve('randomname'));
Expand All @@ -296,7 +297,7 @@ describe('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);
const spawnArgs = execaSpy.calls.argsFor(0);
expect(spawnArgs[1]).toContain(emulator.name);
});
});
Expand All @@ -307,7 +308,7 @@ describe('emulator', () => {
return emu.start(emulator.name).then(() => {
expect(emu.best_image).not.toHaveBeenCalled();

const spawnArgs = childProcessSpy.spawn.calls.argsFor(0);
const spawnArgs = execaSpy.calls.argsFor(0);
expect(spawnArgs[1]).toContain(emulator.name);
});
});
Expand Down Expand Up @@ -581,7 +582,7 @@ describe('emulator', () => {
let AndroidManifestGetActivitySpy;
let AdbSpy;
let buildSpy;
let childProcessSpy;
let execaSpy;
let target;

beforeEach(() => {
Expand All @@ -602,9 +603,8 @@ describe('emulator', () => {
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);
execaSpy = jasmine.createSpy('execa').and.resolveTo({});
emu.__set__('execa', execaSpy);
});

it('should get the full target object if only id is specified', () => {
Expand All @@ -618,7 +618,7 @@ describe('emulator', () => {

it('should install to the passed target', () => {
return emu.install(target, {}).then(() => {
const execCmd = childProcessSpy.exec.calls.argsFor(0)[0];
const execCmd = execaSpy.calls.argsFor(0)[1].join(' ');
expect(execCmd).toContain(`-s ${target.target} install`);
});
});
Expand All @@ -636,33 +636,26 @@ describe('emulator', () => {
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}`));
const execCmd = execaSpy.calls.argsFor(0)[1].join(' ');
expect(execCmd).toContain(`install -r ${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();
}
});
execaSpy.and.returnValues(
...['Failure: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES', '']
.map(out => Promise.resolve({ stdout: out }))
);

return emu.install(target, {}).then(() => {
expect(childProcessSpy.exec).toHaveBeenCalledTimes(2);
expect(execaSpy).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);
});
execaSpy.and.resolveTo({ stdout: errorMsg });

return emu.install(target, {}).then(
() => fail('Unexpectedly resolved'),
Expand Down

0 comments on commit 3a9d2a2

Please sign in to comment.