1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 16:19:12 -05:00

feat(cli/js/testing): Add TestDefinition::skip (#4351)

This commit is contained in:
Nayeem Rahman 2020-03-15 09:34:24 +00:00 committed by GitHub
parent a159165fe5
commit 64a35acd64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 104 additions and 105 deletions

View file

@ -17,6 +17,7 @@ declare namespace Deno {
export interface TestDefinition { export interface TestDefinition {
fn: TestFunction; fn: TestFunction;
name: string; name: string;
skip?: boolean;
} }
/** Register a test which will be run when `deno test` is used on the command /** Register a test which will be run when `deno test` is used on the command
@ -32,12 +33,16 @@ declare namespace Deno {
* when `Deno.runTests` is used */ * when `Deno.runTests` is used */
export function test(name: string, fn: TestFunction): void; export function test(name: string, fn: TestFunction): void;
enum TestStatus {
Passed = "passed",
Failed = "failed",
Skipped = "skipped"
}
interface TestResult { interface TestResult {
passed: boolean;
name: string; name: string;
skipped: boolean; status: TestStatus;
hasRun: boolean; duration?: number;
duration: number;
error?: Error; error?: Error;
} }

View file

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

View file

@ -11,8 +11,8 @@ unitTest(function nameOfTestCaseCantBeEmpty(): void {
() => { () => {
Deno.test("", () => {}); Deno.test("", () => {});
}, },
Error, TypeError,
"The name of test case can't be empty" "The test name can't be empty"
); );
assertThrows( assertThrows(
() => { () => {
@ -21,8 +21,8 @@ unitTest(function nameOfTestCaseCantBeEmpty(): void {
fn: () => {} fn: () => {}
}); });
}, },
Error, TypeError,
"The name of test case can't be empty" "The test name can't be empty"
); );
}); });
@ -31,7 +31,7 @@ unitTest(function testFnCantBeAnonymous(): void {
() => { () => {
Deno.test(function() {}); Deno.test(function() {});
}, },
Error, TypeError,
"Test function can't be anonymous" "The test function can't be anonymous"
); );
}); });

View file

@ -13,4 +13,3 @@ OK bundleApiJsModules [WILDCARD]
OK diagnosticsTest [WILDCARD] OK diagnosticsTest [WILDCARD]
test result: OK 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD] test result: OK 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]