| 'use strict'; |
| /** |
| * @module TAP |
| */ |
| /** |
| * Module dependencies. |
| */ |
| |
| var util = require('util'); |
| var Base = require('./base'); |
| var constants = require('../runner').constants; |
| var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; |
| var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; |
| var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; |
| var EVENT_RUN_END = constants.EVENT_RUN_END; |
| var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; |
| var EVENT_TEST_END = constants.EVENT_TEST_END; |
| var inherits = require('../utils').inherits; |
| var sprintf = util.format; |
| |
| /** |
| * Expose `TAP`. |
| */ |
| |
| exports = module.exports = TAP; |
| |
| /** |
| * Constructs a new `TAP` reporter instance. |
| * |
| * @public |
| * @class |
| * @memberof Mocha.reporters |
| * @extends Mocha.reporters.Base |
| * @param {Runner} runner - Instance triggers reporter actions. |
| * @param {Object} [options] - runner options |
| */ |
| function TAP(runner, options) { |
| Base.call(this, runner, options); |
| |
| var self = this; |
| var n = 1; |
| |
| var tapVersion = '12'; |
| if (options && options.reporterOptions) { |
| if (options.reporterOptions.tapVersion) { |
| tapVersion = options.reporterOptions.tapVersion.toString(); |
| } |
| } |
| |
| this._producer = createProducer(tapVersion); |
| |
| runner.once(EVENT_RUN_BEGIN, function() { |
| var ntests = runner.grepTotal(runner.suite); |
| self._producer.writeVersion(); |
| self._producer.writePlan(ntests); |
| }); |
| |
| runner.on(EVENT_TEST_END, function() { |
| ++n; |
| }); |
| |
| runner.on(EVENT_TEST_PENDING, function(test) { |
| self._producer.writePending(n, test); |
| }); |
| |
| runner.on(EVENT_TEST_PASS, function(test) { |
| self._producer.writePass(n, test); |
| }); |
| |
| runner.on(EVENT_TEST_FAIL, function(test, err) { |
| self._producer.writeFail(n, test, err); |
| }); |
| |
| runner.once(EVENT_RUN_END, function() { |
| self._producer.writeEpilogue(runner.stats); |
| }); |
| } |
| |
| /** |
| * Inherit from `Base.prototype`. |
| */ |
| inherits(TAP, Base); |
| |
| /** |
| * Returns a TAP-safe title of `test`. |
| * |
| * @private |
| * @param {Test} test - Test instance. |
| * @return {String} title with any hash character removed |
| */ |
| function title(test) { |
| return test.fullTitle().replace(/#/g, ''); |
| } |
| |
| /** |
| * Writes newline-terminated formatted string to reporter output stream. |
| * |
| * @private |
| * @param {string} format - `printf`-like format string |
| * @param {...*} [varArgs] - Format string arguments |
| */ |
| function println(format, varArgs) { |
| var vargs = Array.from(arguments); |
| vargs[0] += '\n'; |
| process.stdout.write(sprintf.apply(null, vargs)); |
| } |
| |
| /** |
| * Returns a `tapVersion`-appropriate TAP producer instance, if possible. |
| * |
| * @private |
| * @param {string} tapVersion - Version of TAP specification to produce. |
| * @returns {TAPProducer} specification-appropriate instance |
| * @throws {Error} if specification version has no associated producer. |
| */ |
| function createProducer(tapVersion) { |
| var producers = { |
| '12': new TAP12Producer(), |
| '13': new TAP13Producer() |
| }; |
| var producer = producers[tapVersion]; |
| |
| if (!producer) { |
| throw new Error( |
| 'invalid or unsupported TAP version: ' + JSON.stringify(tapVersion) |
| ); |
| } |
| |
| return producer; |
| } |
| |
| /** |
| * @summary |
| * Constructs a new TAPProducer. |
| * |
| * @description |
| * <em>Only</em> to be used as an abstract base class. |
| * |
| * @private |
| * @constructor |
| */ |
| function TAPProducer() {} |
| |
| /** |
| * Writes the TAP version to reporter output stream. |
| * |
| * @abstract |
| */ |
| TAPProducer.prototype.writeVersion = function() {}; |
| |
| /** |
| * Writes the plan to reporter output stream. |
| * |
| * @abstract |
| * @param {number} ntests - Number of tests that are planned to run. |
| */ |
| TAPProducer.prototype.writePlan = function(ntests) { |
| println('%d..%d', 1, ntests); |
| }; |
| |
| /** |
| * Writes that test passed to reporter output stream. |
| * |
| * @abstract |
| * @param {number} n - Index of test that passed. |
| * @param {Test} test - Instance containing test information. |
| */ |
| TAPProducer.prototype.writePass = function(n, test) { |
| println('ok %d %s', n, title(test)); |
| }; |
| |
| /** |
| * Writes that test was skipped to reporter output stream. |
| * |
| * @abstract |
| * @param {number} n - Index of test that was skipped. |
| * @param {Test} test - Instance containing test information. |
| */ |
| TAPProducer.prototype.writePending = function(n, test) { |
| println('ok %d %s # SKIP -', n, title(test)); |
| }; |
| |
| /** |
| * Writes that test failed to reporter output stream. |
| * |
| * @abstract |
| * @param {number} n - Index of test that failed. |
| * @param {Test} test - Instance containing test information. |
| * @param {Error} err - Reason the test failed. |
| */ |
| TAPProducer.prototype.writeFail = function(n, test, err) { |
| println('not ok %d %s', n, title(test)); |
| }; |
| |
| /** |
| * Writes the summary epilogue to reporter output stream. |
| * |
| * @abstract |
| * @param {Object} stats - Object containing run statistics. |
| */ |
| TAPProducer.prototype.writeEpilogue = function(stats) { |
| // :TBD: Why is this not counting pending tests? |
| println('# tests ' + (stats.passes + stats.failures)); |
| println('# pass ' + stats.passes); |
| // :TBD: Why are we not showing pending results? |
| println('# fail ' + stats.failures); |
| }; |
| |
| /** |
| * @summary |
| * Constructs a new TAP12Producer. |
| * |
| * @description |
| * Produces output conforming to the TAP12 specification. |
| * |
| * @private |
| * @constructor |
| * @extends TAPProducer |
| * @see {@link https://testanything.org/tap-specification.html|Specification} |
| */ |
| function TAP12Producer() { |
| /** |
| * Writes that test failed to reporter output stream, with error formatting. |
| * @override |
| */ |
| this.writeFail = function(n, test, err) { |
| TAPProducer.prototype.writeFail.call(this, n, test, err); |
| if (err.message) { |
| println(err.message.replace(/^/gm, ' ')); |
| } |
| if (err.stack) { |
| println(err.stack.replace(/^/gm, ' ')); |
| } |
| }; |
| } |
| |
| /** |
| * Inherit from `TAPProducer.prototype`. |
| */ |
| inherits(TAP12Producer, TAPProducer); |
| |
| /** |
| * @summary |
| * Constructs a new TAP13Producer. |
| * |
| * @description |
| * Produces output conforming to the TAP13 specification. |
| * |
| * @private |
| * @constructor |
| * @extends TAPProducer |
| * @see {@link https://testanything.org/tap-version-13-specification.html|Specification} |
| */ |
| function TAP13Producer() { |
| /** |
| * Writes the TAP version to reporter output stream. |
| * @override |
| */ |
| this.writeVersion = function() { |
| println('TAP version 13'); |
| }; |
| |
| /** |
| * Writes that test failed to reporter output stream, with error formatting. |
| * @override |
| */ |
| this.writeFail = function(n, test, err) { |
| TAPProducer.prototype.writeFail.call(this, n, test, err); |
| var emitYamlBlock = err.message != null || err.stack != null; |
| if (emitYamlBlock) { |
| println(indent(1) + '---'); |
| if (err.message) { |
| println(indent(2) + 'message: |-'); |
| println(err.message.replace(/^/gm, indent(3))); |
| } |
| if (err.stack) { |
| println(indent(2) + 'stack: |-'); |
| println(err.stack.replace(/^/gm, indent(3))); |
| } |
| println(indent(1) + '...'); |
| } |
| }; |
| |
| function indent(level) { |
| return Array(level + 1).join(' '); |
| } |
| } |
| |
| /** |
| * Inherit from `TAPProducer.prototype`. |
| */ |
| inherits(TAP13Producer, TAPProducer); |
| |
| TAP.description = 'TAP-compatible output'; |