diff options
Diffstat (limited to 'cli/rt/40_testing.js')
-rw-r--r-- | cli/rt/40_testing.js | 350 |
1 files changed, 0 insertions, 350 deletions
diff --git a/cli/rt/40_testing.js b/cli/rt/40_testing.js deleted file mode 100644 index 082d17fe0..000000000 --- a/cli/rt/40_testing.js +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -((window) => { - const core = window.Deno.core; - const colors = window.__bootstrap.colors; - const { exit } = window.__bootstrap.os; - const { Console, inspectArgs } = window.__bootstrap.console; - const { stdout } = window.__bootstrap.files; - const { exposeForTest } = window.__bootstrap.internals; - const { metrics } = window.__bootstrap.metrics; - const { assert } = window.__bootstrap.util; - - const disabledConsole = new Console(() => {}); - - function delay(ms) { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - } - - function formatDuration(time = 0) { - const gray = colors.maybeColor(colors.gray); - const italic = colors.maybeColor(colors.italic); - const timeStr = `(${time}ms)`; - return gray(italic(timeStr)); - } - - // Wrap test function in additional assertion that makes sure - // the test case does not leak async "ops" - ie. number of async - // completed ops after the test is the same as number of dispatched - // ops. Note that "unref" ops are ignored since in nature that are - // optional. - function assertOps(fn) { - return async function asyncOpSanitizer() { - const pre = metrics(); - await fn(); - // Defer until next event loop turn - that way timeouts and intervals - // cleared can actually be removed from resource table, otherwise - // false positives may occur (https://github.com/denoland/deno/issues/4591) - await delay(0); - const post = metrics(); - // We're checking diff because one might spawn HTTP server in the background - // that will be a pending async op before test starts. - const dispatchedDiff = post.opsDispatchedAsync - pre.opsDispatchedAsync; - const completedDiff = post.opsCompletedAsync - pre.opsCompletedAsync; - assert( - dispatchedDiff === completedDiff, - `Test case is leaking async ops. -Before: - - dispatched: ${pre.opsDispatchedAsync} - - completed: ${pre.opsCompletedAsync} -After: - - dispatched: ${post.opsDispatchedAsync} - - completed: ${post.opsCompletedAsync} - -Make sure to await all promises returned from Deno APIs before -finishing test case.`, - ); - }; - } - - // Wrap test function in additional assertion that makes sure - // the test case does not "leak" resources - ie. resource table after - // the test has exactly the same contents as before the test. - function assertResources( - fn, - ) { - return async function resourceSanitizer() { - const pre = core.resources(); - await fn(); - const post = core.resources(); - - const preStr = JSON.stringify(pre, null, 2); - const postStr = JSON.stringify(post, null, 2); - const msg = `Test case is leaking resources. -Before: ${preStr} -After: ${postStr} - -Make sure to close all open resource handles returned from Deno APIs before -finishing test case.`; - assert(preStr === postStr, msg); - }; - } - - const TEST_REGISTRY = []; - - // Main test function provided by Deno, as you can see it merely - // creates a new object with "name" and "fn" fields. - function test( - t, - fn, - ) { - let testDef; - const defaults = { - ignore: false, - only: false, - sanitizeOps: true, - sanitizeResources: true, - }; - - if (typeof t === "string") { - if (!fn || typeof fn != "function") { - throw new TypeError("Missing test function"); - } - if (!t) { - throw new TypeError("The test name can't be empty"); - } - testDef = { fn: fn, name: t, ...defaults }; - } else { - if (!t.fn) { - throw new TypeError("Missing test function"); - } - if (!t.name) { - throw new TypeError("The test name can't be empty"); - } - testDef = { ...defaults, ...t }; - } - - if (testDef.sanitizeOps) { - testDef.fn = assertOps(testDef.fn); - } - - if (testDef.sanitizeResources) { - testDef.fn = assertResources(testDef.fn); - } - - TEST_REGISTRY.push(testDef); - } - - const encoder = new TextEncoder(); - - function log(msg, noNewLine = false) { - if (!noNewLine) { - msg += "\n"; - } - - // Using `stdout` here because it doesn't force new lines - // compared to `console.log`; `core.print` on the other hand - // is line-buffered and doesn't output message without newline - stdout.writeSync(encoder.encode(msg)); - } - - function reportToConsole(message) { - const green = colors.maybeColor(colors.green); - const red = colors.maybeColor(colors.red); - const yellow = colors.maybeColor(colors.yellow); - const redFailed = red("FAILED"); - const greenOk = green("ok"); - const yellowIgnored = yellow("ignored"); - if (message.start != null) { - log(`running ${message.start.tests.length} tests`); - } else if (message.testStart != null) { - const { name } = message.testStart; - - log(`test ${name} ... `, true); - return; - } else if (message.testEnd != null) { - switch (message.testEnd.status) { - case "passed": - log(`${greenOk} ${formatDuration(message.testEnd.duration)}`); - break; - case "failed": - log(`${redFailed} ${formatDuration(message.testEnd.duration)}`); - break; - case "ignored": - log(`${yellowIgnored} ${formatDuration(message.testEnd.duration)}`); - break; - } - } else if (message.end != null) { - const failures = message.end.results.filter((m) => m.error != null); - if (failures.length > 0) { - log(`\nfailures:\n`); - - for (const { name, error } of failures) { - log(name); - log(inspectArgs([error])); - log(""); - } - - log(`failures:\n`); - - for (const { name } of failures) { - log(`\t${name}`); - } - } - log( - `\ntest result: ${message.end.failed ? redFailed : greenOk}. ` + - `${message.end.passed} passed; ${message.end.failed} failed; ` + - `${message.end.ignored} ignored; ${message.end.measured} measured; ` + - `${message.end.filtered} filtered out ` + - `${formatDuration(message.end.duration)}\n`, - ); - - if (message.end.usedOnly && message.end.failed == 0) { - log(`${redFailed} because the "only" option was used\n`); - } - } - } - - exposeForTest("reportToConsole", reportToConsole); - - // TODO: already implements AsyncGenerator<RunTestsMessage>, but add as "implements to class" - // TODO: implements PromiseLike<RunTestsEndResult> - class TestRunner { - #usedOnly = false; - - constructor( - tests, - filterFn, - failFast, - ) { - this.stats = { - filtered: 0, - ignored: 0, - measured: 0, - passed: 0, - failed: 0, - }; - this.filterFn = filterFn; - this.failFast = failFast; - const onlyTests = tests.filter(({ only }) => only); - this.#usedOnly = onlyTests.length > 0; - const unfilteredTests = this.#usedOnly ? onlyTests : tests; - this.testsToRun = unfilteredTests.filter(filterFn); - this.stats.filtered = unfilteredTests.length - this.testsToRun.length; - } - - async *[Symbol.asyncIterator]() { - yield { start: { tests: this.testsToRun } }; - - const results = []; - const suiteStart = +new Date(); - for (const test of this.testsToRun) { - const endMessage = { - name: test.name, - duration: 0, - }; - yield { testStart: { ...test } }; - if (test.ignore) { - endMessage.status = "ignored"; - this.stats.ignored++; - } else { - const start = +new Date(); - try { - await test.fn(); - endMessage.status = "passed"; - this.stats.passed++; - } catch (err) { - endMessage.status = "failed"; - endMessage.error = err; - this.stats.failed++; - } - endMessage.duration = +new Date() - start; - } - results.push(endMessage); - yield { testEnd: endMessage }; - if (this.failFast && endMessage.error != null) { - break; - } - } - - const duration = +new Date() - suiteStart; - - yield { - end: { ...this.stats, usedOnly: this.#usedOnly, duration, results }, - }; - } - } - - function createFilterFn( - filter, - skip, - ) { - return (def) => { - let passes = true; - - if (filter) { - if (filter instanceof RegExp) { - passes = passes && filter.test(def.name); - } else if (filter.startsWith("/") && filter.endsWith("/")) { - const filterAsRegex = new RegExp(filter.slice(1, filter.length - 1)); - passes = passes && filterAsRegex.test(def.name); - } else { - passes = passes && def.name.includes(filter); - } - } - - if (skip) { - if (skip instanceof RegExp) { - passes = passes && !skip.test(def.name); - } else { - passes = passes && !def.name.includes(skip); - } - } - - return passes; - }; - } - - exposeForTest("createFilterFn", createFilterFn); - - async function runTests({ - exitOnFail = true, - failFast = false, - filter = undefined, - skip = undefined, - disableLog = false, - reportToConsole: reportToConsole_ = true, - onMessage = undefined, - } = {}) { - const filterFn = createFilterFn(filter, skip); - const testRunner = new TestRunner(TEST_REGISTRY, filterFn, failFast); - - const originalConsole = globalThis.console; - - if (disableLog) { - globalThis.console = disabledConsole; - } - - let endMsg; - - for await (const message of testRunner) { - if (onMessage != null) { - await onMessage(message); - } - if (reportToConsole_) { - reportToConsole(message); - } - if (message.end != null) { - endMsg = message.end; - } - } - - if (disableLog) { - globalThis.console = originalConsole; - } - - if ((endMsg.failed > 0 || endMsg?.usedOnly) && exitOnFail) { - exit(1); - } - - return endMsg; - } - - exposeForTest("runTests", runTests); - - window.__bootstrap.testing = { - test, - }; -})(this); |