var defined = require('defined'); var EventEmitter = require('events').EventEmitter; var inherits = require('inherits'); var through = require('through'); var resumer = require('resumer'); var inspect = require('object-inspect'); var bind = require('function-bind'); var has = require('has'); var regexpTest = bind.call(Function.call, RegExp.prototype.test); var yamlIndicators = /\:|\-|\?/; var nextTick = typeof setImmediate !== 'undefined' ? setImmediate : process.nextTick ; module.exports = Results; inherits(Results, EventEmitter); function coalesceWhiteSpaces(str) { return String(str).replace(/\s+/g, ' '); } function Results() { if (!(this instanceof Results)) return new Results; this.count = 0; this.fail = 0; this.pass = 0; this.todo = 0; this._stream = through(); this.tests = []; this._only = null; this._isRunning = false; } Results.prototype.createStream = function (opts) { if (!opts) opts = {}; var self = this; var output, testId = 0; if (opts.objectMode) { output = through(); self.on('_push', function ontest(t, extra) { if (!extra) extra = {}; var id = testId++; t.once('prerun', function () { var row = { type: 'test', name: t.name, id: id, skip: t._skip, todo: t._todo }; if (has(extra, 'parent')) { row.parent = extra.parent; } output.queue(row); }); t.on('test', function (st) { ontest(st, { parent: id }); }); t.on('result', function (res) { res.test = id; res.type = 'assert'; output.queue(res); }); t.on('end', function () { output.queue({ type: 'end', test: id }); }); }); self.on('done', function () { output.queue(null); }); } else { output = resumer(); output.queue('TAP version 13\n'); self._stream.pipe(output); } if (!this._isRunning) { this._isRunning = true; nextTick(function next() { var t; while (t = getNextTest(self)) { t.run(); if (!t.ended) return t.once('end', function () { nextTick(next); }); } self.emit('done'); }); } return output; }; Results.prototype.push = function (t) { var self = this; self.tests.push(t); self._watch(t); self.emit('_push', t); }; Results.prototype.only = function (t) { this._only = t; }; Results.prototype._watch = function (t) { var self = this; var write = function (s) { self._stream.queue(s); }; t.once('prerun', function () { var premsg = ''; if (t._skip) premsg = 'SKIP '; else if (t._todo) premsg = 'TODO '; write('# ' + premsg + coalesceWhiteSpaces(t.name) + '\n'); }); t.on('result', function (res) { if (typeof res === 'string') { write('# ' + res + '\n'); return; } write(encodeResult(res, self.count + 1)); self.count ++; if (res.ok || res.todo) self.pass ++; else { self.fail ++; self.emit('fail'); } }); t.on('test', function (st) { self._watch(st); }); }; Results.prototype.close = function () { var self = this; if (self.closed) self._stream.emit('error', new Error('ALREADY CLOSED')); self.closed = true; var write = function (s) { self._stream.queue(s); }; write('\n1..' + self.count + '\n'); write('# tests ' + self.count + '\n'); write('# pass ' + (self.pass + self.todo) + '\n'); if (self.todo) write('# todo ' + self.todo + '\n'); if (self.fail) write('# fail ' + self.fail + '\n'); else write('\n# ok\n'); self._stream.queue(null); }; function encodeResult(res, count) { var output = ''; output += (res.ok ? 'ok ' : 'not ok ') + count; output += res.name ? ' ' + coalesceWhiteSpaces(res.name) : ''; if (res.skip) { output += ' # SKIP' + ((typeof res.skip === 'string') ? ' ' + coalesceWhiteSpaces(res.skip) : ''); } else if (res.todo) { output += ' # TODO' + ((typeof res.todo === 'string') ? ' ' + coalesceWhiteSpaces(res.todo) : ''); }; output += '\n'; if (res.ok) return output; var outer = ' '; var inner = outer + ' '; output += outer + '---\n'; output += inner + 'operator: ' + res.operator + '\n'; if (has(res, 'expected') || has(res, 'actual')) { var ex = inspect(res.expected, {depth: res.objectPrintDepth}); var ac = inspect(res.actual, {depth: res.objectPrintDepth}); if (Math.max(ex.length, ac.length) > 65 || invalidYaml(ex) || invalidYaml(ac)) { output += inner + 'expected: |-\n' + inner + ' ' + ex + '\n'; output += inner + 'actual: |-\n' + inner + ' ' + ac + '\n'; } else { output += inner + 'expected: ' + ex + '\n'; output += inner + 'actual: ' + ac + '\n'; } } if (res.at) { output += inner + 'at: ' + res.at + '\n'; } var actualStack = res.actual && (typeof res.actual === 'object' || typeof res.actual === 'function') ? res.actual.stack : undefined; var errorStack = res.error && res.error.stack; var stack = defined(actualStack, errorStack); if (stack) { var lines = String(stack).split('\n'); output += inner + 'stack: |-\n'; for (var i = 0; i < lines.length; i++) { output += inner + ' ' + lines[i] + '\n'; } } output += outer + '...\n'; return output; } function getNextTest(results) { if (!results._only) { return results.tests.shift(); } do { var t = results.tests.shift(); if (!t) continue; if (results._only === t) { return t; } } while (results.tests.length !== 0); } function invalidYaml(str) { return regexpTest(yamlIndicators, str); }