coffee
Version:
Test command line on nodejs
226 lines (197 loc) • 5.5 kB
JavaScript
'use strict';
const EventEmitter = require('events');
const cp = require('child_process');
const assert = require('assert');
const debug = require('debug')('coffee');
const assertion = require('./assert');
const show = require('./show');
const Rule = require('./rule');
// Only accept these type below for assertion
const acceptType = [ 'stdout', 'stderr', 'code', 'error' ];
class Coffee extends EventEmitter {
constructor(opt) {
opt || (opt = {});
assert(opt.method && opt.cmd, 'should specify method and cmd');
super();
this.method = opt.method;
this.cmd = opt.cmd;
this.args = opt.args;
this.opt = opt.opt;
this.restore();
this.on('stdout_data', buf => {
debug('output stdout `%s`', show(buf));
this._debug_stdout && process.stdout.write(buf);
this.stdout += buf;
});
this.on('stderr_data', buf => {
debug('output stderr `%s`', show(buf));
this._debug_stderr && process.stderr.write(buf);
this.stderr += buf;
});
this.on('error', err => {
this.error = err;
});
this.once('close', code => {
debug('output code `%s`', show(code));
this.code = code;
try {
assertion(this.waitAssert.stdout, this.stdout, 'match stdout');
assertion(this.waitAssert.stderr, this.stderr, 'match stderr');
assertion(this.waitAssert.code, this.code, 'match code');
this.error && assertion(this.waitAssert.error, this.error.message, 'match error message');
} catch (err) {
return done(err);
}
done();
});
const self = this;
function done(err) {
self.complete = true;
if (self.cb) {
self.cb.call(self, err, {
stdout: self.stdout,
stderr: self.stderr,
code: self.code,
error: self.error,
});
} else {
if (err) {
self.emit('complete_error', err);
} else {
self.emit('complete_success', {
stdout: self.stdout,
stderr: self.stderr,
});
}
}
}
if (process.env.COFFEE_DEBUG) {
this.debug(process.env.COFFEE_DEBUG);
}
process.nextTick(this._run.bind(this));
}
coverage() {
// it has not been impelmented
// if (enable === false) {
// process.env.NYC_NO_INSTRUMENT = true;
// }
return this;
}
debug(level) {
this._debug_stderr = false;
// 0 (default) -> stdout + stderr
// 1 -> stdout
// 2 -> stderr
switch (String(level)) {
case '1':
this._debug_stdout = true;
break;
case '2':
this._debug_stderr = true;
break;
case 'false':
this._debug_stdout = false;
this._debug_stderr = false;
break;
default:
this._debug_stdout = true;
this._debug_stderr = true;
}
return this;
}
expect(type, value) {
if (acceptType.indexOf(type) > -1) {
const rule = new Rule(value);
if (this.complete) {
assertion([ rule ], this[type], 'match ' + type);
} else {
this.waitAssert[type].push(rule);
}
}
return this;
}
notExpect(type, value) {
if (acceptType.indexOf(type) > -1) {
const rule = new Rule(value, true);
if (this.complete) {
assertion([ rule ], this[type], 'match ' + type);
} else {
this.waitAssert[type].push(rule);
}
}
return this;
}
/*
Write data to stdin of the command
*/
write(value) {
assert(!this._isEndCalled, 'can\'t call write after end');
this.stdin.push(value);
return this;
}
end(cb) {
this.cb = cb;
if (!cb) {
return new Promise((resolve, reject) => {
this.on('complete_success', resolve);
this.on('complete_error', reject);
});
}
}
_run() {
this._isEndCalled = true;
const cmd = this.proc = run(this.method, this.cmd, this.args, this.opt);
cmd.stdout && cmd.stdout.on('data', this.emit.bind(this, 'stdout_data'));
cmd.stderr && cmd.stderr.on('data', this.emit.bind(this, 'stderr_data'));
cmd.once('error', this.emit.bind(this, 'error'));
cmd.once('close', this.emit.bind(this, 'close'));
if (this.stdin.length) {
this.stdin.forEach(function(buf) {
debug('input stdin `%s`', show(buf));
cmd.stdin.write(buf);
});
cmd.stdin.end();
}
return this;
}
restore() {
// cache input for command
this.stdin = [];
// cache output for command
this.stdout = '';
this.stderr = '';
this.code = null;
this.error = null;
// cache expected output
this.waitAssert = {
stderr: [],
stdout: [],
code: [],
error: [],
};
this.complete = false;
this._isEndCalled = false;
this._debug_stdout = false;
this._debug_stderr = false;
this._isCoverage = true;
return this;
}
}
module.exports = Coffee;
function run(method, cmd, args, opt) {
if (!opt && args && typeof args === 'object' && !Array.isArray(args)) {
// run(method, cmd, opt)
opt = args;
args = null;
}
args = args || [];
opt = opt || {};
// Force pipe to parent
if (method === 'fork') {
// Boolean If true, stdin, stdout, and stderr of the child will be piped to the parent,
// otherwise they will be inherited from the parent
opt.silent = true;
}
debug('child_process.%s("%s", [%s], %j)', method, cmd, args, opt);
return cp[method](cmd, args, opt);
}