| 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 Results () { |
| if (!(this instanceof Results)) return new Results; |
| this.count = 0; |
| this.fail = 0; |
| this.pass = 0; |
| this._stream = through(); |
| this.tests = []; |
| this._only = null; |
| } |
| |
| 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 |
| }; |
| 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); |
| } |
| |
| 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 () { |
| write('# ' + 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) 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 + '\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 ? ' ' + res.name.toString().replace(/\s+/g, ' ') : ''; |
| |
| if (res.skip) output += ' # SKIP'; |
| else if (res.todo) output += ' # 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); |
| } |