diff options
Diffstat (limited to 'testing')
| -rw-r--r-- | testing/mod.ts | 162 | ||||
| -rw-r--r-- | testing/test.ts | 57 |
2 files changed, 219 insertions, 0 deletions
diff --git a/testing/mod.ts b/testing/mod.ts new file mode 100644 index 000000000..f33e743fc --- /dev/null +++ b/testing/mod.ts @@ -0,0 +1,162 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +// Do not add imports in this file in order to be compatible with Node. + +export function assertEqual(actual: unknown, expected: unknown, msg?: string) { + if (!equal(actual, expected)) { + let actualString: string; + let expectedString: string; + try { + actualString = String(actual); + } catch (e) { + actualString = "[Cannot display]"; + } + try { + expectedString = String(expected); + } catch (e) { + expectedString = "[Cannot display]"; + } + console.error( + "assertEqual failed. actual =", + actualString, + "expected =", + expectedString + ); + if (!msg) { + msg = `actual: ${actualString} expected: ${expectedString}`; + } + throw new Error(msg); + } +} + +export function assert(expr: boolean, msg = "") { + if (!expr) { + throw new Error(msg); + } +} + +// TODO(ry) Use unknown here for parameters types. +// tslint:disable-next-line:no-any +export function equal(c: any, d: any): boolean { + const seen = new Map(); + return (function compare(a, b) { + if (Object.is(a, b)) { + return true; + } + if (a && typeof a === "object" && b && typeof b === "object") { + if (seen.get(a) === b) { + return true; + } + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + for (const key in { ...a, ...b }) { + if (!compare(a[key], b[key])) { + return false; + } + } + seen.set(a, b); + return true; + } + return false; + })(c, d); +} + +export type TestFunction = () => void | Promise<void>; + +export interface TestDefinition { + fn: TestFunction; + name: string; +} + +export const exitOnFail = true; + +let filterRegExp: RegExp | null; +const tests: TestDefinition[] = []; + +let filtered = 0; +const ignored = 0; +const measured = 0; + +// Must be called before any test() that needs to be filtered. +export function setFilter(s: string): void { + filterRegExp = new RegExp(s, "i"); +} + +export function test(t: TestDefinition | TestFunction): void { + const fn: TestFunction = typeof t === "function" ? t : t.fn; + const name: string = t.name; + + if (!name) { + throw new Error("Test function may not be anonymous"); + } + if (filter(name)) { + tests.push({ fn, name }); + } else { + filtered++; + } +} + +function filter(name: string): boolean { + if (filterRegExp) { + return filterRegExp.test(name); + } else { + return true; + } +} + +const RESET = "\x1b[0m"; +const FG_RED = "\x1b[31m"; +const FG_GREEN = "\x1b[32m"; + +function red_failed() { + return FG_RED + "FAILED" + RESET; +} + +function green_ok() { + return FG_GREEN + "ok" + RESET; +} + +async function runTests() { + let passed = 0; + let failed = 0; + + console.log("running", tests.length, "tests"); + for (let i = 0; i < tests.length; i++) { + const { fn, name } = tests[i]; + let result = green_ok(); + console.log("test", name); + try { + await fn(); + passed++; + } catch (e) { + result = red_failed(); + console.error((e && e.stack) || e); + failed++; + if (exitOnFail) { + break; + } + } + // TODO Do this on the same line as test name is printed. + console.log("...", result); + } + + // Attempting to match the output of Rust's test runner. + const result = failed > 0 ? red_failed() : green_ok(); + console.log( + `\ntest result: ${result}. ${passed} passed; ${failed} failed; ` + + `${ignored} ignored; ${measured} measured; ${filtered} filtered out\n` + ); + + if (failed === 0) { + // All good. + } else { + // Use setTimeout to avoid the error being ignored due to unhandled + // promise rejections being swallowed. + setTimeout(() => { + throw new Error(`There were ${failed} test failures.`); + }, 0); + } +} + +setTimeout(runTests, 0); diff --git a/testing/test.ts b/testing/test.ts new file mode 100644 index 000000000..7012a6e47 --- /dev/null +++ b/testing/test.ts @@ -0,0 +1,57 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +import { test, assert, assertEqual, equal } from "./mod.ts"; + +test(function testingEqual() { + assert(equal("world", "world")); + assert(!equal("hello", "world")); + assert(equal(5, 5)); + assert(!equal(5, 6)); + assert(equal(NaN, NaN)); + assert(equal({ hello: "world" }, { hello: "world" })); + assert(!equal({ world: "hello" }, { hello: "world" })); + assert( + equal( + { hello: "world", hi: { there: "everyone" } }, + { hello: "world", hi: { there: "everyone" } } + ) + ); + assert( + !equal( + { hello: "world", hi: { there: "everyone" } }, + { hello: "world", hi: { there: "everyone else" } } + ) + ); +}); + +test(function testingAssertEqual() { + const a = Object.create(null); + a.b = "foo"; + assertEqual(a, a); +}); + +test(function testingAssertEqualActualUncoercable() { + let didThrow = false; + const a = Object.create(null); + try { + assertEqual(a, "bar"); + } catch (e) { + didThrow = true; + console.log(e.message); + assert(e.message === "actual: [Cannot display] expected: bar"); + } + assert(didThrow); +}); + +test(function testingAssertEqualExpectedUncoercable() { + let didThrow = false; + const a = Object.create(null); + try { + assertEqual("bar", a); + } catch (e) { + didThrow = true; + console.log(e.message); + assert(e.message === "actual: bar expected: [Cannot display]"); + } + assert(didThrow); +}); |
