diff options
| author | chiefbiiko <noah.anabiik.schwarz@gmail.com> | 2019-03-04 20:19:03 +0100 |
|---|---|---|
| committer | Ryan Dahl <ry@tinyclouds.org> | 2019-03-04 14:19:03 -0500 |
| commit | ce7a987009aed294d336dc420b72743ecd51db89 (patch) | |
| tree | 88bbe6b54b162f725b985c957537e3a13a42c555 /testing/mod.ts | |
| parent | 2bbde0c226d220307e46cc74e414320ac74aeddd (diff) | |
feat: parallel testing (denoland/deno_std#224)
Original: https://github.com/denoland/deno_std/commit/41bdd096f0b300056c58a04392d109bf11c1ce8e
Diffstat (limited to 'testing/mod.ts')
| -rw-r--r-- | testing/mod.ts | 193 |
1 files changed, 167 insertions, 26 deletions
diff --git a/testing/mod.ts b/testing/mod.ts index 30f2a0c98..41ae61bea 100644 --- a/testing/mod.ts +++ b/testing/mod.ts @@ -241,55 +241,196 @@ function green_ok() { return green("ok"); } -export 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(); +interface TestStats { + filtered: number; + ignored: number; + measured: number; + passed: number; + failed: number; +} + +interface TestResult { + name: string; + error: Error; + ok: boolean; + printed: boolean; +} + +interface TestResults { + keys: Map<string, number>; + cases: Map<number, TestResult>; +} + +function createTestResults(tests: Array<TestDefinition>): TestResults { + return tests.reduce( + (acc: TestResults, { name }: TestDefinition, i: number): TestResults => { + acc.keys.set(name, i); + acc.cases.set(i, { name, printed: false, ok: false, error: null }); + return acc; + }, + { cases: new Map(), keys: new Map() } + ); +} + +function report(result: TestResult): void { + if (result.ok) { + console.log(`test ${result.name} ... ${green_ok()}`); + } else if (result.error) { + console.error( + `test ${result.name} ... ${red_failed()}\n${result.error.stack}` + ); + } else { + console.log(`test ${result.name} ... unresolved`); + } + result.printed = true; +} + +function printResults( + stats: TestStats, + results: TestResults, + flush: boolean +): void { + if (flush) { + for (const result of results.cases.values()) { + if (!result.printed) { + report(result); + if (result.error && exitOnFail) { + break; + } + } + } + } + // Attempting to match the output of Rust's test runner. + console.log( + `\ntest result: ${stats.failed ? red_failed() : green_ok()}. ` + + `${stats.passed} passed; ${stats.failed} failed; ` + + `${stats.ignored} ignored; ${stats.measured} measured; ` + + `${stats.filtered} filtered out\n` + ); +} + +function previousPrinted(name: string, results: TestResults): boolean { + const curIndex: number = results.keys.get(name); + if (curIndex === 0) { + return true; + } + return results.cases.get(curIndex - 1).printed; +} + +async function createTestCase( + stats: TestStats, + results: TestResults, + { fn, name }: TestDefinition +): Promise<void> { + const result: TestResult = results.cases.get(results.keys.get(name)); + try { + await fn(); + stats.passed++; + result.ok = true; + } catch (err) { + stats.failed++; + result.error = err; + if (exitOnFail) { + throw err; + } + } + if (previousPrinted(name, results)) { + report(result); + } +} + +function initTestCases( + stats: TestStats, + results: TestResults, + tests: Array<TestDefinition> +): Array<Promise<void>> { + return tests.map(createTestCase.bind(null, stats, results)); +} + +async function runTestsParallel( + stats: TestStats, + results: TestResults, + tests: Array<TestDefinition> +): Promise<void> { + try { + await Promise.all(initTestCases(stats, results, tests)); + } catch (_) { + // The error was thrown to stop awaiting all promises if exitOnFail === true + // stats.failed has been incremented and the error stored in results + } +} + +async function runTestsSerial( + stats: TestStats, + tests: Array<TestDefinition> +): Promise<void> { + for (const { fn, name } of tests) { // See https://github.com/denoland/deno/pull/1452 // about this usage of groupCollapsed console.groupCollapsed(`test ${name} `); try { await fn(); - passed++; - console.log("...", result); + stats.passed++; + console.log("...", green_ok()); console.groupEnd(); - } catch (e) { - result = red_failed(); - console.log("...", result); + } catch (err) { + console.log("...", red_failed()); console.groupEnd(); - console.error(e); - failed++; + console.error(err.stack); + stats.failed++; if (exitOnFail) { break; } } } +} - // 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` - ); +/** Defines options for controlling execution details of a test suite. */ +export interface RunOptions { + parallel?: boolean; +} - if (failed === 0) { - // All good. +/** + * Runs specified test cases. + * Parallel execution can be enabled via the boolean option; default: serial. + */ +export async function runTests({ parallel = false }: RunOptions = {}): Promise< + void +> { + const stats: TestStats = { + measured: 0, + ignored: 0, + filtered: filtered, + passed: 0, + failed: 0 + }; + const results: TestResults = createTestResults(tests); + console.log(`running ${tests.length} tests`); + if (parallel) { + await runTestsParallel(stats, results, tests); } else { + await runTestsSerial(stats, tests); + } + printResults(stats, results, parallel); + if (stats.failed) { // Use setTimeout to avoid the error being ignored due to unhandled // promise rejections being swallowed. setTimeout(() => { - console.error(`There were ${failed} test failures.`); + console.error(`There were ${stats.failed} test failures.`); Deno.exit(1); }, 0); } } -export async function runIfMain(meta: ImportMeta) { +/** + * Runs specified test cases if the enclosing script is main. + * Execution mode is toggleable via opts.parallel, defaults to false. + */ +export async function runIfMain( + meta: ImportMeta, + opts?: RunOptions +): Promise<void> { if (meta.main) { - runTests(); + return runTests(opts); } } |
