results.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. 'use strict';
  2. var defined = require('defined');
  3. var EventEmitter = require('events').EventEmitter;
  4. var inherits = require('inherits');
  5. var through = require('through');
  6. var resumer = require('resumer');
  7. var inspect = require('object-inspect');
  8. var callBound = require('call-bind/callBound');
  9. var has = require('has');
  10. var $exec = callBound('RegExp.prototype.exec');
  11. var yamlIndicators = /:|-|\?/;
  12. var nextTick = typeof setImmediate !== 'undefined' ? setImmediate : process.nextTick;
  13. function coalesceWhiteSpaces(str) {
  14. return String(str).replace(/\s+/g, ' ');
  15. }
  16. function invalidYaml(str) {
  17. return $exec(yamlIndicators, str) !== null;
  18. }
  19. function encodeResult(res, count) {
  20. var output = '';
  21. output += (res.ok ? 'ok ' : 'not ok ') + count;
  22. output += res.name ? ' ' + coalesceWhiteSpaces(res.name) : '';
  23. if (res.skip) {
  24. output += ' # SKIP' + (typeof res.skip === 'string' ? ' ' + coalesceWhiteSpaces(res.skip) : '');
  25. } else if (res.todo) {
  26. output += ' # TODO' + (typeof res.todo === 'string' ? ' ' + coalesceWhiteSpaces(res.todo) : '');
  27. }
  28. output += '\n';
  29. if (res.ok) { return output; }
  30. var outer = ' ';
  31. var inner = outer + ' ';
  32. output += outer + '---\n';
  33. output += inner + 'operator: ' + res.operator + '\n';
  34. if (has(res, 'expected') || has(res, 'actual')) {
  35. var ex = inspect(res.expected, { depth: res.objectPrintDepth });
  36. var ac = inspect(res.actual, { depth: res.objectPrintDepth });
  37. if (Math.max(ex.length, ac.length) > 65 || invalidYaml(ex) || invalidYaml(ac)) {
  38. output += inner + 'expected: |-\n' + inner + ' ' + ex + '\n';
  39. output += inner + 'actual: |-\n' + inner + ' ' + ac + '\n';
  40. } else {
  41. output += inner + 'expected: ' + ex + '\n';
  42. output += inner + 'actual: ' + ac + '\n';
  43. }
  44. }
  45. if (res.at) {
  46. output += inner + 'at: ' + res.at + '\n';
  47. }
  48. var actualStack = res.actual && (typeof res.actual === 'object' || typeof res.actual === 'function') ? res.actual.stack : undefined;
  49. var errorStack = res.error && res.error.stack;
  50. var stack = defined(actualStack, errorStack);
  51. if (stack) {
  52. var lines = String(stack).split('\n');
  53. output += inner + 'stack: |-\n';
  54. for (var i = 0; i < lines.length; i++) {
  55. output += inner + ' ' + lines[i] + '\n';
  56. }
  57. }
  58. output += outer + '...\n';
  59. return output;
  60. }
  61. function getNextTest(results) {
  62. if (!results._only) {
  63. return results.tests.shift();
  64. }
  65. do {
  66. var t = results.tests.shift();
  67. if (t && results._only === t) {
  68. return t;
  69. }
  70. } while (results.tests.length !== 0);
  71. return void undefined;
  72. }
  73. function Results() {
  74. if (!(this instanceof Results)) { return new Results(); }
  75. this.count = 0;
  76. this.fail = 0;
  77. this.pass = 0;
  78. this.todo = 0;
  79. this._stream = through();
  80. this.tests = [];
  81. this._only = null;
  82. this._isRunning = false;
  83. }
  84. inherits(Results, EventEmitter);
  85. Results.prototype.createStream = function (opts) {
  86. if (!opts) { opts = {}; }
  87. var self = this;
  88. var output;
  89. var testId = 0;
  90. if (opts.objectMode) {
  91. output = through();
  92. self.on('_push', function ontest(t, extra) {
  93. if (!extra) { extra = {}; }
  94. var id = testId++;
  95. t.once('prerun', function () {
  96. var row = {
  97. type: 'test',
  98. name: t.name,
  99. id: id,
  100. skip: t._skip,
  101. todo: t._todo
  102. };
  103. if (has(extra, 'parent')) {
  104. row.parent = extra.parent;
  105. }
  106. output.queue(row);
  107. });
  108. t.on('test', function (st) {
  109. ontest(st, { parent: id });
  110. });
  111. t.on('result', function (res) {
  112. if (res && typeof res === 'object') {
  113. res.test = id;
  114. res.type = 'assert';
  115. }
  116. output.queue(res);
  117. });
  118. t.on('end', function () {
  119. output.queue({ type: 'end', test: id });
  120. });
  121. });
  122. self.on('done', function () { output.queue(null); });
  123. } else {
  124. output = resumer();
  125. output.queue('TAP version 13\n');
  126. self._stream.pipe(output);
  127. }
  128. if (!this._isRunning) {
  129. this._isRunning = true;
  130. nextTick(function next() {
  131. var t;
  132. while (t = getNextTest(self)) {
  133. t.run();
  134. if (!t.ended) {
  135. t.once('end', function () { nextTick(next); });
  136. return;
  137. }
  138. }
  139. self.emit('done');
  140. });
  141. }
  142. return output;
  143. };
  144. Results.prototype.push = function (t) {
  145. var self = this;
  146. self.tests.push(t);
  147. self._watch(t);
  148. self.emit('_push', t);
  149. };
  150. Results.prototype.only = function (t) {
  151. this._only = t;
  152. };
  153. Results.prototype._watch = function (t) {
  154. var self = this;
  155. function write(s) { self._stream.queue(s); }
  156. t.once('prerun', function () {
  157. var premsg = '';
  158. if (t._skip) {
  159. premsg = 'SKIP ';
  160. } else if (t._todo) {
  161. premsg = 'TODO ';
  162. }
  163. write('# ' + premsg + coalesceWhiteSpaces(t.name) + '\n');
  164. });
  165. t.on('result', function (res) {
  166. if (typeof res === 'string') {
  167. write('# ' + res + '\n');
  168. return;
  169. }
  170. write(encodeResult(res, self.count + 1));
  171. self.count++;
  172. if (res.ok || res.todo) {
  173. self.pass++;
  174. } else {
  175. self.fail++;
  176. self.emit('fail');
  177. }
  178. });
  179. t.on('test', function (st) { self._watch(st); });
  180. };
  181. Results.prototype.close = function () {
  182. var self = this;
  183. if (self.closed) { self._stream.emit('error', new Error('ALREADY CLOSED')); }
  184. self.closed = true;
  185. function write(s) { self._stream.queue(s); }
  186. write('\n1..' + self.count + '\n');
  187. write('# tests ' + self.count + '\n');
  188. write('# pass ' + (self.pass + self.todo) + '\n');
  189. if (self.todo) {
  190. write('# todo ' + self.todo + '\n');
  191. } if (self.fail) {
  192. write('# fail ' + self.fail + '\n');
  193. } else {
  194. write('\n# ok\n');
  195. }
  196. self._stream.queue(null);
  197. };
  198. module.exports = Results;