tap.js 6.9 KB

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