1 | var EventEmitter = require('events').EventEmitter;
|
2 | var inherits = require('inherits');
|
3 | var through = require('through');
|
4 | var resumer = require('resumer');
|
5 | var inspect = require('object-inspect');
|
6 | var bind = require('function-bind');
|
7 | var has = require('has');
|
8 | var regexpTest = bind.call(Function.call, RegExp.prototype.test);
|
9 | var yamlIndicators = /\:|\-|\?/;
|
10 | var nextTick = typeof setImmediate !== 'undefined'
|
11 | ? setImmediate
|
12 | : process.nextTick
|
13 | ;
|
14 |
|
15 | module.exports = Results;
|
16 | inherits(Results, EventEmitter);
|
17 |
|
18 | function Results () {
|
19 | if (!(this instanceof Results)) return new Results;
|
20 | this.count = 0;
|
21 | this.fail = 0;
|
22 | this.pass = 0;
|
23 | this._stream = through();
|
24 | this.tests = [];
|
25 | this._only = null;
|
26 | }
|
27 |
|
28 | Results.prototype.createStream = function (opts) {
|
29 | if (!opts) opts = {};
|
30 | var self = this;
|
31 | var output, testId = 0;
|
32 | if (opts.objectMode) {
|
33 | output = through();
|
34 | self.on('_push', function ontest (t, extra) {
|
35 | if (!extra) extra = {};
|
36 | var id = testId++;
|
37 | t.once('prerun', function () {
|
38 | var row = {
|
39 | type: 'test',
|
40 | name: t.name,
|
41 | id: id
|
42 | };
|
43 | if (has(extra, 'parent')) {
|
44 | row.parent = extra.parent;
|
45 | }
|
46 | output.queue(row);
|
47 | });
|
48 | t.on('test', function (st) {
|
49 | ontest(st, { parent: id });
|
50 | });
|
51 | t.on('result', function (res) {
|
52 | res.test = id;
|
53 | res.type = 'assert';
|
54 | output.queue(res);
|
55 | });
|
56 | t.on('end', function () {
|
57 | output.queue({ type: 'end', test: id });
|
58 | });
|
59 | });
|
60 | self.on('done', function () { output.queue(null) });
|
61 | }
|
62 | else {
|
63 | output = resumer();
|
64 | output.queue('TAP version 13\n');
|
65 | self._stream.pipe(output);
|
66 | }
|
67 |
|
68 | nextTick(function next() {
|
69 | var t;
|
70 | while (t = getNextTest(self)) {
|
71 | t.run();
|
72 | if (!t.ended) return t.once('end', function(){ nextTick(next); });
|
73 | }
|
74 | self.emit('done');
|
75 | });
|
76 |
|
77 | return output;
|
78 | };
|
79 |
|
80 | Results.prototype.push = function (t) {
|
81 | var self = this;
|
82 | self.tests.push(t);
|
83 | self._watch(t);
|
84 | self.emit('_push', t);
|
85 | };
|
86 |
|
87 | Results.prototype.only = function (t) {
|
88 | this._only = t;
|
89 | };
|
90 |
|
91 | Results.prototype._watch = function (t) {
|
92 | var self = this;
|
93 | var write = function (s) { self._stream.queue(s) };
|
94 | t.once('prerun', function () {
|
95 | write('# ' + t.name + '\n');
|
96 | });
|
97 |
|
98 | t.on('result', function (res) {
|
99 | if (typeof res === 'string') {
|
100 | write('# ' + res + '\n');
|
101 | return;
|
102 | }
|
103 | write(encodeResult(res, self.count + 1));
|
104 | self.count ++;
|
105 |
|
106 | if (res.ok) self.pass ++
|
107 | else self.fail ++
|
108 | });
|
109 |
|
110 | t.on('test', function (st) { self._watch(st) });
|
111 | };
|
112 |
|
113 | Results.prototype.close = function () {
|
114 | var self = this;
|
115 | if (self.closed) self._stream.emit('error', new Error('ALREADY CLOSED'));
|
116 | self.closed = true;
|
117 | var write = function (s) { self._stream.queue(s) };
|
118 |
|
119 | write('\n1..' + self.count + '\n');
|
120 | write('# tests ' + self.count + '\n');
|
121 | write('# pass ' + self.pass + '\n');
|
122 | if (self.fail) write('# fail ' + self.fail + '\n')
|
123 | else write('\n# ok\n')
|
124 |
|
125 | self._stream.queue(null);
|
126 | };
|
127 |
|
128 | function encodeResult (res, count) {
|
129 | var output = '';
|
130 | output += (res.ok ? 'ok ' : 'not ok ') + count;
|
131 | output += res.name ? ' ' + res.name.toString().replace(/\s+/g, ' ') : '';
|
132 |
|
133 | if (res.skip) output += ' # SKIP';
|
134 | else if (res.todo) output += ' # TODO';
|
135 |
|
136 | output += '\n';
|
137 | if (res.ok) return output;
|
138 |
|
139 | var outer = ' ';
|
140 | var inner = outer + ' ';
|
141 | output += outer + '---\n';
|
142 | output += inner + 'operator: ' + res.operator + '\n';
|
143 |
|
144 | if (has(res, 'expected') || has(res, 'actual')) {
|
145 | var ex = inspect(res.expected, {depth: res.objectPrintDepth});
|
146 | var ac = inspect(res.actual, {depth: res.objectPrintDepth});
|
147 |
|
148 | if (Math.max(ex.length, ac.length) > 65 || invalidYaml(ex) || invalidYaml(ac)) {
|
149 | output += inner + 'expected: |-\n' + inner + ' ' + ex + '\n';
|
150 | output += inner + 'actual: |-\n' + inner + ' ' + ac + '\n';
|
151 | }
|
152 | else {
|
153 | output += inner + 'expected: ' + ex + '\n';
|
154 | output += inner + 'actual: ' + ac + '\n';
|
155 | }
|
156 | }
|
157 | if (res.at) {
|
158 | output += inner + 'at: ' + res.at + '\n';
|
159 | }
|
160 | if (res.operator === 'error' && res.actual && res.actual.stack) {
|
161 | var lines = String(res.actual.stack).split('\n');
|
162 | output += inner + 'stack: |-\n';
|
163 | for (var i = 0; i < lines.length; i++) {
|
164 | output += inner + ' ' + lines[i] + '\n';
|
165 | }
|
166 | }
|
167 |
|
168 | output += outer + '...\n';
|
169 | return output;
|
170 | }
|
171 |
|
172 | function getNextTest (results) {
|
173 | if (!results._only) {
|
174 | return results.tests.shift();
|
175 | }
|
176 |
|
177 | do {
|
178 | var t = results.tests.shift();
|
179 | if (!t) continue;
|
180 | if (results._only === t) {
|
181 | return t;
|
182 | }
|
183 | } while (results.tests.length !== 0)
|
184 | }
|
185 |
|
186 | function invalidYaml (str) {
|
187 | return regexpTest(yamlIndicators, str);
|
188 | }
|