chore: replace superspawn & child_process with execa (#862)

* chore: added execa dependency

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

* chore: execa - drop superspawn in android_sdk

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

* chore: execa - drop superspawn in build

* chore: execa - drop superspawn in check_reqs

Plus: Remove useless trimming of execa output

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

* chore: execa - drop superspawn in emulator

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

* chore: execa - drop superspawn in device

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

* chore: execa - drop superspawn in run_java_unit_tests

* chore: execa - drop superspawn in ProjectBuilder

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

* chore: execa - drop superspawn in adb

* chore: execa - drop superspawn in plugin.spec

* chore: execa - replace child_process in log

* chore: execa - replace child_process in check_reqs

* chore: execa - replace child_process in emulator

Co-authored-by: エリス <erisu@users.noreply.github.com>
This commit is contained in:
Raphael von der Grün 2020-01-06 23:15:22 +01:00 committed by GitHub
parent e3cc75caff
commit fd57909730
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 138 additions and 157 deletions

View File

@ -19,8 +19,8 @@
var Q = require('q'); var Q = require('q');
var os = require('os'); var os = require('os');
var execa = require('execa');
var events = require('cordova-common').events; var events = require('cordova-common').events;
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var Adb = {}; var Adb = {};
@ -44,7 +44,7 @@ function isEmulator (line) {
* devices/emulators * devices/emulators
*/ */
Adb.devices = function (opts) { Adb.devices = function (opts) {
return spawn('adb', ['devices'], { cwd: os.tmpdir() }).then(function (output) { return execa('adb', ['devices'], { cwd: os.tmpdir() }).then(({ stdout: output }) => {
return output.split('\n').filter(function (line) { return output.split('\n').filter(function (line) {
// Filter out either real devices or emulators, depending on options // Filter out either real devices or emulators, depending on options
return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line); return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line);
@ -58,7 +58,7 @@ Adb.install = function (target, packagePath, opts) {
events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...'); events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...');
var args = ['-s', target, 'install']; var args = ['-s', target, 'install'];
if (opts && opts.replace) args.push('-r'); if (opts && opts.replace) args.push('-r');
return spawn('adb', args.concat(packagePath), { cwd: os.tmpdir() }).then(function (output) { return execa('adb', args.concat(packagePath), { cwd: os.tmpdir() }).then(({ stdout: output }) => {
// 'adb install' seems to always returns no error, even if installation fails // 'adb install' seems to always returns no error, even if installation fails
// so we catching output to detect installation failure // so we catching output to detect installation failure
if (output.match(/Failure/)) { if (output.match(/Failure/)) {
@ -77,24 +77,24 @@ Adb.install = function (target, packagePath, opts) {
Adb.uninstall = function (target, packageId) { Adb.uninstall = function (target, packageId) {
events.emit('verbose', 'Uninstalling package ' + packageId + ' from target ' + target + '...'); events.emit('verbose', 'Uninstalling package ' + packageId + ' from target ' + target + '...');
return spawn('adb', ['-s', target, 'uninstall', packageId], { cwd: os.tmpdir() }); return execa('adb', ['-s', target, 'uninstall', packageId], { cwd: os.tmpdir() }).then(({ stdout }) => stdout);
}; };
Adb.shell = function (target, shellCommand) { Adb.shell = function (target, shellCommand) {
events.emit('verbose', 'Running adb shell command "' + shellCommand + '" on target ' + target + '...'); events.emit('verbose', 'Running adb shell command "' + shellCommand + '" on target ' + target + '...');
var args = ['-s', target, 'shell']; var args = ['-s', target, 'shell'];
shellCommand = shellCommand.split(/\s+/); shellCommand = shellCommand.split(/\s+/);
return spawn('adb', args.concat(shellCommand), { cwd: os.tmpdir() }).catch(function (output) { return execa('adb', args.concat(shellCommand), { cwd: os.tmpdir() }).catch((error) => {
return Q.reject(new CordovaError('Failed to execute shell command "' + return Q.reject(new CordovaError('Failed to execute shell command "' +
shellCommand + '"" on device: ' + output)); shellCommand + '"" on device: ' + error));
}); });
}; };
Adb.start = function (target, activityName) { Adb.start = function (target, activityName) {
events.emit('verbose', 'Starting application "' + activityName + '" on target ' + target + '...'); events.emit('verbose', 'Starting application "' + activityName + '" on target ' + target + '...');
return Adb.shell(target, 'am start -W -a android.intent.action.MAIN -n' + activityName).catch(function (output) { return Adb.shell(target, 'am start -W -a android.intent.action.MAIN -n' + activityName).catch((error) => {
return Q.reject(new CordovaError('Failed to start application "' + return Q.reject(new CordovaError('Failed to start application "' +
activityName + '"" on device: ' + output)); activityName + '"" on device: ' + error));
}); });
}; };

View File

@ -17,7 +17,7 @@
under the License. under the License.
*/ */
var superspawn = require('cordova-common').superspawn; const execa = require('execa');
var suffix_number_regex = /(\d+)$/; var suffix_number_regex = /(\d+)$/;
// Used for sorting Android targets, example strings to sort: // Used for sorting Android targets, example strings to sort:
@ -77,11 +77,11 @@ function parse_targets (output) {
} }
module.exports.list_targets_with_android = function () { module.exports.list_targets_with_android = function () {
return superspawn.spawn('android', ['list', 'target']).then(parse_targets); return execa('android', ['list', 'target']).then(result => parse_targets(result.stdout));
}; };
module.exports.list_targets_with_avdmanager = function () { module.exports.list_targets_with_avdmanager = function () {
return superspawn.spawn('avdmanager', ['list', 'target']).then(parse_targets); return execa('avdmanager', ['list', 'target']).then(result => parse_targets(result.stdout));
}; };
module.exports.list_targets = function () { module.exports.list_targets = function () {

View File

@ -28,7 +28,7 @@ var Adb = require('./Adb');
var builders = require('./builders/builders'); var builders = require('./builders/builders');
var events = require('cordova-common').events; var events = require('cordova-common').events;
var spawn = require('cordova-common').superspawn.spawn; const execa = require('execa');
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var PackageType = require('./PackageType'); var PackageType = require('./PackageType');
@ -212,11 +212,11 @@ module.exports.detectArchitecture = function (target) {
// Could probably find a x-platform version of killall, but I'm not actually // Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines. // sure that this scenario even happens on non-OSX machines.
events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.'); events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.');
return spawn('killall', ['adb']).then(function () { return execa('killall', ['adb']).then(function () {
return helper().then(null, function () { return helper().then(null, function () {
// The double kill is sadly often necessary, at least on mac. // The double kill is sadly often necessary, at least on mac.
events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.'); events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.');
return spawn('killall', ['adb']).then(function () { return execa('killall', ['adb']).then(function () {
return helper().then(null, function () { return helper().then(null, function () {
return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.')); return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.'));
}); });

View File

@ -17,11 +17,10 @@
under the License. under the License.
*/ */
var Q = require('q');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var shell = require('shelljs'); var shell = require('shelljs');
var spawn = require('cordova-common').superspawn.spawn; const execa = require('execa');
var events = require('cordova-common').events; var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var check_reqs = require('../check_reqs'); var check_reqs = require('../check_reqs');
@ -80,7 +79,7 @@ class ProjectBuilder {
if (fs.existsSync(gradlePath)) { if (fs.existsSync(gradlePath)) {
// Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows // Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows
} else { } else {
return spawn(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], { stdio: 'inherit' }); return execa(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], { stdio: 'inherit' });
} }
} }
@ -250,16 +249,16 @@ class ProjectBuilder {
var wrapper = path.join(this.root, 'gradlew'); var wrapper = path.join(this.root, 'gradlew');
var args = this.getArgs(opts.buildType === 'debug' ? 'debug' : 'release', opts); var args = this.getArgs(opts.buildType === 'debug' ? 'debug' : 'release', opts);
return spawn(wrapper, args, { stdio: 'inherit' }) return execa(wrapper, args, { stdio: 'inherit' })
.catch(function (error) { .catch(function (error) {
if (error.toString().indexOf('failed to find target with hash string') >= 0) { if (error.toString().indexOf('failed to find target with hash string') >= 0) {
return check_reqs.check_android_target(error).then(function () { return check_reqs.check_android_target(error).then(function () {
// If due to some odd reason - check_android_target succeeds // If due to some odd reason - check_android_target succeeds
// we should still fail here. // we should still fail here.
return Q.reject(error); throw error;
}); });
} }
return Q.reject(error); throw error;
}); });
} }
@ -267,9 +266,7 @@ class ProjectBuilder {
var builder = this; var builder = this;
var wrapper = path.join(this.root, 'gradlew'); var wrapper = path.join(this.root, 'gradlew');
var args = builder.getArgs('clean', opts); var args = builder.getArgs('clean', opts);
return Q().then(function () { return execa(wrapper, args, { stdio: 'inherit' })
return spawn(wrapper, args, { stdio: 'inherit' });
})
.then(function () { .then(function () {
shell.rm('-rf', path.join(builder.root, 'out')); shell.rm('-rf', path.join(builder.root, 'out'));

View File

@ -19,8 +19,8 @@
under the License. under the License.
*/ */
const execa = require('execa');
var shelljs = require('shelljs'); var shelljs = require('shelljs');
var child_process = require('child_process');
var Q = require('q'); var Q = require('q');
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
@ -28,7 +28,6 @@ var os = require('os');
var REPO_ROOT = path.join(__dirname, '..', '..', '..', '..'); var REPO_ROOT = path.join(__dirname, '..', '..', '..', '..');
var PROJECT_ROOT = path.join(__dirname, '..', '..'); var PROJECT_ROOT = path.join(__dirname, '..', '..');
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var superspawn = require('cordova-common').superspawn;
var android_sdk = require('./android_sdk'); var android_sdk = require('./android_sdk');
function forgivingWhichSync (cmd) { function forgivingWhichSync (cmd) {
@ -71,7 +70,7 @@ module.exports.get_target = function () {
// Returns a promise. Called only by build and clean commands. // Returns a promise. Called only by build and clean commands.
module.exports.check_ant = function () { module.exports.check_ant = function () {
return superspawn.spawn('ant', ['-version']).then(function (output) { return execa('ant', ['-version']).then(({ stdout: output }) => {
// Parse Ant version from command output // Parse Ant version from command output
return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1]; return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
}).catch(function (err) { }).catch(function (err) {
@ -89,7 +88,7 @@ module.exports.get_gradle_wrapper = function () {
// OK, This hack only works on Windows, not on Mac OS or Linux. We will be deleting this eventually! // OK, This hack only works on Windows, not on Mac OS or Linux. We will be deleting this eventually!
if (module.exports.isWindows()) { if (module.exports.isWindows()) {
var result = child_process.spawnSync(path.join(__dirname, 'getASPath.bat')); var result = execa.sync(path.join(__dirname, 'getASPath.bat'));
// console.log('result.stdout =' + result.stdout.toString()); // console.log('result.stdout =' + result.stdout.toString());
// console.log('result.stderr =' + result.stderr.toString()); // console.log('result.stderr =' + result.stderr.toString());
@ -157,8 +156,8 @@ module.exports.check_java = function () {
var find_java = '/usr/libexec/java_home'; var find_java = '/usr/libexec/java_home';
var default_java_error_msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting it manually.'; var default_java_error_msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting it manually.';
if (fs.existsSync(find_java)) { if (fs.existsSync(find_java)) {
return superspawn.spawn(find_java).then(function (stdout) { return execa(find_java).then(({ stdout }) => {
process.env['JAVA_HOME'] = stdout.trim(); process.env['JAVA_HOME'] = stdout;
}).catch(function (err) { }).catch(function (err) {
if (err) { if (err) {
throw new CordovaError(default_java_error_msg); throw new CordovaError(default_java_error_msg);
@ -194,11 +193,9 @@ module.exports.check_java = function () {
} }
} }
}).then(function () { }).then(function () {
return Q.denodeify(child_process.exec)('javac -version') return execa('javac', ['-version'], { all: true })
.then(outputs => { .then(({ all: output }) => {
// outputs contains two entries: stdout and stderr
// Java <= 8 writes version info to stderr, Java >= 9 to stdout // Java <= 8 writes version info to stderr, Java >= 9 to stdout
const output = outputs.join('').trim();
const match = /javac\s+([\d.]+)/i.exec(output); const match = /javac\s+([\d.]+)/i.exec(output);
return match && match[1]; return match && match[1];
}, () => { }, () => {

View File

@ -19,11 +19,11 @@
under the License. under the License.
*/ */
const execa = require('execa');
var build = require('./build'); var build = require('./build');
var path = require('path'); var path = require('path');
var Adb = require('./Adb'); var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest'); var AndroidManifest = require('./AndroidManifest');
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var events = require('cordova-common').events; var events = require('cordova-common').events;
@ -37,7 +37,7 @@ module.exports.list = function (lookHarder) {
// adb kill-server doesn't seem to do the trick. // adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually // Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines. // sure that this scenario even happens on non-OSX machines.
return spawn('killall', ['adb']).then(function () { return execa('killall', ['adb']).then(function () {
events.emit('verbose', 'Restarting adb to see if more devices are detected.'); events.emit('verbose', 'Restarting adb to see if more devices are detected.');
return Adb.devices(); return Adb.devices();
}, function () { }, function () {

View File

@ -19,6 +19,7 @@
under the License. under the License.
*/ */
const execa = require('execa');
var android_versions = require('android-versions'); var android_versions = require('android-versions');
var retry = require('./retry'); var retry = require('./retry');
var build = require('./build'); var build = require('./build');
@ -26,7 +27,6 @@ var path = require('path');
var Adb = require('./Adb'); var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest'); var AndroidManifest = require('./AndroidManifest');
var events = require('cordova-common').events; var events = require('cordova-common').events;
var superspawn = require('cordova-common').superspawn;
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var shelljs = require('shelljs'); var shelljs = require('shelljs');
var android_sdk = require('./android_sdk'); var android_sdk = require('./android_sdk');
@ -34,7 +34,6 @@ var check_reqs = require('./check_reqs');
var os = require('os'); var os = require('os');
var fs = require('fs'); var fs = require('fs');
var child_process = require('child_process');
// constants // constants
var ONE_SECOND = 1000; // in milliseconds var ONE_SECOND = 1000; // in milliseconds
@ -53,7 +52,7 @@ function forgivingWhichSync (cmd) {
} }
module.exports.list_images_using_avdmanager = function () { module.exports.list_images_using_avdmanager = function () {
return superspawn.spawn('avdmanager', ['list', 'avd']).then(function (output) { return execa('avdmanager', ['list', 'avd']).then(({ stdout: output }) => {
var response = output.split('\n'); var response = output.split('\n');
var emulator_list = []; var emulator_list = [];
for (var i = 1; i < response.length; i++) { for (var i = 1; i < response.length; i++) {
@ -113,7 +112,7 @@ module.exports.list_images_using_avdmanager = function () {
}; };
module.exports.list_images_using_android = function () { module.exports.list_images_using_android = function () {
return superspawn.spawn('android', ['list', 'avd']).then(function (output) { return execa('android', ['list', 'avd']).then(({ stdout: output }) => {
var response = output.split('\n'); var response = output.split('\n');
var emulator_list = []; var emulator_list = [];
for (var i = 1; i < response.length; i++) { for (var i = 1; i < response.length; i++) {
@ -229,7 +228,7 @@ module.exports.list_started = function () {
// Returns a promise. // Returns a promise.
// TODO: we should remove this, there's a more robust method under android_sdk.js // TODO: we should remove this, there's a more robust method under android_sdk.js
module.exports.list_targets = function () { module.exports.list_targets = function () {
return superspawn.spawn('android', ['list', 'targets'], { cwd: os.tmpdir() }).then(function (output) { return execa('android', ['list', 'targets'], { cwd: os.tmpdir() }).then(({ stdout: output }) => {
var target_out = output.split('\n'); var target_out = output.split('\n');
var targets = []; var targets = [];
for (var i = target_out.length; i >= 0; i--) { for (var i = target_out.length; i >= 0; i--) {
@ -294,8 +293,7 @@ module.exports.start = function (emulator_ID, boot_timeout) {
var emulator_dir = path.dirname(shelljs.which('emulator')); var emulator_dir = path.dirname(shelljs.which('emulator'));
var args = ['-avd', emulatorId, '-port', port]; var args = ['-avd', emulatorId, '-port', port];
// Don't wait for it to finish, since the emulator will probably keep running for a long time. // Don't wait for it to finish, since the emulator will probably keep running for a long time.
child_process execa('emulator', args, { stdio: 'inherit', detached: true, cwd: emulator_dir })
.spawn('emulator', args, { stdio: 'inherit', detached: true, cwd: emulator_dir })
.unref(); .unref();
// wait for emulator to start // wait for emulator to start
@ -387,22 +385,22 @@ module.exports.wait_for_boot = function (emulator_id, time_remaining) {
module.exports.create_image = function (name, target) { module.exports.create_image = function (name, target) {
console.log('Creating new avd named ' + name); console.log('Creating new avd named ' + name);
if (target) { if (target) {
return superspawn.spawn('android', ['create', 'avd', '--name', name, '--target', target]).then(null, function (error) { return execa('android', ['create', 'avd', '--name', name, '--target', target]).then(null, function (error) {
console.error('ERROR : Failed to create emulator image : '); console.error('ERROR : Failed to create emulator image : ');
console.error(' Do you have the latest android targets including ' + target + '?'); console.error(' Do you have the latest android targets including ' + target + '?');
console.error(error); console.error(error.message);
}); });
} else { } else {
console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.'); console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.');
// TODO: there's a more robust method for finding targets in android_sdk.js // TODO: there's a more robust method for finding targets in android_sdk.js
return superspawn.spawn('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]]).then(function () { return execa('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]]).then(function () {
// TODO: This seems like another error case, even though it always happens. // TODO: This seems like another error case, even though it always happens.
console.error('ERROR : Unable to create an avd emulator, no targets found.'); console.error('ERROR : Unable to create an avd emulator, no targets found.');
console.error('Ensure you have targets available by running the "android" command'); console.error('Ensure you have targets available by running the "android" command');
return Promise.reject(new CordovaError()); return Promise.reject(new CordovaError());
}, function (error) { }, function (error) {
console.error('ERROR : Failed to create emulator image : '); console.error('ERROR : Failed to create emulator image : ');
console.error(error); console.error(error.message);
}); });
} }
}; };
@ -474,13 +472,11 @@ module.exports.install = function (givenTarget, buildResults) {
function adbInstallWithOptions (target, apk, opts) { function adbInstallWithOptions (target, apk, opts) {
events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...'); events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...');
var command = 'adb -s ' + target + ' install -r "' + apk + '"'; const args = ['-s', target, 'install', '-r', apk];
return new Promise(function (resolve, reject) { return execa('adb', args, opts).then(({ stdout }) => {
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 // 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. // message to stdout, so we have to use RegExp matching to detect installation failure.
else if (/Failure/.test(stdout)) { if (/Failure/.test(stdout)) {
if (stdout.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) { if (stdout.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
stdout += 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' + stdout += 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
' or sign and deploy the unsigned apk manually using Android tools.'; ' or sign and deploy the unsigned apk manually using Android tools.';
@ -489,9 +485,8 @@ module.exports.install = function (givenTarget, buildResults) {
'\nEither uninstall an app or increment the versionCode.'; '\nEither uninstall an app or increment the versionCode.';
} }
reject(new CordovaError('Failed to install apk to emulator: ' + stdout)); throw new CordovaError('Failed to install apk to emulator: ' + stdout);
} else resolve(stdout); }
});
}); });
} }

View File

@ -21,8 +21,7 @@
var path = require('path'); var path = require('path');
var os = require('os'); var os = require('os');
var Q = require('q'); var execa = require('execa');
var child_process = require('child_process');
var ROOT = path.join(__dirname, '..', '..'); var ROOT = path.join(__dirname, '..', '..');
/* /*
@ -30,8 +29,7 @@ var ROOT = path.join(__dirname, '..', '..');
* Returns a promise. * Returns a promise.
*/ */
module.exports.run = function () { module.exports.run = function () {
var d = Q.defer(); var adb = execa('adb', ['logcat'], { cwd: os.tmpdir(), stderr: 'inherit' });
var adb = child_process.spawn('adb', ['logcat'], { cwd: os.tmpdir() });
adb.stdout.on('data', function (data) { adb.stdout.on('data', function (data) {
var lines = data ? data.toString().split('\n') : []; var lines = data ? data.toString().split('\n') : [];
@ -39,14 +37,7 @@ module.exports.run = function () {
console.log(out.join('\n')); console.log(out.join('\n'));
}); });
adb.stderr.on('data', console.error); return adb;
adb.on('close', function (code) {
if (code > 0) {
d.reject('Failed to run logcat command.');
} else d.resolve();
});
return d.promise;
}; };
module.exports.help = function () { module.exports.help = function () {

View File

@ -32,6 +32,7 @@
"android-versions": "^1.4.0", "android-versions": "^1.4.0",
"compare-func": "^1.3.2", "compare-func": "^1.3.2",
"cordova-common": "^3.2.0", "cordova-common": "^3.2.0",
"execa": "^3.2.0",
"nopt": "^4.0.1", "nopt": "^4.0.1",
"properties-parser": "^0.3.1", "properties-parser": "^0.3.1",
"q": "^1.5.1", "q": "^1.5.1",

View File

@ -21,7 +21,8 @@ const os = require('os');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const shell = require('shelljs'); const shell = require('shelljs');
const { PluginInfoProvider, superspawn } = require('cordova-common'); const execa = require('execa');
const { PluginInfoProvider } = require('cordova-common');
const createBin = path.join(__dirname, '../../bin/create'); const createBin = path.join(__dirname, '../../bin/create');
const fakePluginPath = path.join(__dirname, 'fixtures/cordova-plugin-fake'); const fakePluginPath = path.join(__dirname, 'fixtures/cordova-plugin-fake');
@ -44,7 +45,7 @@ describe('plugin add', function () {
const pluginInfo = new PluginInfoProvider().get(fakePluginPath); const pluginInfo = new PluginInfoProvider().get(fakePluginPath);
return Promise.resolve() return Promise.resolve()
.then(() => superspawn.spawn(createBin, [projectPath, projectid, projectname])) .then(() => execa(createBin, [projectPath, projectid, projectname]))
.then(() => { .then(() => {
const Api = require(path.join(projectPath, 'cordova/Api.js')); const Api = require(path.join(projectPath, 'cordova/Api.js'));
return new Api('android', projectPath).addPlugin(pluginInfo); return new Api('android', projectPath).addPlugin(pluginInfo);

View File

@ -33,12 +33,12 @@ emulator-5554\tdevice
const downgradeError = 'adb: failed to install app.apk: Failure[INSTALL_FAILED_VERSION_DOWNGRADE]'; const downgradeError = 'adb: failed to install app.apk: Failure[INSTALL_FAILED_VERSION_DOWNGRADE]';
let Adb; let Adb;
let spawnSpy; let execaSpy;
beforeEach(() => { beforeEach(() => {
Adb = rewire('../../bin/templates/cordova/lib/Adb'); Adb = rewire('../../bin/templates/cordova/lib/Adb');
spawnSpy = jasmine.createSpy('spawn'); execaSpy = jasmine.createSpy('execa');
Adb.__set__('spawn', spawnSpy); Adb.__set__('execa', execaSpy);
}); });
describe('isDevice', () => { describe('isDevice', () => {
@ -61,7 +61,7 @@ emulator-5554\tdevice
describe('devices', () => { describe('devices', () => {
beforeEach(() => { beforeEach(() => {
spawnSpy.and.returnValue(Promise.resolve(adbOutput)); execaSpy.and.returnValue(Promise.resolve({ stdout: adbOutput }));
}); });
it('should return only devices if no options are specified', () => { it('should return only devices if no options are specified', () => {
@ -81,12 +81,12 @@ emulator-5554\tdevice
describe('install', () => { describe('install', () => {
beforeEach(() => { beforeEach(() => {
spawnSpy.and.returnValue(Promise.resolve('')); execaSpy.and.returnValue(Promise.resolve({ stdout: '' }));
}); });
it('should target the passed device id to adb', () => { it('should target the passed device id to adb', () => {
return Adb.install(deviceId).then(() => { return Adb.install(deviceId).then(() => {
const args = spawnSpy.calls.argsFor(0); const args = execaSpy.calls.argsFor(0);
expect(args[0]).toBe('adb'); expect(args[0]).toBe('adb');
const adbArgs = args[1].join(' '); const adbArgs = args[1].join(' ');
@ -96,7 +96,7 @@ emulator-5554\tdevice
it('should add the -r flag if opts.replace is set', () => { it('should add the -r flag if opts.replace is set', () => {
return Adb.install(deviceId, '', { replace: true }).then(() => { return Adb.install(deviceId, '', { replace: true }).then(() => {
const adbArgs = spawnSpy.calls.argsFor(0)[1]; const adbArgs = execaSpy.calls.argsFor(0)[1];
expect(adbArgs).toContain('-r'); expect(adbArgs).toContain('-r');
}); });
}); });
@ -105,13 +105,13 @@ emulator-5554\tdevice
const packagePath = 'build/test/app.apk'; const packagePath = 'build/test/app.apk';
return Adb.install(deviceId, packagePath).then(() => { return Adb.install(deviceId, packagePath).then(() => {
const adbArgs = spawnSpy.calls.argsFor(0)[1]; const adbArgs = execaSpy.calls.argsFor(0)[1];
expect(adbArgs).toContain(packagePath); expect(adbArgs).toContain(packagePath);
}); });
}); });
it('should reject with a CordovaError if the adb output suggests a failure', () => { it('should reject with a CordovaError if the adb output suggests a failure', () => {
spawnSpy.and.returnValue(Promise.resolve(alreadyExistsError)); execaSpy.and.returnValue(Promise.resolve({ stdout: alreadyExistsError }));
return Adb.install(deviceId, '').then( return Adb.install(deviceId, '').then(
() => fail('Unexpectedly resolved'), () => fail('Unexpectedly resolved'),
@ -124,7 +124,7 @@ emulator-5554\tdevice
// The following two tests are somewhat brittle as they are dependent on the // The following two tests are somewhat brittle as they are dependent on the
// exact message returned. But it is better to have them tested than not at all. // exact message returned. But it is better to have them tested than not at all.
it('should give a more specific error message if there is a certificate failure', () => { it('should give a more specific error message if there is a certificate failure', () => {
spawnSpy.and.returnValue(Promise.resolve(certificateError)); execaSpy.and.returnValue(Promise.resolve({ stdout: certificateError }));
return Adb.install(deviceId, '').then( return Adb.install(deviceId, '').then(
() => fail('Unexpectedly resolved'), () => fail('Unexpectedly resolved'),
@ -136,7 +136,7 @@ emulator-5554\tdevice
}); });
it('should give a more specific error message if there is a downgrade error', () => { it('should give a more specific error message if there is a downgrade error', () => {
spawnSpy.and.returnValue(Promise.resolve(downgradeError)); execaSpy.and.returnValue(Promise.resolve({ stdout: downgradeError }));
return Adb.install(deviceId, '').then( return Adb.install(deviceId, '').then(
() => fail('Unexpectedly resolved'), () => fail('Unexpectedly resolved'),
@ -151,10 +151,10 @@ emulator-5554\tdevice
describe('uninstall', () => { describe('uninstall', () => {
it('should call adb uninstall with the correct arguments', () => { it('should call adb uninstall with the correct arguments', () => {
const packageId = 'io.cordova.test'; const packageId = 'io.cordova.test';
spawnSpy.and.returnValue(Promise.resolve('')); execaSpy.and.returnValue(Promise.resolve({ stdout: '' }));
return Adb.uninstall(deviceId, packageId).then(() => { return Adb.uninstall(deviceId, packageId).then(() => {
const args = spawnSpy.calls.argsFor(0); const args = execaSpy.calls.argsFor(0);
expect(args[0]).toBe('adb'); expect(args[0]).toBe('adb');
const adbArgs = args[1]; const adbArgs = args[1];
@ -169,10 +169,10 @@ emulator-5554\tdevice
const shellCommand = 'ls -l /sdcard'; const shellCommand = 'ls -l /sdcard';
it('should run the passed command on the target device', () => { it('should run the passed command on the target device', () => {
spawnSpy.and.returnValue(Promise.resolve('')); execaSpy.and.returnValue(Promise.resolve({ stdout: '' }));
return Adb.shell(deviceId, shellCommand).then(() => { return Adb.shell(deviceId, shellCommand).then(() => {
const args = spawnSpy.calls.argsFor(0); const args = execaSpy.calls.argsFor(0);
expect(args[0]).toBe('adb'); expect(args[0]).toBe('adb');
const adbArgs = args[1].join(' '); const adbArgs = args[1].join(' ');
@ -184,7 +184,7 @@ emulator-5554\tdevice
it('should reject with a CordovaError on failure', () => { it('should reject with a CordovaError on failure', () => {
const errorMessage = 'shell error'; const errorMessage = 'shell error';
spawnSpy.and.returnValue(Promise.reject(errorMessage)); execaSpy.and.rejectWith(new Error(errorMessage));
return Adb.shell(deviceId, shellCommand).then( return Adb.shell(deviceId, shellCommand).then(
() => fail('Unexpectedly resolved'), () => fail('Unexpectedly resolved'),
@ -214,7 +214,7 @@ emulator-5554\tdevice
it('should reject with a CordovaError on a shell error', () => { it('should reject with a CordovaError on a shell error', () => {
const errorMessage = 'Test Start error'; const errorMessage = 'Test Start error';
spyOn(Adb, 'shell').and.returnValue(Promise.reject(errorMessage)); spyOn(Adb, 'shell').and.rejectWith(new CordovaError(errorMessage));
return Adb.start(deviceId, activityName).then( return Adb.start(deviceId, activityName).then(
() => fail('Unexpectedly resolved'), () => fail('Unexpectedly resolved'),

View File

@ -17,16 +17,18 @@
under the License. under the License.
*/ */
const superspawn = require('cordova-common').superspawn;
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const rewire = require('rewire'); const rewire = require('rewire');
describe('android_sdk', () => { describe('android_sdk', () => {
let android_sdk; let android_sdk;
let execaSpy;
beforeEach(() => { beforeEach(() => {
android_sdk = rewire('../../bin/templates/cordova/lib/android_sdk'); android_sdk = rewire('../../bin/templates/cordova/lib/android_sdk');
execaSpy = jasmine.createSpy('execa');
android_sdk.__set__('execa', execaSpy);
}); });
describe('sort_by_largest_numerical_suffix', () => { describe('sort_by_largest_numerical_suffix', () => {
@ -59,14 +61,14 @@ describe('android_sdk', () => {
describe('list_targets_with_android', () => { describe('list_targets_with_android', () => {
it('should invoke `android` with the `list target` command and _not_ the `list targets` command, as the plural form is not supported in some Android SDK Tools versions', () => { it('should invoke `android` with the `list target` command and _not_ the `list targets` command, as the plural form is not supported in some Android SDK Tools versions', () => {
spyOn(superspawn, 'spawn').and.returnValue(new Promise(() => {}, () => {})); execaSpy.and.returnValue(Promise.resolve({ stdout: '' }));
android_sdk.list_targets_with_android(); android_sdk.list_targets_with_android();
expect(superspawn.spawn).toHaveBeenCalledWith('android', ['list', 'target']); expect(execaSpy).toHaveBeenCalledWith('android', ['list', 'target']);
}); });
it('should parse and return results from `android list targets` command', () => { it('should parse and return results from `android list targets` command', () => {
const testTargets = fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.2-android_list_targets.txt'), 'utf-8'); const testTargets = fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.2-android_list_targets.txt'), 'utf-8');
spyOn(superspawn, 'spawn').and.returnValue(Promise.resolve(testTargets)); execaSpy.and.returnValue(Promise.resolve({ stdout: testTargets }));
return android_sdk.list_targets_with_android().then(list => { return android_sdk.list_targets_with_android().then(list => {
[ 'Google Inc.:Google APIs:23', [ 'Google Inc.:Google APIs:23',
@ -87,7 +89,7 @@ describe('android_sdk', () => {
describe('list_targets_with_avdmanager', () => { describe('list_targets_with_avdmanager', () => {
it('should parse and return results from `avdmanager list target` command', () => { it('should parse and return results from `avdmanager list target` command', () => {
const testTargets = fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.3-avdmanager_list_target.txt'), 'utf-8'); const testTargets = fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.3-avdmanager_list_target.txt'), 'utf-8');
spyOn(superspawn, 'spawn').and.returnValue(Promise.resolve(testTargets)); execaSpy.and.returnValue(Promise.resolve({ stdout: testTargets }));
return android_sdk.list_targets_with_avdmanager().then(list => { return android_sdk.list_targets_with_avdmanager().then(list => {
expect(list).toContain('android-25'); expect(list).toContain('android-25');

View File

@ -19,7 +19,6 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const Q = require('q');
const rewire = require('rewire'); const rewire = require('rewire');
const CordovaError = require('cordova-common').CordovaError; const CordovaError = require('cordova-common').CordovaError;
@ -29,12 +28,12 @@ describe('ProjectBuilder', () => {
let builder; let builder;
let ProjectBuilder; let ProjectBuilder;
let spawnSpy; let execaSpy;
beforeEach(() => { beforeEach(() => {
spawnSpy = jasmine.createSpy('spawn').and.returnValue(Q.defer().promise); execaSpy = jasmine.createSpy('execa').and.returnValue(new Promise(() => {}));
ProjectBuilder = rewire('../../../bin/templates/cordova/lib/builders/ProjectBuilder'); ProjectBuilder = rewire('../../../bin/templates/cordova/lib/builders/ProjectBuilder');
ProjectBuilder.__set__('spawn', spawnSpy); ProjectBuilder.__set__('execa', execaSpy);
builder = new ProjectBuilder(rootDir); builder = new ProjectBuilder(rootDir);
}); });
@ -120,13 +119,13 @@ describe('ProjectBuilder', () => {
it('should run the provided gradle command if a gradle wrapper does not already exist', () => { it('should run the provided gradle command if a gradle wrapper does not already exist', () => {
spyOn(fs, 'existsSync').and.returnValue(false); spyOn(fs, 'existsSync').and.returnValue(false);
builder.runGradleWrapper('/my/sweet/gradle'); builder.runGradleWrapper('/my/sweet/gradle');
expect(spawnSpy).toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object)); expect(execaSpy).toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
}); });
it('should do nothing if a gradle wrapper exists in the project directory', () => { it('should do nothing if a gradle wrapper exists in the project directory', () => {
spyOn(fs, 'existsSync').and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(true);
builder.runGradleWrapper('/my/sweet/gradle'); builder.runGradleWrapper('/my/sweet/gradle');
expect(spawnSpy).not.toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object)); expect(execaSpy).not.toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
}); });
}); });
@ -182,34 +181,34 @@ describe('ProjectBuilder', () => {
builder.build({}); builder.build({});
expect(spawnSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), testArgs, jasmine.anything()); expect(execaSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), testArgs, jasmine.anything());
}); });
it('should reject if the spawn fails', () => { it('should reject if the spawn fails', () => {
const errorMessage = 'ERROR: Failed to spawn'; const errorMessage = 'Test error';
spawnSpy.and.returnValue(Q.reject(errorMessage)); execaSpy.and.rejectWith(new Error(errorMessage));
return builder.build({}).then( return builder.build({}).then(
() => fail('Unexpectedly resolved'), () => fail('Unexpectedly resolved'),
err => { error => {
expect(err).toBe(errorMessage); expect(error.message).toBe(errorMessage);
} }
); );
}); });
it('should check the Android target if failed to find target', () => { it('should check the Android target if failed to find target', () => {
const checkReqsSpy = jasmine.createSpyObj('check_reqs', ['check_android_target']); const checkReqsSpy = jasmine.createSpyObj('check_reqs', ['check_android_target']);
const errorMessage = 'ERROR: failed to find target with hash string'; const testError = 'failed to find target with hash string';
ProjectBuilder.__set__('check_reqs', checkReqsSpy); ProjectBuilder.__set__('check_reqs', checkReqsSpy);
checkReqsSpy.check_android_target.and.returnValue(Q.resolve()); checkReqsSpy.check_android_target.and.returnValue(Promise.resolve());
spawnSpy.and.returnValue(Q.reject(errorMessage)); execaSpy.and.rejectWith(testError);
return builder.build({}).then( return builder.build({}).then(
() => fail('Unexpectedly resolved'), () => fail('Unexpectedly resolved'),
err => { error => {
expect(checkReqsSpy.check_android_target).toHaveBeenCalledWith(errorMessage); expect(checkReqsSpy.check_android_target).toHaveBeenCalledWith(testError);
expect(err).toBe(errorMessage); expect(error).toBe(testError);
} }
); );
}); });
@ -222,7 +221,7 @@ describe('ProjectBuilder', () => {
shellSpy = jasmine.createSpyObj('shell', ['rm']); shellSpy = jasmine.createSpyObj('shell', ['rm']);
ProjectBuilder.__set__('shell', shellSpy); ProjectBuilder.__set__('shell', shellSpy);
spyOn(builder, 'getArgs'); spyOn(builder, 'getArgs');
spawnSpy.and.returnValue(Promise.resolve()); execaSpy.and.returnValue(Promise.resolve());
}); });
it('should get arguments for cleaning', () => { it('should get arguments for cleaning', () => {
@ -238,7 +237,7 @@ describe('ProjectBuilder', () => {
builder.getArgs.and.returnValue(gradleArgs); builder.getArgs.and.returnValue(gradleArgs);
return builder.clean(opts).then(() => { return builder.clean(opts).then(() => {
expect(spawnSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), gradleArgs, jasmine.anything()); expect(execaSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), gradleArgs, jasmine.anything());
}); });
}); });

View File

@ -42,12 +42,12 @@ describe('device', () => {
}); });
it('should kill adb and try to get devices again if none are found the first time, and `lookHarder` is set', () => { it('should kill adb and try to get devices again if none are found the first time, and `lookHarder` is set', () => {
const spawnSpy = jasmine.createSpy('spawn').and.returnValue(Promise.resolve()); const execaSpy = jasmine.createSpy('execa').and.returnValue(Promise.resolve());
device.__set__('spawn', spawnSpy); device.__set__('execa', execaSpy);
AdbSpy.devices.and.returnValues(Promise.resolve([]), Promise.resolve(DEVICE_LIST)); AdbSpy.devices.and.returnValues(Promise.resolve([]), Promise.resolve(DEVICE_LIST));
return device.list(true).then(list => { return device.list(true).then(list => {
expect(spawnSpy).toHaveBeenCalledWith('killall', ['adb']); expect(execaSpy).toHaveBeenCalledWith('killall', ['adb']);
expect(list).toBe(DEVICE_LIST); expect(list).toBe(DEVICE_LIST);
expect(AdbSpy.devices).toHaveBeenCalledTimes(2); expect(AdbSpy.devices).toHaveBeenCalledTimes(2);
}); });
@ -55,12 +55,12 @@ describe('device', () => {
it('should return the empty list if killing adb fails', () => { it('should return the empty list if killing adb fails', () => {
const emptyDevices = []; const emptyDevices = [];
const spawnSpy = jasmine.createSpy('spawn').and.returnValue(Promise.reject()); const execaSpy = jasmine.createSpy('execa').and.returnValue(Promise.reject());
device.__set__('spawn', spawnSpy); device.__set__('execa', execaSpy);
AdbSpy.devices.and.returnValues(Promise.resolve(emptyDevices)); AdbSpy.devices.and.returnValues(Promise.resolve(emptyDevices));
return device.list(true).then(list => { return device.list(true).then(list => {
expect(spawnSpy).toHaveBeenCalledWith('killall', ['adb']); expect(execaSpy).toHaveBeenCalledWith('killall', ['adb']);
expect(list).toBe(emptyDevices); expect(list).toBe(emptyDevices);
expect(AdbSpy.devices).toHaveBeenCalledTimes(1); expect(AdbSpy.devices).toHaveBeenCalledTimes(1);
}); });

View File

@ -24,7 +24,6 @@ const shelljs = require('shelljs');
const CordovaError = require('cordova-common').CordovaError; const CordovaError = require('cordova-common').CordovaError;
const check_reqs = require('../../bin/templates/cordova/lib/check_reqs'); const check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
const superspawn = require('cordova-common').superspawn;
describe('emulator', () => { describe('emulator', () => {
const EMULATOR_LIST = ['emulator-5555', 'emulator-5556', 'emulator-5557']; const EMULATOR_LIST = ['emulator-5555', 'emulator-5556', 'emulator-5557'];
@ -37,7 +36,9 @@ describe('emulator', () => {
describe('list_images_using_avdmanager', () => { describe('list_images_using_avdmanager', () => {
it('should properly parse details of SDK Tools 25.3.1 `avdmanager` output', () => { it('should properly parse details of SDK Tools 25.3.1 `avdmanager` output', () => {
const avdList = fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.3-avdmanager_list_avd.txt'), 'utf-8'); const avdList = fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.3-avdmanager_list_avd.txt'), 'utf-8');
spyOn(superspawn, 'spawn').and.returnValue(Promise.resolve(avdList));
let execaSpy = jasmine.createSpy('execa').and.returnValue(Promise.resolve({ stdout: avdList }));
emu.__set__('execa', execaSpy);
return emu.list_images_using_avdmanager().then(list => { return emu.list_images_using_avdmanager().then(list => {
expect(list).toBeDefined(); expect(list).toBeDefined();
@ -51,14 +52,18 @@ describe('emulator', () => {
describe('list_images_using_android', () => { describe('list_images_using_android', () => {
it('should invoke `android` with the `list avd` command and _not_ the `list avds` command, as the plural form is not supported in some Android SDK Tools versions', () => { it('should invoke `android` with the `list avd` command and _not_ the `list avds` command, as the plural form is not supported in some Android SDK Tools versions', () => {
spyOn(superspawn, 'spawn').and.returnValue(new Promise(() => {}, () => {})); let execaSpy = jasmine.createSpy('execa').and.returnValue(Promise.resolve({ stdout: '' }));
emu.__set__('execa', execaSpy);
emu.list_images_using_android(); emu.list_images_using_android();
expect(superspawn.spawn).toHaveBeenCalledWith('android', ['list', 'avd']); expect(execaSpy).toHaveBeenCalledWith('android', ['list', 'avd']);
}); });
it('should properly parse details of SDK Tools pre-25.3.1 `android list avd` output', () => { it('should properly parse details of SDK Tools pre-25.3.1 `android list avd` output', () => {
const avdList = fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.2-android_list_avd.txt'), 'utf-8'); const avdList = fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.2-android_list_avd.txt'), 'utf-8');
spyOn(superspawn, 'spawn').and.returnValue(Promise.resolve(avdList));
let execaSpy = jasmine.createSpy('execa').and.returnValue(Promise.resolve({ stdout: avdList }));
emu.__set__('execa', execaSpy);
return emu.list_images_using_android().then(list => { return emu.list_images_using_android().then(list => {
expect(list).toBeDefined(); expect(list).toBeDefined();
@ -249,7 +254,7 @@ describe('emulator', () => {
let emulator; let emulator;
let AdbSpy; let AdbSpy;
let checkReqsSpy; let checkReqsSpy;
let childProcessSpy; let execaSpy;
let shellJsSpy; let shellJsSpy;
beforeEach(() => { beforeEach(() => {
@ -268,9 +273,10 @@ describe('emulator', () => {
checkReqsSpy = jasmine.createSpyObj('create_reqs', ['getAbsoluteAndroidCmd']); checkReqsSpy = jasmine.createSpyObj('create_reqs', ['getAbsoluteAndroidCmd']);
emu.__set__('check_reqs', checkReqsSpy); emu.__set__('check_reqs', checkReqsSpy);
childProcessSpy = jasmine.createSpyObj('child_process', ['spawn']); execaSpy = jasmine.createSpy('execa').and.returnValue(
childProcessSpy.spawn.and.returnValue(jasmine.createSpyObj('spawnFns', ['unref'])); jasmine.createSpyObj('spawnFns', ['unref'])
emu.__set__('child_process', childProcessSpy); );
emu.__set__('execa', execaSpy);
spyOn(emu, 'get_available_port').and.returnValue(Promise.resolve(port)); spyOn(emu, 'get_available_port').and.returnValue(Promise.resolve(port));
spyOn(emu, 'wait_for_emulator').and.returnValue(Promise.resolve('randomname')); spyOn(emu, 'wait_for_emulator').and.returnValue(Promise.resolve('randomname'));
@ -291,7 +297,7 @@ describe('emulator', () => {
return emu.start().then(() => { return emu.start().then(() => {
// This is the earliest part in the code where we can hook in and check // This is the earliest part in the code where we can hook in and check
// the emulator that has been selected. // 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); expect(spawnArgs[1]).toContain(emulator.name);
}); });
}); });
@ -302,7 +308,7 @@ describe('emulator', () => {
return emu.start(emulator.name).then(() => { return emu.start(emulator.name).then(() => {
expect(emu.best_image).not.toHaveBeenCalled(); 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); expect(spawnArgs[1]).toContain(emulator.name);
}); });
}); });
@ -576,7 +582,7 @@ describe('emulator', () => {
let AndroidManifestGetActivitySpy; let AndroidManifestGetActivitySpy;
let AdbSpy; let AdbSpy;
let buildSpy; let buildSpy;
let childProcessSpy; let execaSpy;
let target; let target;
beforeEach(() => { beforeEach(() => {
@ -597,9 +603,8 @@ describe('emulator', () => {
AdbSpy.uninstall.and.returnValue(Promise.resolve()); AdbSpy.uninstall.and.returnValue(Promise.resolve());
emu.__set__('Adb', AdbSpy); emu.__set__('Adb', AdbSpy);
childProcessSpy = jasmine.createSpyObj('child_process', ['exec']); execaSpy = jasmine.createSpy('execa').and.resolveTo({});
childProcessSpy.exec.and.callFake((cmd, opts, callback) => callback()); emu.__set__('execa', execaSpy);
emu.__set__('child_process', childProcessSpy);
}); });
it('should get the full target object if only id is specified', () => { it('should get the full target object if only id is specified', () => {
@ -613,7 +618,7 @@ describe('emulator', () => {
it('should install to the passed target', () => { it('should install to the passed target', () => {
return emu.install(target, {}).then(() => { 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`); expect(execCmd).toContain(`-s ${target.target} install`);
}); });
}); });
@ -631,33 +636,26 @@ describe('emulator', () => {
return emu.install(target, buildResults).then(() => { return emu.install(target, buildResults).then(() => {
expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, target.arch); expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, target.arch);
const execCmd = childProcessSpy.exec.calls.argsFor(0)[0]; const execCmd = execaSpy.calls.argsFor(0)[1].join(' ');
expect(execCmd).toMatch(new RegExp(`install.*${apkPath}`)); expect(execCmd).toContain(`install -r ${apkPath}`);
}); });
}); });
it('should uninstall and reinstall app if failure is due to different certificates', () => { it('should uninstall and reinstall app if failure is due to different certificates', () => {
let execAlreadyCalled; execaSpy.and.returnValues(
childProcessSpy.exec.and.callFake((cmd, opts, callback) => { ...['Failure: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES', '']
if (!execAlreadyCalled) { .map(out => Promise.resolve({ stdout: out }))
execAlreadyCalled = true; );
callback(null, 'Failure: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES');
} else {
callback();
}
});
return emu.install(target, {}).then(() => { return emu.install(target, {}).then(() => {
expect(childProcessSpy.exec).toHaveBeenCalledTimes(2); expect(execaSpy).toHaveBeenCalledTimes(2);
expect(AdbSpy.uninstall).toHaveBeenCalled(); expect(AdbSpy.uninstall).toHaveBeenCalled();
}); });
}); });
it('should throw any error not caused by different certificates', () => { it('should throw any error not caused by different certificates', () => {
const errorMsg = 'Failure: Failed to install'; const errorMsg = 'Failure: Failed to install';
childProcessSpy.exec.and.callFake((cmd, opts, callback) => { execaSpy.and.resolveTo({ stdout: errorMsg });
callback(null, errorMsg);
});
return emu.install(target, {}).then( return emu.install(target, {}).then(
() => fail('Unexpectedly resolved'), () => fail('Unexpectedly resolved'),

View File

@ -21,7 +21,7 @@
var Q = require('q'); var Q = require('q');
var path = require('path'); var path = require('path');
var superspawn = require('cordova-common').superspawn; var execa = require('execa');
var ProjectBuilder = require('../bin/templates/cordova/lib/builders/ProjectBuilder'); var ProjectBuilder = require('../bin/templates/cordova/lib/builders/ProjectBuilder');
Q.resolve() Q.resolve()
@ -42,7 +42,7 @@ process.on('unhandledRejection', err => {
function gradlew () { function gradlew () {
const wrapperPath = path.join(__dirname, 'gradlew'); const wrapperPath = path.join(__dirname, 'gradlew');
return superspawn.spawn(wrapperPath, Array.from(arguments), { return execa(wrapperPath, Array.from(arguments), {
stdio: 'inherit', stdio: 'inherit',
cwd: __dirname cwd: __dirname
}); });