diff options
Diffstat (limited to 'cli/js/testing.ts')
-rw-r--r-- | cli/js/testing.ts | 131 |
1 files changed, 63 insertions, 68 deletions
diff --git a/cli/js/testing.ts b/cli/js/testing.ts index a2944aff4..a4523d2b7 100644 --- a/cli/js/testing.ts +++ b/cli/js/testing.ts @@ -1,10 +1,11 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { red, green, bgRed, gray, italic } from "./colors.ts"; +import { bgRed, gray, green, italic, red, yellow } from "./colors.ts"; import { exit } from "./ops/os.ts"; import { Console } from "./web/console.ts"; const RED_FAILED = red("FAILED"); const GREEN_OK = green("OK"); +const YELLOW_SKIPPED = yellow("SKIPPED"); const RED_BG_FAIL = bgRed(" FAIL "); const disabledConsole = new Console((_x: string, _isErr?: boolean): void => {}); @@ -18,6 +19,7 @@ export type TestFunction = () => void | Promise<void>; export interface TestDefinition { fn: TestFunction; name: string; + skip?: boolean; } const TEST_REGISTRY: TestDefinition[] = []; @@ -31,34 +33,32 @@ export function test( t: string | TestDefinition | TestFunction, fn?: TestFunction ): void { - let name: string; + let testDef: TestDefinition; if (typeof t === "string") { - if (!fn) { - throw new Error("Missing test function"); + if (!fn || typeof fn != "function") { + throw new TypeError("Missing test function"); } - name = t; - if (!name) { - throw new Error("The name of test case can't be empty"); + if (!t) { + throw new TypeError("The test name can't be empty"); } + testDef = { fn: fn as TestFunction, name: t, skip: false }; } else if (typeof t === "function") { - fn = t; - name = t.name; - if (!name) { - throw new Error("Test function can't be anonymous"); + if (!t.name) { + throw new TypeError("The test function can't be anonymous"); } + testDef = { fn: t, name: t.name, skip: false }; } else { - fn = t.fn; - if (!fn) { - throw new Error("Missing test function"); + if (!t.fn) { + throw new TypeError("Missing test function"); } - name = t.name; - if (!name) { - throw new Error("The name of test case can't be empty"); + if (!t.name) { + throw new TypeError("The test name can't be empty"); } + testDef = { fn: t.fn, name: t.name, skip: Boolean(t.skip) }; } - TEST_REGISTRY.push({ fn, name }); + TEST_REGISTRY.push(testDef); } interface TestStats { @@ -78,20 +78,19 @@ export interface RunTestsOptions { reporter?: TestReporter; } +enum TestStatus { + Passed = "passed", + Failed = "failed", + Skipped = "skipped" +} + interface TestResult { - passed: boolean; name: string; - skipped: boolean; - hasRun: boolean; - duration: number; + status: TestStatus; + duration?: number; error?: Error; } -interface TestCase { - result: TestResult; - fn: TestFunction; -} - export enum TestEvent { Start = "start", Result = "result", @@ -115,24 +114,10 @@ interface TestEventEnd { results: TestResult[]; } -function testDefinitionToTestCase(def: TestDefinition): TestCase { - return { - fn: def.fn, - result: { - name: def.name, - passed: false, - skipped: false, - hasRun: false, - duration: 0 - } - }; -} - // TODO: already implements AsyncGenerator<RunTestsMessage>, but add as "implements to class" // TODO: implements PromiseLike<TestsResult> class TestApi { readonly testsToRun: TestDefinition[]; - readonly testCases: TestCase[]; readonly stats: TestStats = { filtered: 0, ignored: 0, @@ -148,7 +133,6 @@ class TestApi { ) { this.testsToRun = tests.filter(filterFn); this.stats.filtered = tests.length - this.testsToRun.length; - this.testCases = this.testsToRun.map(testDefinitionToTestCase); } async *[Symbol.asyncIterator](): AsyncIterator< @@ -159,32 +143,35 @@ class TestApi { tests: this.testsToRun.length }; + const results: TestResult[] = []; const suiteStart = +new Date(); - for (const testCase of this.testCases) { - const { fn, result } = testCase; - let shouldBreak = false; - try { + for (const { name, fn, skip } of this.testsToRun) { + const result: Partial<TestResult> = { name }; + if (skip) { + result.status = TestStatus.Skipped; + this.stats.ignored++; + } else { const start = +new Date(); - await fn(); - result.duration = +new Date() - start; - result.passed = true; - this.stats.passed++; - } catch (err) { - result.passed = false; - result.error = err; - this.stats.failed++; - shouldBreak = this.failFast; - } finally { - result.hasRun = true; - yield { kind: TestEvent.Result, result }; - if (shouldBreak) { - break; + try { + await fn(); + result.duration = +new Date() - start; + result.status = TestStatus.Passed; + this.stats.passed++; + } catch (err) { + result.duration = +new Date() - start; + result.status = TestStatus.Failed; + result.error = err; + this.stats.failed++; } } + yield { kind: TestEvent.Result, result: result as TestResult }; + results.push(result as TestResult); + if (this.failFast && result.error != null) { + break; + } } const duration = +new Date() - suiteStart; - const results = this.testCases.map(r => r.result); yield { kind: TestEvent.End, @@ -241,13 +228,21 @@ export class ConsoleTestReporter implements TestReporter { async result(event: TestEventResult): Promise<void> { const { result } = event; - if (result.passed) { - this.console.log( - `${GREEN_OK} ${result.name} ${formatDuration(result.duration)}` - ); - } else { - this.console.log(`${RED_FAILED} ${result.name}`); - this.console.log(result.error!); + switch (result.status) { + case TestStatus.Passed: + this.console.log( + `${GREEN_OK} ${result.name} ${formatDuration(result.duration!)}` + ); + break; + case TestStatus.Failed: + this.console.log( + `${RED_FAILED} ${result.name} ${formatDuration(result.duration!)}` + ); + this.console.log(result.error!); + break; + case TestStatus.Skipped: + this.console.log(`${YELLOW_SKIPPED} ${result.name}`); + break; } } |