Assert.jsm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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. // http://wiki.commonjs.org/wiki/Unit_Testing/1.0
  5. // When you see a javadoc comment that contains a number, it's a reference to a
  6. // specific section of the CommonJS spec.
  7. //
  8. // Originally from narwhal.js (http://narwhaljs.org)
  9. // Copyright (c) 2009 Thomas Robinson <280north.com>
  10. // MIT license: http://opensource.org/licenses/MIT
  11. "use strict";
  12. this.EXPORTED_SYMBOLS = [
  13. "Assert"
  14. ];
  15. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  16. Components.utils.import("resource://gre/modules/ObjectUtils.jsm");
  17. XPCOMUtils.defineLazyModuleGetter(this, "Promise",
  18. "resource://gre/modules/Promise.jsm");
  19. /**
  20. * 1. The assert module provides functions that throw AssertionError's when
  21. * particular conditions are not met.
  22. *
  23. * To use the module you'll need to instantiate it first, which allows consumers
  24. * to override certain behavior on the newly obtained instance. For examples,
  25. * see the javadoc comments for the `report` member function.
  26. */
  27. var Assert = this.Assert = function(reporterFunc) {
  28. if (reporterFunc)
  29. this.setReporter(reporterFunc);
  30. };
  31. function instanceOf(object, type) {
  32. return Object.prototype.toString.call(object) == "[object " + type + "]";
  33. }
  34. function replacer(key, value) {
  35. if (value === undefined) {
  36. return "" + value;
  37. }
  38. if (typeof value === "number" && (isNaN(value) || !isFinite(value))) {
  39. return value.toString();
  40. }
  41. if (typeof value === "function" || instanceOf(value, "RegExp")) {
  42. return value.toString();
  43. }
  44. return value;
  45. }
  46. const kTruncateLength = 128;
  47. function truncate(text, newLength = kTruncateLength) {
  48. if (typeof text == "string") {
  49. return text.length < newLength ? text : text.slice(0, newLength);
  50. } else {
  51. return text;
  52. }
  53. }
  54. function getMessage(error, prefix = "") {
  55. let actual, expected;
  56. // Wrap calls to JSON.stringify in try...catch blocks, as they may throw. If
  57. // so, fall back to toString().
  58. try {
  59. actual = JSON.stringify(error.actual, replacer);
  60. } catch (ex) {
  61. actual = Object.prototype.toString.call(error.actual);
  62. }
  63. try {
  64. expected = JSON.stringify(error.expected, replacer);
  65. } catch (ex) {
  66. expected = Object.prototype.toString.call(error.expected);
  67. }
  68. let message = prefix;
  69. if (error.operator) {
  70. message += (prefix ? " - " : "") + truncate(actual) + " " + error.operator +
  71. " " + truncate(expected);
  72. }
  73. return message;
  74. }
  75. /**
  76. * 2. The AssertionError is defined in assert.
  77. *
  78. * Example:
  79. * new assert.AssertionError({
  80. * message: message,
  81. * actual: actual,
  82. * expected: expected,
  83. * operator: operator
  84. * });
  85. *
  86. * At present only the four keys mentioned above are used and
  87. * understood by the spec. Implementations or sub modules can pass
  88. * other keys to the AssertionError's constructor - they will be
  89. * ignored.
  90. */
  91. Assert.AssertionError = function(options) {
  92. this.name = "AssertionError";
  93. this.actual = options.actual;
  94. this.expected = options.expected;
  95. this.operator = options.operator;
  96. this.message = getMessage(this, options.message);
  97. // The part of the stack that comes from this module is not interesting.
  98. let stack = Components.stack;
  99. do {
  100. stack = stack.asyncCaller || stack.caller;
  101. } while(stack && stack.filename && stack.filename.includes("Assert.jsm"))
  102. this.stack = stack;
  103. };
  104. // assert.AssertionError instanceof Error
  105. Assert.AssertionError.prototype = Object.create(Error.prototype, {
  106. constructor: {
  107. value: Assert.AssertionError,
  108. enumerable: false,
  109. writable: true,
  110. configurable: true
  111. }
  112. });
  113. var proto = Assert.prototype;
  114. proto._reporter = null;
  115. /**
  116. * Set a custom assertion report handler function. Arguments passed in to this
  117. * function are:
  118. * err (AssertionError|null) An error object when the assertion failed or null
  119. * when it passed
  120. * message (string) Message describing the assertion
  121. * stack (stack) Stack trace of the assertion function
  122. *
  123. * Example:
  124. * ```js
  125. * Assert.setReporter(function customReporter(err, message, stack) {
  126. * if (err) {
  127. * do_report_result(false, err.message, err.stack);
  128. * } else {
  129. * do_report_result(true, message, stack);
  130. * }
  131. * });
  132. * ```
  133. *
  134. * @param reporterFunc
  135. * (function) Report handler function
  136. */
  137. proto.setReporter = function(reporterFunc) {
  138. this._reporter = reporterFunc;
  139. };
  140. /**
  141. * 3. All of the following functions must throw an AssertionError when a
  142. * corresponding condition is not met, with a message that may be undefined if
  143. * not provided. All assertion methods provide both the actual and expected
  144. * values to the assertion error for display purposes.
  145. *
  146. * This report method only throws errors on assertion failures, as per spec,
  147. * but consumers of this module (think: xpcshell-test, mochitest) may want to
  148. * override this default implementation.
  149. *
  150. * Example:
  151. * ```js
  152. * // The following will report an assertion failure.
  153. * this.report(1 != 2, 1, 2, "testing JS number math!", "==");
  154. * ```
  155. *
  156. * @param failed
  157. * (boolean) Indicates if the assertion failed or not
  158. * @param actual
  159. * (mixed) The result of evaluating the assertion
  160. * @param expected (optional)
  161. * (mixed) Expected result from the test author
  162. * @param message (optional)
  163. * (string) Short explanation of the expected result
  164. * @param operator (optional)
  165. * (string) Operation qualifier used by the assertion method (ex: '==')
  166. */
  167. proto.report = function(failed, actual, expected, message, operator) {
  168. let err = new Assert.AssertionError({
  169. message: message,
  170. actual: actual,
  171. expected: expected,
  172. operator: operator
  173. });
  174. if (!this._reporter) {
  175. // If no custom reporter is set, throw the error.
  176. if (failed) {
  177. throw err;
  178. }
  179. } else {
  180. this._reporter(failed ? err : null, err.message, err.stack);
  181. }
  182. };
  183. /**
  184. * 4. Pure assertion tests whether a value is truthy, as determined by !!guard.
  185. * assert.ok(guard, message_opt);
  186. * This statement is equivalent to assert.equal(true, !!guard, message_opt);.
  187. * To test strictly for the value true, use assert.strictEqual(true, guard,
  188. * message_opt);.
  189. *
  190. * @param value
  191. * (mixed) Test subject to be evaluated as truthy
  192. * @param message (optional)
  193. * (string) Short explanation of the expected result
  194. */
  195. proto.ok = function(value, message) {
  196. this.report(!value, value, true, message, "==");
  197. };
  198. /**
  199. * 5. The equality assertion tests shallow, coercive equality with ==.
  200. * assert.equal(actual, expected, message_opt);
  201. *
  202. * @param actual
  203. * (mixed) Test subject to be evaluated as equivalent to `expected`
  204. * @param expected
  205. * (mixed) Test reference to evaluate against `actual`
  206. * @param message (optional)
  207. * (string) Short explanation of the expected result
  208. */
  209. proto.equal = function equal(actual, expected, message) {
  210. this.report(actual != expected, actual, expected, message, "==");
  211. };
  212. /**
  213. * 6. The non-equality assertion tests for whether two objects are not equal
  214. * with != assert.notEqual(actual, expected, message_opt);
  215. *
  216. * @param actual
  217. * (mixed) Test subject to be evaluated as NOT equivalent to `expected`
  218. * @param expected
  219. * (mixed) Test reference to evaluate against `actual`
  220. * @param message (optional)
  221. * (string) Short explanation of the expected result
  222. */
  223. proto.notEqual = function notEqual(actual, expected, message) {
  224. this.report(actual == expected, actual, expected, message, "!=");
  225. };
  226. /**
  227. * 7. The equivalence assertion tests a deep equality relation.
  228. * assert.deepEqual(actual, expected, message_opt);
  229. *
  230. * We check using the most exact approximation of equality between two objects
  231. * to keep the chance of false positives to a minimum.
  232. * `JSON.stringify` is not designed to be used for this purpose; objects may
  233. * have ambiguous `toJSON()` implementations that would influence the test.
  234. *
  235. * @param actual
  236. * (mixed) Test subject to be evaluated as equivalent to `expected`, including nested properties
  237. * @param expected
  238. * (mixed) Test reference to evaluate against `actual`
  239. * @param message (optional)
  240. * (string) Short explanation of the expected result
  241. */
  242. proto.deepEqual = function deepEqual(actual, expected, message) {
  243. this.report(!ObjectUtils.deepEqual(actual, expected), actual, expected, message, "deepEqual");
  244. };
  245. /**
  246. * 8. The non-equivalence assertion tests for any deep inequality.
  247. * assert.notDeepEqual(actual, expected, message_opt);
  248. *
  249. * @param actual
  250. * (mixed) Test subject to be evaluated as NOT equivalent to `expected`, including nested properties
  251. * @param expected
  252. * (mixed) Test reference to evaluate against `actual`
  253. * @param message (optional)
  254. * (string) Short explanation of the expected result
  255. */
  256. proto.notDeepEqual = function notDeepEqual(actual, expected, message) {
  257. this.report(ObjectUtils.deepEqual(actual, expected), actual, expected, message, "notDeepEqual");
  258. };
  259. /**
  260. * 9. The strict equality assertion tests strict equality, as determined by ===.
  261. * assert.strictEqual(actual, expected, message_opt);
  262. *
  263. * @param actual
  264. * (mixed) Test subject to be evaluated as strictly equivalent to `expected`
  265. * @param expected
  266. * (mixed) Test reference to evaluate against `actual`
  267. * @param message (optional)
  268. * (string) Short explanation of the expected result
  269. */
  270. proto.strictEqual = function strictEqual(actual, expected, message) {
  271. this.report(actual !== expected, actual, expected, message, "===");
  272. };
  273. /**
  274. * 10. The strict non-equality assertion tests for strict inequality, as
  275. * determined by !==. assert.notStrictEqual(actual, expected, message_opt);
  276. *
  277. * @param actual
  278. * (mixed) Test subject to be evaluated as NOT strictly equivalent to `expected`
  279. * @param expected
  280. * (mixed) Test reference to evaluate against `actual`
  281. * @param message (optional)
  282. * (string) Short explanation of the expected result
  283. */
  284. proto.notStrictEqual = function notStrictEqual(actual, expected, message) {
  285. this.report(actual === expected, actual, expected, message, "!==");
  286. };
  287. function expectedException(actual, expected) {
  288. if (!actual || !expected) {
  289. return false;
  290. }
  291. if (instanceOf(expected, "RegExp")) {
  292. return expected.test(actual);
  293. } else if (actual instanceof expected) {
  294. return true;
  295. } else if (expected.call({}, actual) === true) {
  296. return true;
  297. }
  298. return false;
  299. }
  300. /**
  301. * 11. Expected to throw an error:
  302. * assert.throws(block, Error_opt, message_opt);
  303. *
  304. * @param block
  305. * (function) Function block to evaluate and catch eventual thrown errors
  306. * @param expected (optional)
  307. * (mixed) Test reference to evaluate against the thrown result from `block`
  308. * @param message (optional)
  309. * (string) Short explanation of the expected result
  310. */
  311. proto.throws = function(block, expected, message) {
  312. let actual;
  313. if (typeof expected === "string") {
  314. message = expected;
  315. expected = null;
  316. }
  317. try {
  318. block();
  319. } catch (e) {
  320. actual = e;
  321. }
  322. message = (expected && expected.name ? " (" + expected.name + ")." : ".") +
  323. (message ? " " + message : ".");
  324. if (!actual) {
  325. this.report(true, actual, expected, "Missing expected exception" + message);
  326. }
  327. if ((actual && expected && !expectedException(actual, expected))) {
  328. throw actual;
  329. }
  330. this.report(false, expected, expected, message);
  331. };
  332. /**
  333. * A promise that is expected to reject:
  334. * assert.rejects(promise, expected, message);
  335. *
  336. * @param promise
  337. * (promise) A promise that is expected to reject
  338. * @param expected (optional)
  339. * (mixed) Test reference to evaluate against the rejection result
  340. * @param message (optional)
  341. * (string) Short explanation of the expected result
  342. */
  343. proto.rejects = function(promise, expected, message) {
  344. return new Promise((resolve, reject) => {
  345. if (typeof expected === "string") {
  346. message = expected;
  347. expected = null;
  348. }
  349. return promise.then(
  350. () => this.report(true, null, expected, "Missing expected exception " + message),
  351. err => {
  352. if (expected && !expectedException(err, expected)) {
  353. reject(err);
  354. return;
  355. }
  356. this.report(false, err, expected, message);
  357. resolve();
  358. }
  359. ).then(null, reject);
  360. });
  361. };
  362. function compareNumbers(expression, lhs, rhs, message, operator) {
  363. let lhsIsNumber = typeof lhs == "number";
  364. let rhsIsNumber = typeof rhs == "number";
  365. if (lhsIsNumber && rhsIsNumber) {
  366. this.report(expression, lhs, rhs, message, operator);
  367. return;
  368. }
  369. let errorMessage;
  370. if (!lhsIsNumber && !rhsIsNumber) {
  371. errorMessage = "Neither '" + lhs + "' nor '" + rhs + "' are numbers";
  372. } else {
  373. errorMessage = "'" + (lhsIsNumber ? rhs : lhs) + "' is not a number";
  374. }
  375. this.report(true, lhs, rhs, errorMessage);
  376. }
  377. /**
  378. * The lhs must be greater than the rhs.
  379. * assert.greater(lhs, rhs, message_opt);
  380. *
  381. * @param lhs
  382. * (number) The left-hand side value
  383. * @param rhs
  384. * (number) The right-hand side value
  385. * @param message (optional)
  386. * (string) Short explanation of the comparison result
  387. */
  388. proto.greater = function greater(lhs, rhs, message) {
  389. compareNumbers.call(this, lhs <= rhs, lhs, rhs, message, ">");
  390. };
  391. /**
  392. * The lhs must be greater than or equal to the rhs.
  393. * assert.greaterOrEqual(lhs, rhs, message_opt);
  394. *
  395. * @param lhs
  396. * (number) The left-hand side value
  397. * @param rhs
  398. * (number) The right-hand side value
  399. * @param message (optional)
  400. * (string) Short explanation of the comparison result
  401. */
  402. proto.greaterOrEqual = function greaterOrEqual(lhs, rhs, message) {
  403. compareNumbers.call(this, lhs < rhs, lhs, rhs, message, ">=");
  404. };
  405. /**
  406. * The lhs must be less than the rhs.
  407. * assert.less(lhs, rhs, message_opt);
  408. *
  409. * @param lhs
  410. * (number) The left-hand side value
  411. * @param rhs
  412. * (number) The right-hand side value
  413. * @param message (optional)
  414. * (string) Short explanation of the comparison result
  415. */
  416. proto.less = function less(lhs, rhs, message) {
  417. compareNumbers.call(this, lhs >= rhs, lhs, rhs, message, "<");
  418. };
  419. /**
  420. * The lhs must be less than or equal to the rhs.
  421. * assert.lessOrEqual(lhs, rhs, message_opt);
  422. *
  423. * @param lhs
  424. * (number) The left-hand side value
  425. * @param rhs
  426. * (number) The right-hand side value
  427. * @param message (optional)
  428. * (string) Short explanation of the comparison result
  429. */
  430. proto.lessOrEqual = function lessOrEqual(lhs, rhs, message) {
  431. compareNumbers.call(this, lhs > rhs, lhs, rhs, message, "<=");
  432. };