summaryrefslogtreecommitdiff
path: root/cli/js
diff options
context:
space:
mode:
Diffstat (limited to 'cli/js')
-rw-r--r--cli/js/lib.deno.ns.d.ts18
-rw-r--r--cli/js/testing.ts128
-rw-r--r--cli/js/testing_test.ts37
-rw-r--r--cli/js/unit_tests.ts1
4 files changed, 119 insertions, 65 deletions
diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts
index 9a339f3d2..555a2ebf7 100644
--- a/cli/js/lib.deno.ns.d.ts
+++ b/cli/js/lib.deno.ns.d.ts
@@ -33,15 +33,17 @@ declare namespace Deno {
export function test(name: string, fn: TestFunction): void;
export interface RunTestsOptions {
- /** If `true`, Deno will exit upon a failure after logging that failure to
- * the console. Defaults to `false`. */
+ /** If `true`, Deno will exit with status code 1 if there was
+ * test failure. Defaults to `true`. */
exitOnFail?: boolean;
- /** Provide a regular expression of which only tests that match the regular
- * expression are run. */
- only?: RegExp;
- /** Provide a regular expression of which tests that match the regular
- * expression are skipped. */
- skip?: RegExp;
+ /** If `true`, Deno will exit upon first test failure Defaults to `false`. */
+ failFast?: boolean;
+ /** String or RegExp used to filter test to run. Only test with names
+ * matching provided `String` or `RegExp` will be run. */
+ only?: string | RegExp;
+ /** String or RegExp used to skip tests to run. Tests with names
+ * matching provided `String` or `RegExp` will not be run. */
+ skip?: string | RegExp;
/** Disable logging of the results. Defaults to `false`. */
disableLog?: boolean;
}
diff --git a/cli/js/testing.ts b/cli/js/testing.ts
index b4c86e8b8..4283f73d7 100644
--- a/cli/js/testing.ts
+++ b/cli/js/testing.ts
@@ -1,20 +1,17 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
-import { red, green, bgRed, bold, white, gray, italic } from "./colors.ts";
+import { red, green, bgRed, gray, italic } from "./colors.ts";
import { exit } from "./os.ts";
import { Console } from "./console.ts";
-function formatTestTime(time = 0): string {
- return `${time.toFixed(2)}ms`;
+function formatDuration(time = 0): string {
+ const timeStr = `(${time}ms)`;
+ return gray(italic(timeStr));
}
-function promptTestTime(time = 0, displayWarning = false): string {
- // if time > 5s we display a warning
- // only for test time, not the full runtime
- if (displayWarning && time >= 5000) {
- return bgRed(white(bold(`(${formatTestTime(time)})`)));
- } else {
- return gray(italic(`(${formatTestTime(time)})`));
- }
+function defer(n: number): Promise<void> {
+ return new Promise((resolve: () => void, _) => {
+ setTimeout(resolve, n);
+ });
}
export type TestFunction = () => void | Promise<void>;
@@ -24,22 +21,7 @@ export interface TestDefinition {
name: string;
}
-declare global {
- // Only `var` variables show up in the `globalThis` type when doing a global
- // scope augmentation.
- // eslint-disable-next-line no-var
- var __DENO_TEST_REGISTRY: TestDefinition[];
-}
-
-let TEST_REGISTRY: TestDefinition[] = [];
-if (globalThis["__DENO_TEST_REGISTRY"]) {
- TEST_REGISTRY = globalThis.__DENO_TEST_REGISTRY as TestDefinition[];
-} else {
- Object.defineProperty(globalThis, "__DENO_TEST_REGISTRY", {
- enumerable: false,
- value: TEST_REGISTRY
- });
-}
+const TEST_REGISTRY: TestDefinition[] = [];
export function test(t: TestDefinition): void;
export function test(fn: TestFunction): void;
@@ -97,20 +79,48 @@ interface TestCase {
export interface RunTestsOptions {
exitOnFail?: boolean;
- only?: RegExp;
- skip?: RegExp;
+ failFast?: boolean;
+ only?: string | RegExp;
+ skip?: string | RegExp;
disableLog?: boolean;
}
+function filterTests(
+ tests: TestDefinition[],
+ only: undefined | string | RegExp,
+ skip: undefined | string | RegExp
+): TestDefinition[] {
+ return tests.filter((def: TestDefinition): boolean => {
+ let passes = true;
+
+ if (only) {
+ if (only instanceof RegExp) {
+ passes = passes && only.test(def.name);
+ } else {
+ passes = passes && def.name.includes(only);
+ }
+ }
+
+ if (skip) {
+ if (skip instanceof RegExp) {
+ passes = passes && !skip.test(def.name);
+ } else {
+ passes = passes && !def.name.includes(skip);
+ }
+ }
+
+ return passes;
+ });
+}
+
export async function runTests({
- exitOnFail = false,
- only = /[^\s]/,
- skip = /^\s*$/,
+ exitOnFail = true,
+ failFast = false,
+ only = undefined,
+ skip = undefined,
disableLog = false
}: RunTestsOptions = {}): Promise<void> {
- const testsToRun = TEST_REGISTRY.filter(
- ({ name }): boolean => only.test(name) && !skip.test(name)
- );
+ const testsToRun = filterTests(TEST_REGISTRY, only, skip);
const stats: TestStats = {
measured: 0,
@@ -149,16 +159,17 @@ export async function runTests({
const RED_BG_FAIL = bgRed(" FAIL ");
originalConsole.log(`running ${testsToRun.length} tests`);
- const suiteStart = performance.now();
+ const suiteStart = +new Date();
for (const testCase of testCases) {
try {
- const start = performance.now();
+ const start = +new Date();
await testCase.fn();
- const end = performance.now();
- testCase.timeElapsed = end - start;
+ testCase.timeElapsed = +new Date() - start;
originalConsole.log(
- `${GREEN_OK} ${testCase.name} ${promptTestTime(end - start, true)}`
+ `${GREEN_OK} ${testCase.name} ${formatDuration(
+ testCase.timeElapsed
+ )}`
);
stats.passed++;
} catch (err) {
@@ -166,13 +177,13 @@ export async function runTests({
originalConsole.log(`${RED_FAILED} ${testCase.name}`);
originalConsole.log(err.stack);
stats.failed++;
- if (exitOnFail) {
+ if (failFast) {
break;
}
}
}
- const suiteEnd = performance.now();
+ const suiteDuration = +new Date() - suiteStart;
if (disableLog) {
// @ts-ignore
@@ -185,23 +196,26 @@ export async function runTests({
`${stats.passed} passed; ${stats.failed} failed; ` +
`${stats.ignored} ignored; ${stats.measured} measured; ` +
`${stats.filtered} filtered out ` +
- `${promptTestTime(suiteEnd - suiteStart)}\n`
+ `${formatDuration(suiteDuration)}\n`
);
- // TODO(bartlomieju): what's it for? Do we really need, maybe add handler for unhandled
- // promise to avoid such shenanigans
- if (stats.failed) {
- // Use setTimeout to avoid the error being ignored due to unhandled
- // promise rejections being swallowed.
- setTimeout((): void => {
- originalConsole.error(`There were ${stats.failed} test failures.`);
- testCases
- .filter(testCase => !!testCase.error)
- .forEach(testCase => {
- originalConsole.error(`${RED_BG_FAIL} ${red(testCase.name)}`);
- originalConsole.error(testCase.error);
- });
+ // TODO(bartlomieju): is `defer` really needed? Shouldn't unhandled
+ // promise rejection be handled per test case?
+ // Use defer to avoid the error being ignored due to unhandled
+ // promise rejections being swallowed.
+ await defer(0);
+
+ if (stats.failed > 0) {
+ originalConsole.error(`There were ${stats.failed} test failures.`);
+ testCases
+ .filter(testCase => !!testCase.error)
+ .forEach(testCase => {
+ originalConsole.error(`${RED_BG_FAIL} ${red(testCase.name)}`);
+ originalConsole.error(testCase.error);
+ });
+
+ if (exitOnFail) {
exit(1);
- }, 0);
+ }
}
}
diff --git a/cli/js/testing_test.ts b/cli/js/testing_test.ts
new file mode 100644
index 000000000..b47eb03e2
--- /dev/null
+++ b/cli/js/testing_test.ts
@@ -0,0 +1,37 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { assertThrows, unitTest } from "./test_util.ts";
+
+unitTest(function testFnOverloading(): void {
+ // just verifying that you can use this test definition syntax
+ Deno.test("test fn overloading", (): void => {});
+});
+
+unitTest(function nameOfTestCaseCantBeEmpty(): void {
+ assertThrows(
+ () => {
+ Deno.test("", () => {});
+ },
+ Error,
+ "The name of test case can't be empty"
+ );
+ assertThrows(
+ () => {
+ Deno.test({
+ name: "",
+ fn: () => {}
+ });
+ },
+ Error,
+ "The name of test case can't be empty"
+ );
+});
+
+unitTest(function testFnCantBeAnonymous(): void {
+ assertThrows(
+ () => {
+ Deno.test(function() {});
+ },
+ Error,
+ "Test function can't be anonymous"
+ );
+});
diff --git a/cli/js/unit_tests.ts b/cli/js/unit_tests.ts
index 5093ce0b2..2b02f8dcf 100644
--- a/cli/js/unit_tests.ts
+++ b/cli/js/unit_tests.ts
@@ -53,6 +53,7 @@ import "./symbols_test.ts";
import "./symlink_test.ts";
import "./text_encoding_test.ts";
import "./timers_test.ts";
+import "./testing_test.ts";
import "./tls_test.ts";
import "./truncate_test.ts";
import "./tty_test.ts";