refactor: remove copied Adb.install from emulator.install (#1108)

`emulator.install` contains a copy of the code of `Adb.install` just to
be able to pass custom options to `execa`.

This change removes that duplicated code in favor of a new option in
`Adb.install` that allows to pass options through to `execa`.
This commit is contained in:
Raphael von der Grün 2020-11-17 09:06:44 +01:00 committed by GitHub
parent c144c08112
commit 671e1fd1c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 27 additions and 45 deletions

View File

@ -53,13 +53,17 @@ Adb.devices = function (opts) {
});
};
Adb.install = function (target, packagePath, opts) {
Adb.install = function (target, packagePath, { replace = false, execOptions = {} } = {}) {
events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...');
var args = ['-s', target, 'install'];
if (opts && opts.replace) args.push('-r');
return execa('adb', args.concat(packagePath), { cwd: os.tmpdir() }).then(({ stdout: output }) => {
// 'adb install' seems to always returns no error, even if installation fails
// so we catching output to detect installation failure
if (replace) args.push('-r');
const opts = { cwd: os.tmpdir(), ...execOptions };
return execa('adb', args.concat(packagePath), opts).then(({ stdout: output }) => {
// 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 (output.match(/Failure/)) {
if (output.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
output += '\n\n' + 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
@ -69,7 +73,7 @@ Adb.install = function (target, packagePath, opts) {
'\nEither uninstall an app or increment the versionCode.';
}
return Promise.reject(new CordovaError('Failed to install apk to device: ' + output));
throw new CordovaError('Failed to install apk to target: ' + output);
}
});
};

View File

@ -30,7 +30,6 @@ var CordovaError = require('cordova-common').CordovaError;
var android_sdk = require('./android_sdk');
var check_reqs = require('./check_reqs');
var which = require('which');
var os = require('os');
// constants
const ONE_SECOND = 1000; // in milliseconds
@ -406,11 +405,6 @@ module.exports.install = function (givenTarget, buildResults) {
// or the app doesn't installed at all, so no error catching needed.
return Promise.resolve().then(function () {
var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
var execOptions = {
cwd: os.tmpdir(),
timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
killSignal: EXEC_KILL_SIGNAL
};
events.emit('log', 'Using apk: ' + apk_path);
events.emit('log', 'Package name: ' + pkgName);
@ -419,29 +413,18 @@ module.exports.install = function (givenTarget, buildResults) {
// A special function to call adb install in specific environment w/ specific options.
// Introduced as a part of fix for http://issues.apache.org/jira/browse/CB-9119
// to workaround sporadic emulator hangs
function adbInstallWithOptions (target, apk, opts) {
events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...');
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.';
}
throw new CordovaError('Failed to install apk to emulator: ' + stdout);
function adbInstallWithOptions (...args) {
return Adb.install(...args, {
replace: true,
execOptions: {
timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
killSignal: EXEC_KILL_SIGNAL
}
});
}
function installPromise () {
return adbInstallWithOptions(target.target, apk_path, execOptions).catch(function (error) {
return adbInstallWithOptions(target.target, apk_path).catch(function (error) {
// CB-9557 CB-10157 only uninstall and reinstall app if the one that
// is already installed on device was signed w/different certificate
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) { throw error; }
@ -452,7 +435,7 @@ module.exports.install = function (givenTarget, buildResults) {
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
return Adb.uninstall(target.target, pkgName).then(function () {
return adbInstallWithOptions(target.target, apk_path, execOptions);
return adbInstallWithOptions(target.target, apk_path);
});
});
}

View File

@ -582,7 +582,6 @@ describe('emulator', () => {
let AndroidManifestGetActivitySpy;
let AdbSpy;
let buildSpy;
let execaSpy;
let target;
beforeEach(() => {
@ -597,14 +596,12 @@ describe('emulator', () => {
AndroidManifestSpy = jasmine.createSpy('AndroidManifest').and.returnValue(AndroidManifestFns);
emu.__set__('AndroidManifest', AndroidManifestSpy);
AdbSpy = jasmine.createSpyObj('Adb', ['shell', 'start', 'uninstall']);
AdbSpy = jasmine.createSpyObj('Adb', ['shell', 'start', 'install', 'uninstall']);
AdbSpy.shell.and.returnValue(Promise.resolve());
AdbSpy.start.and.returnValue(Promise.resolve());
AdbSpy.install.and.returnValue(Promise.resolve());
AdbSpy.uninstall.and.returnValue(Promise.resolve());
emu.__set__('Adb', AdbSpy);
execaSpy = jasmine.createSpy('execa').and.resolveTo({});
emu.__set__('execa', execaSpy);
});
it('should get the full target object if only id is specified', () => {
@ -618,8 +615,7 @@ describe('emulator', () => {
it('should install to the passed target', () => {
return emu.install(target, {}).then(() => {
const execCmd = execaSpy.calls.argsFor(0)[1].join(' ');
expect(execCmd).toContain(`-s ${target.target} install`);
expect(AdbSpy.install.calls.argsFor(0)[0]).toBe(target.target);
});
});
@ -636,26 +632,25 @@ describe('emulator', () => {
return emu.install(target, buildResults).then(() => {
expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, target.arch);
const execCmd = execaSpy.calls.argsFor(0)[1].join(' ');
expect(execCmd).toContain(`install -r ${apkPath}`);
expect(AdbSpy.install.calls.argsFor(0)[1]).toBe(apkPath);
});
});
it('should uninstall and reinstall app if failure is due to different certificates', () => {
execaSpy.and.returnValues(
...['Failure: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES', '']
.map(out => Promise.resolve({ stdout: out }))
AdbSpy.install.and.returnValues(
Promise.reject('Failure: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES'),
Promise.resolve()
);
return emu.install(target, {}).then(() => {
expect(execaSpy).toHaveBeenCalledTimes(2);
expect(AdbSpy.install).toHaveBeenCalledTimes(2);
expect(AdbSpy.uninstall).toHaveBeenCalled();
});
});
it('should throw any error not caused by different certificates', () => {
const errorMsg = 'Failure: Failed to install';
execaSpy.and.resolveTo({ stdout: errorMsg });
AdbSpy.install.and.rejectWith(new CordovaError(errorMsg));
return emu.install(target, {}).then(
() => fail('Unexpectedly resolved'),