'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';