Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 8 commits into from
Mar 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions cli/js/lib.deno.ns.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ declare namespace Deno {
export interface TestDefinition {
fn: TestFunction;
name: string;
skip?: boolean;
}

/** Register a test which will be run when `deno test` is used on the command
Expand All @@ -32,12 +33,16 @@ declare namespace Deno {
* when `Deno.runTests` is used */
export function test(name: string, fn: TestFunction): void;

enum TestStatus {
Passed = "passed",
Failed = "failed",
Skipped = "skipped"
}
Comment on lines +36 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably collapse all of little interfaces and enums for testing under a single namespace...

Copy link
Collaborator Author

@nayeemrmn nayeemrmn Mar 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realised these are all implicitly public via TestReporter in RunTestsOptions so that would be necessary. The TestEvent* stuff can and should be hidden IMO with some restructuring. All that needs to be public is TestReporter, TestResult, TestStatus and TestStats each with no references to any other proprietary type.

EDIT: Actually TestEvent{Start,Result,End} are necessary to keep TestReporter simple and extensible, but not TestEvent.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this?

namespace tests {
  export interface Result {
    name: string;
    status: "passed" | "failed" | "skipped";
    duration?: number;
    error?: Error;
  }

  export interface Stats {
    filtered: number;
    ignored: number;
    measured: number;
    passed: number;
    failed: number;
  }

  export interface StartEvent {
    tests: Deno.TestDefinition[];
  }

  export interface ResultEvent {
    result: Result;
  }

  export interface EndEvent {
    stats: Stats;
    duration: number;
    results: Result[];
  }

  export interface Reporter {
    start(msg: StartEvent): Promise<void>;
    result(msg: ResultEvent): Promise<void>;
    end(msg: EndEvent): Promise<void>;
  }

  export class ConsoleReporter implements Reporter {
    // ...
  }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nayeemrmn I already needed to split "result" into two separate messages for #4371
We should figure this out before 1.0, but it's not actionable for this PR


interface TestResult {
passed: boolean;
name: string;
skipped: boolean;
hasRun: boolean;
duration: number;
status: TestStatus;
duration?: number;
nayeemrmn marked this conversation as resolved.
Show resolved Hide resolved
nayeemrmn marked this conversation as resolved.
Show resolved Hide resolved
error?: Error;
}

Expand Down
131 changes: 63 additions & 68 deletions cli/js/testing.ts
Original file line number Diff line number Diff line change
@@ -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 => {});

Expand All @@ -18,6 +19,7 @@ export type TestFunction = () => void | Promise<void>;
export interface TestDefinition {
fn: TestFunction;
name: string;
skip?: boolean;
}

const TEST_REGISTRY: TestDefinition[] = [];
Expand All @@ -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 {
Expand All @@ -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",
Expand All @@ -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,
Expand All @@ -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<
Expand All @@ -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;
nayeemrmn marked this conversation as resolved.
Show resolved Hide resolved
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,
Expand Down Expand Up @@ -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;
}
}

Expand Down
12 changes: 6 additions & 6 deletions cli/js/tests/testing_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ unitTest(function nameOfTestCaseCantBeEmpty(): void {
() => {
Deno.test("", () => {});
},
Error,
"The name of test case can't be empty"
TypeError,
"The test name can't be empty"
);
assertThrows(
() => {
Expand All @@ -21,8 +21,8 @@ unitTest(function nameOfTestCaseCantBeEmpty(): void {
fn: () => {}
});
},
Error,
"The name of test case can't be empty"
TypeError,
"The test name can't be empty"
);
});

Expand All @@ -31,7 +31,7 @@ unitTest(function testFnCantBeAnonymous(): void {
() => {
Deno.test(function() {});
},
Error,
"Test function can't be anonymous"
TypeError,
"The test function can't be anonymous"
);
});
16 changes: 8 additions & 8 deletions cli/tests/057_revoke_permissions.out
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
running 7 tests
OK runGranted [WILDCARD]
OK readGranted [WILDCARD]
OK writeGranted [WILDCARD]
OK netGranted [WILDCARD]
OK envGranted [WILDCARD]
OK pluginGranted [WILDCARD]
OK hrtimeGranted [WILDCARD]
OK runGranted [WILDCARD]
OK readGranted [WILDCARD]
OK writeGranted [WILDCARD]
OK netGranted [WILDCARD]
OK envGranted [WILDCARD]
OK pluginGranted [WILDCARD]
OK hrtimeGranted [WILDCARD]

test result: OK 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]
test result: OK 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]
25 changes: 12 additions & 13 deletions cli/tests/compiler_api_test.out
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
running 12 tests
OK compilerApiCompileSources [WILDCARD]
OK compilerApiCompileNoSources [WILDCARD]
OK compilerApiCompileOptions [WILDCARD]
OK compilerApiCompileLib [WILDCARD]
OK compilerApiCompileTypes [WILDCARD]
OK transpileOnlyApi [WILDCARD]
OK transpileOnlyApiConfig [WILDCARD]
OK bundleApiSources [WILDCARD]
OK bundleApiNoSources [WILDCARD]
OK bundleApiConfig [WILDCARD]
OK bundleApiJsModules [WILDCARD]
OK diagnosticsTest [WILDCARD]
OK compilerApiCompileSources [WILDCARD]
OK compilerApiCompileNoSources [WILDCARD]
OK compilerApiCompileOptions [WILDCARD]
OK compilerApiCompileLib [WILDCARD]
OK compilerApiCompileTypes [WILDCARD]
OK transpileOnlyApi [WILDCARD]
OK transpileOnlyApiConfig [WILDCARD]
OK bundleApiSources [WILDCARD]
OK bundleApiNoSources [WILDCARD]
OK bundleApiConfig [WILDCARD]
OK bundleApiJsModules [WILDCARD]
OK diagnosticsTest [WILDCARD]

test result: OK 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]

10 changes: 5 additions & 5 deletions cli/tests/workers_test.out
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
running 4 tests
OK workersBasic [WILDCARD]
OK nestedWorker [WILDCARD]
OK workerThrowsWhenExecuting [WILDCARD]
OK workerCanUseFetch [WILDCARD]
OK workersBasic [WILDCARD]
OK nestedWorker [WILDCARD]
OK workerThrowsWhenExecuting [WILDCARD]
OK workerCanUseFetch [WILDCARD]

test result: OK 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]
test result: OK 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]