UNPKG

7.03 kBJavaScriptView Raw
1'use strict';
2/**
3 * @module TAP
4 */
5/**
6 * Module dependencies.
7 */
8
9var util = require('util');
10var Base = require('./base');
11var constants = require('../runner').constants;
12var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
13var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL;
14var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN;
15var EVENT_RUN_END = constants.EVENT_RUN_END;
16var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING;
17var EVENT_TEST_END = constants.EVENT_TEST_END;
18var inherits = require('../utils').inherits;
19var sprintf = util.format;
20
21/**
22 * Expose `TAP`.
23 */
24
25exports = module.exports = TAP;
26
27/**
28 * Constructs a new `TAP` reporter instance.
29 *
30 * @public
31 * @class
32 * @memberof Mocha.reporters
33 * @extends Mocha.reporters.Base
34 * @param {Runner} runner - Instance triggers reporter actions.
35 * @param {Object} [options] - runner options
36 */
37function TAP(runner, options) {
38 Base.call(this, runner, options);
39
40 var self = this;
41 var n = 1;
42
43 var tapVersion = '12';
44 if (options && options.reporterOptions) {
45 if (options.reporterOptions.tapVersion) {
46 tapVersion = options.reporterOptions.tapVersion.toString();
47 }
48 }
49
50 this._producer = createProducer(tapVersion);
51
52 runner.once(EVENT_RUN_BEGIN, function() {
53 self._producer.writeVersion();
54 });
55
56 runner.on(EVENT_TEST_END, function() {
57 ++n;
58 });
59
60 runner.on(EVENT_TEST_PENDING, function(test) {
61 self._producer.writePending(n, test);
62 });
63
64 runner.on(EVENT_TEST_PASS, function(test) {
65 self._producer.writePass(n, test);
66 });
67
68 runner.on(EVENT_TEST_FAIL, function(test, err) {
69 self._producer.writeFail(n, test, err);
70 });
71
72 runner.once(EVENT_RUN_END, function() {
73 self._producer.writeEpilogue(runner.stats);
74 });
75}
76
77/**
78 * Inherit from `Base.prototype`.
79 */
80inherits(TAP, Base);
81
82/**
83 * Returns a TAP-safe title of `test`.
84 *
85 * @private
86 * @param {Test} test - Test instance.
87 * @return {String} title with any hash character removed
88 */
89function title(test) {
90 return test.fullTitle().replace(/#/g, '');
91}
92
93/**
94 * Writes newline-terminated formatted string to reporter output stream.
95 *
96 * @private
97 * @param {string} format - `printf`-like format string
98 * @param {...*} [varArgs] - Format string arguments
99 */
100function println(format, varArgs) {
101 var vargs = Array.from(arguments);
102 vargs[0] += '\n';
103 process.stdout.write(sprintf.apply(null, vargs));
104}
105
106/**
107 * Returns a `tapVersion`-appropriate TAP producer instance, if possible.
108 *
109 * @private
110 * @param {string} tapVersion - Version of TAP specification to produce.
111 * @returns {TAPProducer} specification-appropriate instance
112 * @throws {Error} if specification version has no associated producer.
113 */
114function createProducer(tapVersion) {
115 var producers = {
116 '12': new TAP12Producer(),
117 '13': new TAP13Producer()
118 };
119 var producer = producers[tapVersion];
120
121 if (!producer) {
122 throw new Error(
123 'invalid or unsupported TAP version: ' + JSON.stringify(tapVersion)
124 );
125 }
126
127 return producer;
128}
129
130/**
131 * @summary
132 * Constructs a new TAPProducer.
133 *
134 * @description
135 * <em>Only</em> to be used as an abstract base class.
136 *
137 * @private
138 * @constructor
139 */
140function TAPProducer() {}
141
142/**
143 * Writes the TAP version to reporter output stream.
144 *
145 * @abstract
146 */
147TAPProducer.prototype.writeVersion = function() {};
148
149/**
150 * Writes the plan to reporter output stream.
151 *
152 * @abstract
153 * @param {number} ntests - Number of tests that are planned to run.
154 */
155TAPProducer.prototype.writePlan = function(ntests) {
156 println('%d..%d', 1, ntests);
157};
158
159/**
160 * Writes that test passed to reporter output stream.
161 *
162 * @abstract
163 * @param {number} n - Index of test that passed.
164 * @param {Test} test - Instance containing test information.
165 */
166TAPProducer.prototype.writePass = function(n, test) {
167 println('ok %d %s', n, title(test));
168};
169
170/**
171 * Writes that test was skipped to reporter output stream.
172 *
173 * @abstract
174 * @param {number} n - Index of test that was skipped.
175 * @param {Test} test - Instance containing test information.
176 */
177TAPProducer.prototype.writePending = function(n, test) {
178 println('ok %d %s # SKIP -', n, title(test));
179};
180
181/**
182 * Writes that test failed to reporter output stream.
183 *
184 * @abstract
185 * @param {number} n - Index of test that failed.
186 * @param {Test} test - Instance containing test information.
187 * @param {Error} err - Reason the test failed.
188 */
189TAPProducer.prototype.writeFail = function(n, test, err) {
190 println('not ok %d %s', n, title(test));
191};
192
193/**
194 * Writes the summary epilogue to reporter output stream.
195 *
196 * @abstract
197 * @param {Object} stats - Object containing run statistics.
198 */
199TAPProducer.prototype.writeEpilogue = function(stats) {
200 // :TBD: Why is this not counting pending tests?
201 println('# tests ' + (stats.passes + stats.failures));
202 println('# pass ' + stats.passes);
203 // :TBD: Why are we not showing pending results?
204 println('# fail ' + stats.failures);
205 this.writePlan(stats.passes + stats.failures + stats.pending);
206};
207
208/**
209 * @summary
210 * Constructs a new TAP12Producer.
211 *
212 * @description
213 * Produces output conforming to the TAP12 specification.
214 *
215 * @private
216 * @constructor
217 * @extends TAPProducer
218 * @see {@link https://testanything.org/tap-specification.html|Specification}
219 */
220function TAP12Producer() {
221 /**
222 * Writes that test failed to reporter output stream, with error formatting.
223 * @override
224 */
225 this.writeFail = function(n, test, err) {
226 TAPProducer.prototype.writeFail.call(this, n, test, err);
227 if (err.message) {
228 println(err.message.replace(/^/gm, ' '));
229 }
230 if (err.stack) {
231 println(err.stack.replace(/^/gm, ' '));
232 }
233 };
234}
235
236/**
237 * Inherit from `TAPProducer.prototype`.
238 */
239inherits(TAP12Producer, TAPProducer);
240
241/**
242 * @summary
243 * Constructs a new TAP13Producer.
244 *
245 * @description
246 * Produces output conforming to the TAP13 specification.
247 *
248 * @private
249 * @constructor
250 * @extends TAPProducer
251 * @see {@link https://testanything.org/tap-version-13-specification.html|Specification}
252 */
253function TAP13Producer() {
254 /**
255 * Writes the TAP version to reporter output stream.
256 * @override
257 */
258 this.writeVersion = function() {
259 println('TAP version 13');
260 };
261
262 /**
263 * Writes that test failed to reporter output stream, with error formatting.
264 * @override
265 */
266 this.writeFail = function(n, test, err) {
267 TAPProducer.prototype.writeFail.call(this, n, test, err);
268 var emitYamlBlock = err.message != null || err.stack != null;
269 if (emitYamlBlock) {
270 println(indent(1) + '---');
271 if (err.message) {
272 println(indent(2) + 'message: |-');
273 println(err.message.replace(/^/gm, indent(3)));
274 }
275 if (err.stack) {
276 println(indent(2) + 'stack: |-');
277 println(err.stack.replace(/^/gm, indent(3)));
278 }
279 println(indent(1) + '...');
280 }
281 };
282
283 function indent(level) {
284 return Array(level + 1).join(' ');
285 }
286}
287
288/**
289 * Inherit from `TAPProducer.prototype`.
290 */
291inherits(TAP13Producer, TAPProducer);
292
293TAP.description = 'TAP-compatible output';