feat: Add TestDefinition::only (#5793)

This commit is contained in:
Nayeem Rahman 2020-06-12 16:58:04 +01:00 committed by GitHub
parent 3eee961473
commit e613bfe47a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 15 deletions

View file

@ -38,6 +38,9 @@ declare namespace Deno {
fn: () => void | Promise<void>; fn: () => void | Promise<void>;
name: string; name: string;
ignore?: boolean; ignore?: boolean;
/** If at lease one test has `only` set to true, only run tests that have
* `only` set to true and fail the test suite. */
only?: boolean;
/** Check that the number of async completed ops after the test is the same /** Check that the number of async completed ops after the test is the same
* as number of dispatched ops. Defaults to true.*/ * as number of dispatched ops. Defaults to true.*/
sanitizeOps?: boolean; sanitizeOps?: boolean;

View file

@ -52,8 +52,8 @@ Before:
After: After:
- dispatched: ${post.opsDispatchedAsync} - dispatched: ${post.opsDispatchedAsync}
- completed: ${post.opsCompletedAsync} - completed: ${post.opsCompletedAsync}
Make sure to await all promises returned from Deno APIs before Make sure to await all promises returned from Deno APIs before
finishing test case.` finishing test case.`
); );
}; };
@ -76,7 +76,7 @@ function assertResources(
Before: ${preStr} Before: ${preStr}
After: ${postStr} After: ${postStr}
Make sure to close all open resource handles returned from Deno APIs before Make sure to close all open resource handles returned from Deno APIs before
finishing test case.`; finishing test case.`;
assert(preStr === postStr, msg); assert(preStr === postStr, msg);
}; };
@ -86,6 +86,7 @@ export interface TestDefinition {
fn: () => void | Promise<void>; fn: () => void | Promise<void>;
name: string; name: string;
ignore?: boolean; ignore?: boolean;
only?: boolean;
sanitizeOps?: boolean; sanitizeOps?: boolean;
sanitizeResources?: boolean; sanitizeResources?: boolean;
} }
@ -103,6 +104,7 @@ export function test(
let testDef: TestDefinition; let testDef: TestDefinition;
const defaults = { const defaults = {
ignore: false, ignore: false,
only: false,
sanitizeOps: true, sanitizeOps: true,
sanitizeResources: true, sanitizeResources: true,
}; };
@ -156,6 +158,7 @@ interface TestMessage {
measured: number; measured: number;
passed: number; passed: number;
failed: number; failed: number;
usedOnly: boolean;
duration: number; duration: number;
results: Array<TestMessage["testEnd"] & {}>; results: Array<TestMessage["testEnd"] & {}>;
}; };
@ -218,6 +221,10 @@ function reportToConsole(message: TestMessage): void {
`${message.end.filtered} filtered out ` + `${message.end.filtered} filtered out ` +
`${formatDuration(message.end.duration)}\n` `${formatDuration(message.end.duration)}\n`
); );
if (message.end.usedOnly && message.end.failed == 0) {
log(`${RED_FAILED} because the "only" option was used\n`);
}
} }
} }
@ -225,7 +232,7 @@ exposeForTest("reportToConsole", reportToConsole);
// 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<RunTestsEndResult> // TODO: implements PromiseLike<RunTestsEndResult>
class TestApi { class TestRunner {
readonly testsToRun: TestDefinition[]; readonly testsToRun: TestDefinition[];
readonly stats = { readonly stats = {
filtered: 0, filtered: 0,
@ -234,14 +241,18 @@ class TestApi {
passed: 0, passed: 0,
failed: 0, failed: 0,
}; };
private usedOnly: boolean;
constructor( constructor(
public tests: TestDefinition[], tests: TestDefinition[],
public filterFn: (def: TestDefinition) => boolean, public filterFn: (def: TestDefinition) => boolean,
public failFast: boolean public failFast: boolean
) { ) {
this.testsToRun = tests.filter(filterFn); const onlyTests = tests.filter(({ only }) => only);
this.stats.filtered = tests.length - this.testsToRun.length; 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](): AsyncIterator<TestMessage> { async *[Symbol.asyncIterator](): AsyncIterator<TestMessage> {
@ -280,7 +291,9 @@ class TestApi {
const duration = +new Date() - suiteStart; const duration = +new Date() - suiteStart;
yield { end: { ...this.stats, duration, results } }; yield {
end: { ...this.stats, usedOnly: this.usedOnly, duration, results },
};
} }
} }
@ -331,7 +344,7 @@ async function runTests({
onMessage = undefined, onMessage = undefined,
}: RunTestsOptions = {}): Promise<TestMessage["end"] & {}> { }: RunTestsOptions = {}): Promise<TestMessage["end"] & {}> {
const filterFn = createFilterFn(filter, skip); const filterFn = createFilterFn(filter, skip);
const testApi = new TestApi(TEST_REGISTRY, filterFn, failFast); const testRunner = new TestRunner(TEST_REGISTRY, filterFn, failFast);
const originalConsole = globalThis.console; const originalConsole = globalThis.console;
@ -342,7 +355,7 @@ async function runTests({
let endMsg: TestMessage["end"]; let endMsg: TestMessage["end"];
for await (const message of testApi) { for await (const message of testRunner) {
if (onMessage != null) { if (onMessage != null) {
await onMessage(message); await onMessage(message);
} }
@ -358,7 +371,7 @@ async function runTests({
globalThis.console = originalConsole; globalThis.console = originalConsole;
} }
if (endMsg!.failed > 0 && exitOnFail) { if ((endMsg!.failed > 0 || endMsg?.usedOnly) && exitOnFail) {
exit(1); exit(1);
} }

View file

@ -0,0 +1,15 @@
Deno.test({
name: "abc",
fn() {},
});
Deno.test({
only: true,
name: "def",
fn() {},
});
Deno.test({
name: "ghi",
fn() {},
});

View file

@ -0,0 +1,7 @@
[WILDCARD]running 1 tests
test def ... ok ([WILDCARD])
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD])
FAILED because the "only" option was used

View file

@ -1272,16 +1272,22 @@ itest!(_026_redirect_javascript {
http_server: true, http_server: true,
}); });
itest!(deno_test {
args: "test test_runner_test.ts",
exit_code: 1,
output: "deno_test.out",
});
itest!(deno_test_fail_fast { itest!(deno_test_fail_fast {
args: "test --failfast test_runner_test.ts", args: "test --failfast test_runner_test.ts",
exit_code: 1, exit_code: 1,
output: "deno_test_fail_fast.out", output: "deno_test_fail_fast.out",
}); });
itest!(deno_test { itest!(deno_test_only {
args: "test test_runner_test.ts", args: "test deno_test_only.ts",
exit_code: 1, exit_code: 1,
output: "deno_test.out", output: "deno_test_only.ts.out",
}); });
#[test] #[test]

View file

@ -1,6 +1,8 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { assert, assertEquals } from "../../../std/testing/asserts.ts"; import { assert, assertEquals } from "../../../std/testing/asserts.ts";
import * as colors from "../../../std/fmt/colors.ts";
export { colors };
import { resolve } from "../../../std/path/mod.ts"; import { resolve } from "../../../std/path/mod.ts";
export { export {
assert, assert,
@ -93,7 +95,9 @@ function registerPermCombination(perms: Permissions): void {
export async function registerUnitTests(): Promise<void> { export async function registerUnitTests(): Promise<void> {
const processPerms = await getProcessPermissions(); const processPerms = await getProcessPermissions();
for (const unitTestDefinition of REGISTERED_UNIT_TESTS) { const onlyTests = REGISTERED_UNIT_TESTS.filter(({ only }) => only);
const unitTests = onlyTests.length > 0 ? onlyTests : REGISTERED_UNIT_TESTS;
for (const unitTestDefinition of unitTests) {
if (!permissionsMatch(processPerms, unitTestDefinition.perms)) { if (!permissionsMatch(processPerms, unitTestDefinition.perms)) {
continue; continue;
} }
@ -126,11 +130,13 @@ interface UnitTestPermissions {
interface UnitTestOptions { interface UnitTestOptions {
ignore?: boolean; ignore?: boolean;
only?: boolean;
perms?: UnitTestPermissions; perms?: UnitTestPermissions;
} }
interface UnitTestDefinition extends Deno.TestDefinition { interface UnitTestDefinition extends Deno.TestDefinition {
ignore: boolean; ignore: boolean;
only: boolean;
perms: Permissions; perms: Permissions;
} }
@ -174,6 +180,7 @@ export function unitTest(
name, name,
fn, fn,
ignore: !!options.ignore, ignore: !!options.ignore,
only: !!options.only,
perms: normalizedPerms, perms: normalizedPerms,
}; };

View file

@ -2,6 +2,8 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import "./unit_tests.ts"; import "./unit_tests.ts";
import { import {
REGISTERED_UNIT_TESTS,
colors,
readLines, readLines,
permissionCombinations, permissionCombinations,
Permissions, Permissions,
@ -225,6 +227,13 @@ async function masterRunnerMain(
} }
console.log("Unit tests passed"); console.log("Unit tests passed");
if (REGISTERED_UNIT_TESTS.find(({ only }) => only)) {
console.error(
`\n${colors.red("FAILED")} because the "only" option was used`
);
Deno.exit(1);
}
} }
const HELP = `Unit test runner const HELP = `Unit test runner