1 | 'use strict';
|
2 |
|
3 | var defined = require('defined');
|
4 | var EventEmitter = require('events').EventEmitter;
|
5 | var inherits = require('inherits');
|
6 | var through = require('through');
|
7 | var resumer = require('resumer');
|
8 | var inspect = require('object-inspect');
|
9 | var callBound = require('call-bind/callBound');
|
10 | var has = require('has');
|
11 | var $exec = callBound('RegExp.prototype.exec');
|
12 | var $split = callBound('String.prototype.split');
|
13 | var $replace = callBound('String.prototype.replace');
|
14 | var $shift = callBound('Array.prototype.shift');
|
15 | var $push = callBound('Array.prototype.push');
|
16 | var yamlIndicators = /:|-|\?/;
|
17 | var nextTick = typeof setImmediate !== 'undefined'
|
18 | ? setImmediate
|
19 | : process.nextTick;
|
20 |
|
21 | function coalesceWhiteSpaces(str) {
|
22 | return $replace(String(str), /\s+/g, ' ');
|
23 | }
|
24 |
|
25 | function 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 |
|
40 | function invalidYaml(str) {
|
41 | return $exec(yamlIndicators, str) !== null;
|
42 | }
|
43 |
|
44 | function 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 |
|
94 | function 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 |
|
106 | inherits(Results, EventEmitter);
|
107 |
|
108 | Results.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 |
|
170 | Results.prototype.push = function (t) {
|
171 | var self = this;
|
172 | $push(self.tests, t);
|
173 | self._watch(t);
|
174 | self.emit('_push', t);
|
175 | };
|
176 |
|
177 | Results.prototype.only = function (t) {
|
178 | this._only = t;
|
179 | };
|
180 |
|
181 | Results.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 |
|
214 | Results.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 |
|
234 | module.exports = Results;
|