unit-test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. module.metadata = {
  6. "stability": "deprecated"
  7. };
  8. const memory = require('./memory');
  9. var timer = require("../timers");
  10. var cfxArgs = require("@test/options");
  11. exports.findAndRunTests = function findAndRunTests(options) {
  12. var TestFinder = require("./unit-test-finder").TestFinder;
  13. var finder = new TestFinder({
  14. filter: options.filter,
  15. testInProcess: options.testInProcess,
  16. testOutOfProcess: options.testOutOfProcess
  17. });
  18. var runner = new TestRunner({fs: options.fs});
  19. finder.findTests(
  20. function (tests) {
  21. runner.startMany({tests: tests,
  22. stopOnError: options.stopOnError,
  23. onDone: options.onDone});
  24. });
  25. };
  26. var TestRunner = exports.TestRunner = function TestRunner(options) {
  27. if (options) {
  28. this.fs = options.fs;
  29. }
  30. this.console = (options && "console" in options) ? options.console : console;
  31. memory.track(this);
  32. this.passed = 0;
  33. this.failed = 0;
  34. this.testRunSummary = [];
  35. this.expectFailNesting = 0;
  36. };
  37. TestRunner.prototype = {
  38. toString: function toString() "[object TestRunner]",
  39. DEFAULT_PAUSE_TIMEOUT: 5*60000,
  40. PAUSE_DELAY: 500,
  41. _logTestFailed: function _logTestFailed(why) {
  42. if (!(why in this.test.errors))
  43. this.test.errors[why] = 0;
  44. this.test.errors[why]++;
  45. },
  46. pass: function pass(message) {
  47. if(!this.expectFailure) {
  48. if ("testMessage" in this.console)
  49. this.console.testMessage(true, true, this.test.name, message);
  50. else
  51. this.console.info("pass:", message);
  52. this.passed++;
  53. this.test.passed++;
  54. }
  55. else {
  56. this.expectFailure = false;
  57. this._logTestFailed("failure");
  58. if ("testMessage" in this.console) {
  59. this.console.testMessage(true, false, this.test.name, message);
  60. }
  61. else {
  62. this.console.error("fail:", 'Failure Expected: ' + message)
  63. this.console.trace();
  64. }
  65. this.failed++;
  66. this.test.failed++;
  67. }
  68. },
  69. fail: function fail(message) {
  70. if(!this.expectFailure) {
  71. this._logTestFailed("failure");
  72. if ("testMessage" in this.console) {
  73. this.console.testMessage(false, false, this.test.name, message);
  74. }
  75. else {
  76. this.console.error("fail:", message)
  77. this.console.trace();
  78. }
  79. this.failed++;
  80. this.test.failed++;
  81. }
  82. else {
  83. this.expectFailure = false;
  84. if ("testMessage" in this.console)
  85. this.console.testMessage(false, true, this.test.name, message);
  86. else
  87. this.console.info("pass:", message);
  88. this.passed++;
  89. this.test.passed++;
  90. }
  91. },
  92. expectFail: function(callback) {
  93. this.expectFailure = true;
  94. callback();
  95. this.expectFailure = false;
  96. },
  97. exception: function exception(e) {
  98. this._logTestFailed("exception");
  99. if (cfxArgs.parseable)
  100. this.console.print("TEST-UNEXPECTED-FAIL | " + this.test.name + " | " + e + "\n");
  101. this.console.exception(e);
  102. this.failed++;
  103. this.test.failed++;
  104. },
  105. assertMatches: function assertMatches(string, regexp, message) {
  106. if (regexp.test(string)) {
  107. if (!message)
  108. message = uneval(string) + " matches " + uneval(regexp);
  109. this.pass(message);
  110. } else {
  111. var no = uneval(string) + " doesn't match " + uneval(regexp);
  112. if (!message)
  113. message = no;
  114. else
  115. message = message + " (" + no + ")";
  116. this.fail(message);
  117. }
  118. },
  119. assertRaises: function assertRaises(func, predicate, message) {
  120. try {
  121. func();
  122. if (message)
  123. this.fail(message + " (no exception thrown)");
  124. else
  125. this.fail("function failed to throw exception");
  126. } catch (e) {
  127. var errorMessage;
  128. if (typeof(e) == "string")
  129. errorMessage = e;
  130. else
  131. errorMessage = e.message;
  132. if (typeof(predicate) == "string")
  133. this.assertEqual(errorMessage, predicate, message);
  134. else
  135. this.assertMatches(errorMessage, predicate, message);
  136. }
  137. },
  138. assert: function assert(a, message) {
  139. if (!a) {
  140. if (!message)
  141. message = "assertion failed, value is " + a;
  142. this.fail(message);
  143. } else
  144. this.pass(message || "assertion successful");
  145. },
  146. assertNotEqual: function assertNotEqual(a, b, message) {
  147. if (a != b) {
  148. if (!message)
  149. message = "a != b != " + uneval(a);
  150. this.pass(message);
  151. } else {
  152. var equality = uneval(a) + " == " + uneval(b);
  153. if (!message)
  154. message = equality;
  155. else
  156. message += " (" + equality + ")";
  157. this.fail(message);
  158. }
  159. },
  160. assertEqual: function assertEqual(a, b, message) {
  161. if (a == b) {
  162. if (!message)
  163. message = "a == b == " + uneval(a);
  164. this.pass(message);
  165. } else {
  166. var inequality = uneval(a) + " != " + uneval(b);
  167. if (!message)
  168. message = inequality;
  169. else
  170. message += " (" + inequality + ")";
  171. this.fail(message);
  172. }
  173. },
  174. assertNotStrictEqual: function assertNotStrictEqual(a, b, message) {
  175. if (a !== b) {
  176. if (!message)
  177. message = "a !== b !== " + uneval(a);
  178. this.pass(message);
  179. } else {
  180. var equality = uneval(a) + " === " + uneval(b);
  181. if (!message)
  182. message = equality;
  183. else
  184. message += " (" + equality + ")";
  185. this.fail(message);
  186. }
  187. },
  188. assertStrictEqual: function assertStrictEqual(a, b, message) {
  189. if (a === b) {
  190. if (!message)
  191. message = "a === b === " + uneval(a);
  192. this.pass(message);
  193. } else {
  194. var inequality = uneval(a) + " !== " + uneval(b);
  195. if (!message)
  196. message = inequality;
  197. else
  198. message += " (" + inequality + ")";
  199. this.fail(message);
  200. }
  201. },
  202. assertFunction: function assertFunction(a, message) {
  203. this.assertStrictEqual('function', typeof a, message);
  204. },
  205. assertUndefined: function(a, message) {
  206. this.assertStrictEqual('undefined', typeof a, message);
  207. },
  208. assertNotUndefined: function(a, message) {
  209. this.assertNotStrictEqual('undefined', typeof a, message);
  210. },
  211. assertNull: function(a, message) {
  212. this.assertStrictEqual(null, a, message);
  213. },
  214. assertNotNull: function(a, message) {
  215. this.assertNotStrictEqual(null, a, message);
  216. },
  217. assertObject: function(a, message) {
  218. this.assertStrictEqual('[object Object]', Object.prototype.toString.apply(a), message);
  219. },
  220. assertString: function(a, message) {
  221. this.assertStrictEqual('[object String]', Object.prototype.toString.apply(a), message);
  222. },
  223. assertArray: function(a, message) {
  224. this.assertStrictEqual('[object Array]', Object.prototype.toString.apply(a), message);
  225. },
  226. assertNumber: function(a, message) {
  227. this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message);
  228. },
  229. done: function done() {
  230. if (!this.isDone) {
  231. this.isDone = true;
  232. if(this.test.teardown) {
  233. this.test.teardown(this);
  234. }
  235. if (this.waitTimeout !== null) {
  236. timer.clearTimeout(this.waitTimeout);
  237. this.waitTimeout = null;
  238. }
  239. // Do not leave any callback set when calling to `waitUntil`
  240. this.waitUntilCallback = null;
  241. if (this.test.passed == 0 && this.test.failed == 0) {
  242. this._logTestFailed("empty test");
  243. if ("testMessage" in this.console) {
  244. this.console.testMessage(false, false, this.test.name, "Empty test");
  245. }
  246. else {
  247. this.console.error("fail:", "Empty test")
  248. }
  249. this.failed++;
  250. this.test.failed++;
  251. }
  252. this.testRunSummary.push({
  253. name: this.test.name,
  254. passed: this.test.passed,
  255. failed: this.test.failed,
  256. errors: [error for (error in this.test.errors)].join(", ")
  257. });
  258. if (this.onDone !== null) {
  259. var onDone = this.onDone;
  260. var self = this;
  261. this.onDone = null;
  262. timer.setTimeout(function() { onDone(self); }, 0);
  263. }
  264. }
  265. },
  266. // Set of assertion functions to wait for an assertion to become true
  267. // These functions take the same arguments as the TestRunner.assert* methods.
  268. waitUntil: function waitUntil() {
  269. return this._waitUntil(this.assert, arguments);
  270. },
  271. waitUntilNotEqual: function waitUntilNotEqual() {
  272. return this._waitUntil(this.assertNotEqual, arguments);
  273. },
  274. waitUntilEqual: function waitUntilEqual() {
  275. return this._waitUntil(this.assertEqual, arguments);
  276. },
  277. waitUntilMatches: function waitUntilMatches() {
  278. return this._waitUntil(this.assertMatches, arguments);
  279. },
  280. /**
  281. * Internal function that waits for an assertion to become true.
  282. * @param {Function} assertionMethod
  283. * Reference to a TestRunner assertion method like test.assert,
  284. * test.assertEqual, ...
  285. * @param {Array} args
  286. * List of arguments to give to the previous assertion method.
  287. * All functions in this list are going to be called to retrieve current
  288. * assertion values.
  289. */
  290. _waitUntil: function waitUntil(assertionMethod, args) {
  291. let count = 0;
  292. let maxCount = this.DEFAULT_PAUSE_TIMEOUT / this.PAUSE_DELAY;
  293. // We need to ensure that test is asynchronous
  294. if (!this.waitTimeout)
  295. this.waitUntilDone(this.DEFAULT_PAUSE_TIMEOUT);
  296. let callback = null;
  297. let finished = false;
  298. let test = this;
  299. // capture a traceback before we go async.
  300. let traceback = require("../console/traceback");
  301. let stack = traceback.get();
  302. stack.splice(-2, 2);
  303. let currentWaitStack = traceback.format(stack);
  304. let timeout = null;
  305. function loop(stopIt) {
  306. timeout = null;
  307. // Build a mockup object to fake TestRunner API and intercept calls to
  308. // pass and fail methods, in order to retrieve nice error messages
  309. // and assertion result
  310. let mock = {
  311. pass: function (msg) {
  312. test.pass(msg);
  313. test.waitUntilCallback = null;
  314. if (callback && !stopIt)
  315. callback();
  316. finished = true;
  317. },
  318. fail: function (msg) {
  319. // If we are called on test timeout, we stop the loop
  320. // and print which test keeps failing:
  321. if (stopIt) {
  322. test.console.error("test assertion never became true:\n",
  323. msg + "\n",
  324. currentWaitStack);
  325. if (timeout)
  326. timer.clearTimeout(timeout);
  327. return;
  328. }
  329. timeout = timer.setTimeout(loop, test.PAUSE_DELAY);
  330. }
  331. };
  332. // Automatically call args closures in order to build arguments for
  333. // assertion function
  334. let appliedArgs = [];
  335. for (let i = 0, l = args.length; i < l; i++) {
  336. let a = args[i];
  337. if (typeof a == "function") {
  338. try {
  339. a = a();
  340. }
  341. catch(e) {
  342. test.fail("Exception when calling asynchronous assertion: " + e +
  343. "\n" + e.stack);
  344. finished = true;
  345. return;
  346. }
  347. }
  348. appliedArgs.push(a);
  349. }
  350. // Finally call assertion function with current assertion values
  351. assertionMethod.apply(mock, appliedArgs);
  352. }
  353. loop();
  354. this.waitUntilCallback = loop;
  355. // Return an object with `then` method, to offer a way to execute
  356. // some code when the assertion passed or failed
  357. return {
  358. then: function (c) {
  359. callback = c;
  360. // In case of immediate positive result, we need to execute callback
  361. // immediately here:
  362. if (finished)
  363. callback();
  364. }
  365. };
  366. },
  367. waitUntilDone: function waitUntilDone(ms) {
  368. if (ms === undefined)
  369. ms = this.DEFAULT_PAUSE_TIMEOUT;
  370. var self = this;
  371. function tiredOfWaiting() {
  372. self._logTestFailed("timed out");
  373. if ("testMessage" in self.console) {
  374. self.console.testMessage(false, false, self.test.name, "Timed out");
  375. }
  376. else {
  377. self.console.error("fail:", "Timed out")
  378. }
  379. if (self.waitUntilCallback) {
  380. self.waitUntilCallback(true);
  381. self.waitUntilCallback = null;
  382. }
  383. self.failed++;
  384. self.test.failed++;
  385. self.done();
  386. }
  387. // We may already have registered a timeout callback
  388. if (this.waitTimeout)
  389. timer.clearTimeout(this.waitTimeout);
  390. this.waitTimeout = timer.setTimeout(tiredOfWaiting, ms);
  391. },
  392. startMany: function startMany(options) {
  393. function runNextTest(self) {
  394. var test = options.tests.shift();
  395. if (options.stopOnError && self.test && self.test.failed) {
  396. self.console.error("aborted: test failed and --stop-on-error was specified");
  397. options.onDone(self);
  398. } else if (test) {
  399. self.start({test: test, onDone: runNextTest});
  400. } else {
  401. options.onDone(self);
  402. }
  403. }
  404. runNextTest(this);
  405. },
  406. start: function start(options) {
  407. this.test = options.test;
  408. this.test.passed = 0;
  409. this.test.failed = 0;
  410. this.test.errors = {};
  411. this.isDone = false;
  412. this.onDone = function(self) {
  413. if (cfxArgs.parseable)
  414. self.console.print("TEST-END | " + self.test.name + "\n");
  415. options.onDone(self);
  416. }
  417. this.waitTimeout = null;
  418. try {
  419. if (cfxArgs.parseable)
  420. this.console.print("TEST-START | " + this.test.name + "\n");
  421. else
  422. this.console.info("executing '" + this.test.name + "'");
  423. if(this.test.setup) {
  424. this.test.setup(this);
  425. }
  426. this.test.testFunction(this);
  427. } catch (e) {
  428. this.exception(e);
  429. }
  430. if (this.waitTimeout === null)
  431. this.done();
  432. }
  433. };