UNPKG

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