tap.js 6.9 KB

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