test-common.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. var describe;
  2. var test;
  3. var expect;
  4. var withinSameSecond;
  5. // Stores the results of each test and suite. Has a terrible
  6. // name to avoid name collision.
  7. var __TestResults__ = {};
  8. // So test names like "toString" don't automatically produce an error
  9. Object.setPrototypeOf(__TestResults__, null);
  10. // This array is used to communicate with the C++ program. It treats
  11. // each message in this array as a separate message. Has a terrible
  12. // name to avoid name collision.
  13. var __UserOutput__ = [];
  14. // We also rebind console.log here to use the array above
  15. console.log = (...args) => {
  16. __UserOutput__.push(args.join(" "));
  17. };
  18. class ExpectationError extends Error {
  19. constructor(message) {
  20. super(message);
  21. this.name = "ExpectationError";
  22. }
  23. }
  24. // Use an IIFE to avoid polluting the global namespace as much as possible
  25. (() => {
  26. // FIXME: This is a very naive deepEquals algorithm
  27. const deepEquals = (a, b) => {
  28. if (Array.isArray(a)) return Array.isArray(b) && deepArrayEquals(a, b);
  29. if (typeof a === "object") return typeof b === "object" && deepObjectEquals(a, b);
  30. return Object.is(a, b);
  31. };
  32. const deepArrayEquals = (a, b) => {
  33. if (a.length !== b.length) return false;
  34. for (let i = 0; i < a.length; ++i) {
  35. if (!deepEquals(a[i], b[i])) return false;
  36. }
  37. return true;
  38. };
  39. const deepObjectEquals = (a, b) => {
  40. if (a === null) return b === null;
  41. for (let key of Reflect.ownKeys(a)) {
  42. if (!deepEquals(a[key], b[key])) return false;
  43. }
  44. return true;
  45. };
  46. const valueToString = value => {
  47. try {
  48. if (value === 0 && 1 / value < 0) {
  49. return "-0";
  50. }
  51. return String(value);
  52. } catch {
  53. // e.g for objects without a prototype, the above throws.
  54. return Object.prototype.toString.call(value);
  55. }
  56. };
  57. class Expector {
  58. constructor(target, inverted) {
  59. this.target = target;
  60. this.inverted = !!inverted;
  61. }
  62. get not() {
  63. return new Expector(this.target, !this.inverted);
  64. }
  65. toBe(value) {
  66. this.__doMatcher(() => {
  67. this.__expect(
  68. Object.is(this.target, value),
  69. () =>
  70. `toBe: expected _${valueToString(value)}_, got _${valueToString(
  71. this.target
  72. )}_`
  73. );
  74. });
  75. }
  76. toBeCloseTo(value, precision = 5) {
  77. this.__expect(
  78. typeof this.target === "number",
  79. () => `toBeCloseTo: expected target of type number, got ${typeof this.target}`
  80. );
  81. this.__expect(
  82. typeof value === "number",
  83. () => `toBeCloseTo: expected argument of type number, got ${typeof value}`
  84. );
  85. this.__expect(
  86. typeof precision === "number",
  87. () => `toBeCloseTo: expected precision of type number, got ${typeof precision}`
  88. );
  89. const epsilon = 10 ** -precision / 2;
  90. this.__doMatcher(() => {
  91. this.__expect(Math.abs(this.target - value) < epsilon);
  92. });
  93. }
  94. toHaveLength(length) {
  95. this.__expect(
  96. typeof this.target.length === "number",
  97. () => "toHaveLength: target.length not of type number"
  98. );
  99. this.__doMatcher(() => {
  100. this.__expect(Object.is(this.target.length, length));
  101. });
  102. }
  103. toHaveSize(size) {
  104. this.__expect(
  105. typeof this.target.size === "number",
  106. () => "toHaveSize: target.size not of type number"
  107. );
  108. this.__doMatcher(() => {
  109. this.__expect(Object.is(this.target.size, size));
  110. });
  111. }
  112. toHaveProperty(property, value) {
  113. this.__doMatcher(() => {
  114. let object = this.target;
  115. if (typeof property === "string" && property.includes(".")) {
  116. let propertyArray = [];
  117. while (property.includes(".")) {
  118. let index = property.indexOf(".");
  119. propertyArray.push(property.substring(0, index));
  120. if (index + 1 >= property.length) break;
  121. property = property.substring(index + 1, property.length);
  122. }
  123. propertyArray.push(property);
  124. property = propertyArray;
  125. }
  126. if (Array.isArray(property)) {
  127. for (let key of property) {
  128. this.__expect(
  129. object !== undefined && object !== null,
  130. "got undefined or null as array key"
  131. );
  132. object = object[key];
  133. }
  134. } else {
  135. object = object[property];
  136. }
  137. this.__expect(object !== undefined, "should not be undefined");
  138. if (value !== undefined)
  139. this.__expect(
  140. deepEquals(object, value),
  141. `value does not equal property ${valueToString(object)} vs ${valueToString(
  142. value
  143. )}`
  144. );
  145. });
  146. }
  147. toBeDefined() {
  148. this.__doMatcher(() => {
  149. this.__expect(
  150. this.target !== undefined,
  151. () => "toBeDefined: expected target to be defined, got undefined"
  152. );
  153. });
  154. }
  155. toBeInstanceOf(class_) {
  156. this.__doMatcher(() => {
  157. this.__expect(
  158. this.target instanceof class_,
  159. `Expected ${valueToString(this.target)} to be instance of ${class_.name}`
  160. );
  161. });
  162. }
  163. toBeNull() {
  164. this.__doMatcher(() => {
  165. this.__expect(
  166. this.target === null,
  167. `Expected target to be null got ${valueToString(this.target)}`
  168. );
  169. });
  170. }
  171. toBeUndefined() {
  172. this.__doMatcher(() => {
  173. this.__expect(
  174. this.target === undefined,
  175. () =>
  176. `toBeUndefined: expected target to be undefined, got _${valueToString(
  177. this.target
  178. )}_`
  179. );
  180. });
  181. }
  182. toBeNaN() {
  183. this.__doMatcher(() => {
  184. this.__expect(
  185. isNaN(this.target),
  186. () => `toBeNaN: expected target to be NaN, got _${valueToString(this.target)}_`
  187. );
  188. });
  189. }
  190. toBeTrue(customDetails = undefined) {
  191. this.__doMatcher(() => {
  192. this.__expect(
  193. this.target === true,
  194. () =>
  195. `toBeTrue: expected target to be true, got _${valueToString(this.target)}_${
  196. customDetails ? ` (${customDetails})` : ""
  197. }`
  198. );
  199. });
  200. }
  201. toBeFalse(customDetails = undefined) {
  202. this.__doMatcher(() => {
  203. this.__expect(
  204. this.target === false,
  205. () =>
  206. `toBeFalse: expected target to be false, got _${valueToString(
  207. this.target
  208. )}_${customDetails ?? ""}`
  209. );
  210. });
  211. }
  212. __validateNumericComparisonTypes(value) {
  213. this.__expect(typeof this.target === "number" || typeof this.target === "bigint");
  214. this.__expect(typeof value === "number" || typeof value === "bigint");
  215. this.__expect(typeof this.target === typeof value);
  216. }
  217. toBeLessThan(value) {
  218. this.__validateNumericComparisonTypes(value);
  219. this.__doMatcher(() => {
  220. this.__expect(this.target < value);
  221. });
  222. }
  223. toBeLessThanOrEqual(value) {
  224. this.__validateNumericComparisonTypes(value);
  225. this.__doMatcher(() => {
  226. this.__expect(this.target <= value);
  227. });
  228. }
  229. toBeGreaterThan(value) {
  230. this.__validateNumericComparisonTypes(value);
  231. this.__doMatcher(() => {
  232. this.__expect(this.target > value);
  233. });
  234. }
  235. toBeGreaterThanOrEqual(value) {
  236. this.__validateNumericComparisonTypes(value);
  237. this.__doMatcher(() => {
  238. this.__expect(this.target >= value);
  239. });
  240. }
  241. toContain(item) {
  242. this.__doMatcher(() => {
  243. for (let element of this.target) {
  244. if (item === element) return;
  245. }
  246. throw new ExpectationError();
  247. });
  248. }
  249. toContainEqual(item) {
  250. this.__doMatcher(() => {
  251. for (let element of this.target) {
  252. if (deepEquals(item, element)) return;
  253. }
  254. throw new ExpectationError();
  255. });
  256. }
  257. toEqual(value) {
  258. this.__doMatcher(() => {
  259. this.__expect(
  260. deepEquals(this.target, value),
  261. () =>
  262. `Expected _${valueToString(value)}_, but got _${valueToString(
  263. this.target
  264. )}_`
  265. );
  266. });
  267. }
  268. toThrow(value) {
  269. this.__expect(typeof this.target === "function");
  270. this.__expect(
  271. typeof value === "string" ||
  272. typeof value === "function" ||
  273. typeof value === "object" ||
  274. value === undefined
  275. );
  276. this.__doMatcher(() => {
  277. let threw = true;
  278. try {
  279. this.target();
  280. threw = false;
  281. } catch (e) {
  282. if (typeof value === "string") {
  283. this.__expect(
  284. e.message.includes(value),
  285. `Expected ${this.target.toString()} to throw and message to include "${value}" but message "${
  286. e.message
  287. }" did not contain it`
  288. );
  289. } else if (typeof value === "function") {
  290. this.__expect(
  291. e instanceof value,
  292. `Expected ${this.target.toString()} to throw and be of type ${value} but it threw ${e}`
  293. );
  294. } else if (typeof value === "object") {
  295. this.__expect(
  296. e.message === value.message,
  297. `Expected ${this.target.toString()} to throw and message to be ${value} but it threw with message ${
  298. e.message
  299. }`
  300. );
  301. }
  302. }
  303. this.__expect(
  304. threw,
  305. `Expected ${this.target.toString()} to throw but it didn't throw anything`
  306. );
  307. });
  308. }
  309. pass(message) {
  310. // FIXME: This does nothing. If we want to implement things
  311. // like assertion count, this will have to do something
  312. }
  313. // jest-extended
  314. fail(message) {
  315. this.__doMatcher(() => {
  316. this.__expect(false, message);
  317. });
  318. }
  319. // jest-extended
  320. toThrowWithMessage(class_, message) {
  321. this.__expect(typeof this.target === "function");
  322. this.__expect(class_ !== undefined);
  323. this.__expect(message !== undefined);
  324. this.__doMatcher(() => {
  325. try {
  326. this.target();
  327. this.__expect(false, () => "toThrowWithMessage: target function did not throw");
  328. } catch (e) {
  329. this.__expect(
  330. e instanceof class_,
  331. () =>
  332. `toThrowWithMessage: expected error to be instance of ${valueToString(
  333. class_.name
  334. )}, got ${valueToString(e.name)}`
  335. );
  336. this.__expect(
  337. e.message.includes(message),
  338. () =>
  339. `toThrowWithMessage: expected error message to include _${valueToString(
  340. message
  341. )}_, got _${valueToString(e.message)}_`
  342. );
  343. }
  344. });
  345. }
  346. // Test for syntax errors; target must be a string
  347. toEval() {
  348. this.__expect(typeof this.target === "string");
  349. const success = canParseSource(this.target);
  350. this.__expect(
  351. this.inverted ? !success : success,
  352. () =>
  353. `Expected _${valueToString(this.target)}_ ` +
  354. (this.inverted ? "not to eval but it did" : "to eval but it didn't")
  355. );
  356. }
  357. // Must compile regardless of inverted-ness
  358. toEvalTo(value) {
  359. this.__expect(typeof this.target === "string");
  360. let result;
  361. try {
  362. result = eval(this.target);
  363. } catch (e) {
  364. throw new ExpectationError(
  365. `Expected _${valueToString(this.target)}_ to eval but it failed with ${e}`
  366. );
  367. }
  368. this.__doMatcher(() => {
  369. this.__expect(
  370. deepEquals(value, result),
  371. () =>
  372. `Expected _${valueToString(this.target)}_ to eval to ` +
  373. `_${valueToString(value)}_ but got _${valueToString(result)}_`
  374. );
  375. });
  376. }
  377. toHaveConfigurableProperty(property) {
  378. this.__expect(this.target !== undefined && this.target !== null);
  379. let d = Object.getOwnPropertyDescriptor(this.target, property);
  380. this.__expect(d !== undefined);
  381. this.__doMatcher(() => {
  382. this.__expect(d.configurable);
  383. });
  384. }
  385. toHaveEnumerableProperty(property) {
  386. this.__expect(this.target !== undefined && this.target !== null);
  387. let d = Object.getOwnPropertyDescriptor(this.target, property);
  388. this.__expect(d !== undefined);
  389. this.__doMatcher(() => {
  390. this.__expect(d.enumerable);
  391. });
  392. }
  393. toHaveWritableProperty(property) {
  394. this.__expect(this.target !== undefined && this.target !== null);
  395. let d = Object.getOwnPropertyDescriptor(this.target, property);
  396. this.__expect(d !== undefined);
  397. this.__doMatcher(() => {
  398. this.__expect(d.writable);
  399. });
  400. }
  401. toHaveValueProperty(property, value) {
  402. this.__expect(this.target !== undefined && this.target !== null);
  403. let d = Object.getOwnPropertyDescriptor(this.target, property);
  404. this.__expect(d !== undefined);
  405. this.__doMatcher(() => {
  406. this.__expect(d.value !== undefined);
  407. if (value !== undefined) this.__expect(deepEquals(value, d.value));
  408. });
  409. }
  410. toHaveGetterProperty(property) {
  411. this.__expect(this.target !== undefined && this.target !== null);
  412. let d = Object.getOwnPropertyDescriptor(this.target, property);
  413. this.__expect(d !== undefined);
  414. this.__doMatcher(() => {
  415. this.__expect(d.get !== undefined);
  416. });
  417. }
  418. toHaveSetterProperty(property) {
  419. this.__expect(this.target !== undefined && this.target !== null);
  420. let d = Object.getOwnPropertyDescriptor(this.target, property);
  421. this.__expect(d !== undefined);
  422. this.__doMatcher(() => {
  423. this.__expect(d.set !== undefined);
  424. });
  425. }
  426. toBeIteratorResultWithValue(value) {
  427. this.__expect(this.target !== undefined && this.target !== null);
  428. this.__doMatcher(() => {
  429. this.__expect(
  430. this.target.done === false,
  431. () =>
  432. `toGiveIteratorResultWithValue: expected 'done' to be _false_ got ${valueToString(
  433. this.target.done
  434. )}`
  435. );
  436. this.__expect(
  437. deepEquals(value, this.target.value),
  438. () =>
  439. `toGiveIteratorResultWithValue: expected 'value' to be _${valueToString(
  440. value
  441. )}_ got ${valueToString(this.target.value)}`
  442. );
  443. });
  444. }
  445. toBeIteratorResultDone() {
  446. this.__expect(this.target !== undefined && this.target !== null);
  447. this.__doMatcher(() => {
  448. this.__expect(
  449. this.target.done === true,
  450. () =>
  451. `toGiveIteratorResultDone: expected 'done' to be _true_ got ${valueToString(
  452. this.target.done
  453. )}`
  454. );
  455. this.__expect(
  456. this.target.value === undefined,
  457. () =>
  458. `toGiveIteratorResultDone: expected 'value' to be _undefined_ got ${valueToString(
  459. this.target.value
  460. )}`
  461. );
  462. });
  463. }
  464. __doMatcher(matcher) {
  465. if (!this.inverted) {
  466. matcher();
  467. } else {
  468. let threw = false;
  469. try {
  470. matcher();
  471. } catch (e) {
  472. if (e.name === "ExpectationError") threw = true;
  473. }
  474. if (!threw) throw new ExpectationError("not: test didn't fail");
  475. }
  476. }
  477. __expect(value, details) {
  478. if (value !== true) {
  479. if (details !== undefined) {
  480. if (details instanceof Function) throw new ExpectationError(details());
  481. else throw new ExpectationError(details);
  482. } else {
  483. throw new ExpectationError();
  484. }
  485. }
  486. }
  487. }
  488. expect = value => new Expector(value);
  489. // describe is able to lump test results inside of it by using this context
  490. // variable. Top level tests have the default suite message
  491. const defaultSuiteMessage = "__$$TOP_LEVEL$$__";
  492. let suiteMessage = defaultSuiteMessage;
  493. describe = (message, callback) => {
  494. suiteMessage = message;
  495. if (!__TestResults__[suiteMessage]) __TestResults__[suiteMessage] = {};
  496. try {
  497. callback();
  498. } catch (e) {
  499. __TestResults__[suiteMessage][defaultSuiteMessage] = {
  500. result: "fail",
  501. details: String(e),
  502. duration: 0,
  503. };
  504. }
  505. suiteMessage = defaultSuiteMessage;
  506. };
  507. test = (message, callback) => {
  508. if (!__TestResults__[suiteMessage]) __TestResults__[suiteMessage] = {};
  509. const suite = __TestResults__[suiteMessage];
  510. if (Object.prototype.hasOwnProperty.call(suite, message)) {
  511. suite[message] = {
  512. result: "fail",
  513. details: "Another test with the same message did already run",
  514. duration: 0,
  515. };
  516. return;
  517. }
  518. const now = () => Temporal.Now.instant().epochNanoseconds;
  519. const start = now();
  520. const time_us = () => Number(BigInt.asIntN(53, (now() - start) / 1000n));
  521. try {
  522. callback();
  523. suite[message] = {
  524. result: "pass",
  525. duration: time_us(),
  526. };
  527. } catch (e) {
  528. suite[message] = {
  529. result: "fail",
  530. details: String(e),
  531. duration: time_us(),
  532. };
  533. }
  534. };
  535. test.skip = (message, callback) => {
  536. if (typeof callback !== "function")
  537. throw new Error("test.skip has invalid second argument (must be a function)");
  538. if (!__TestResults__[suiteMessage]) __TestResults__[suiteMessage] = {};
  539. const suite = __TestResults__[suiteMessage];
  540. if (Object.prototype.hasOwnProperty.call(suite, message)) {
  541. suite[message] = {
  542. result: "fail",
  543. details: "Another test with the same message did already run",
  544. duration: 0,
  545. };
  546. return;
  547. }
  548. suite[message] = {
  549. result: "skip",
  550. duration: 0,
  551. };
  552. };
  553. test.xfail = (message, callback) => {
  554. if (!__TestResults__[suiteMessage]) __TestResults__[suiteMessage] = {};
  555. const suite = __TestResults__[suiteMessage];
  556. if (Object.prototype.hasOwnProperty.call(suite, message)) {
  557. suite[message] = {
  558. result: "fail",
  559. details: "Another test with the same message did already run",
  560. duration: 0,
  561. };
  562. return;
  563. }
  564. const now = () => Temporal.Now.instant().epochNanoseconds;
  565. const start = now();
  566. const time_us = () => Number(BigInt.asIntN(53, (now() - start) / 1000n));
  567. try {
  568. callback();
  569. suite[message] = {
  570. result: "fail",
  571. details: "Expected test to fail, but it passed",
  572. duration: time_us(),
  573. };
  574. } catch (e) {
  575. suite[message] = {
  576. result: "xfail",
  577. duration: time_us(),
  578. };
  579. }
  580. };
  581. test.xfailIf = (condition, message, callback) => {
  582. condition ? test.xfail(message, callback) : test(message, callback);
  583. };
  584. withinSameSecond = callback => {
  585. let callbackDuration;
  586. for (let tries = 0; tries < 5; tries++) {
  587. const start = Temporal.Now.instant();
  588. const result = callback();
  589. const end = Temporal.Now.instant();
  590. if (start.epochSeconds !== end.epochSeconds) {
  591. callbackDuration = start.until(end);
  592. continue;
  593. }
  594. return result;
  595. }
  596. throw new ExpectationError(
  597. `Tried to execute callback '${callback}' 5 times within the same second but ` +
  598. `failed. Make sure the callback does as little work as possible (the last run ` +
  599. `took ${callbackDuration.total(
  600. "milliseconds"
  601. )} ms) and the machine is not overloaded. If you see this ` +
  602. `error appearing in the CI it is most likely a flaky failure!`
  603. );
  604. };
  605. })();