diff options
Diffstat (limited to 'cli/js/test_util.ts')
-rw-r--r-- | cli/js/test_util.ts | 411 |
1 files changed, 0 insertions, 411 deletions
diff --git a/cli/js/test_util.ts b/cli/js/test_util.ts deleted file mode 100644 index fa17a2f87..000000000 --- a/cli/js/test_util.ts +++ /dev/null @@ -1,411 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -// -// We want to test many ops in deno which have different behavior depending on -// the permissions set. These tests can specify which permissions they expect, -// which appends a special string like "permW1N0" to the end of the test name. -// Here we run several copies of deno with different permissions, filtering the -// tests by the special string. permW1N0 means allow-write but not allow-net. -// See tools/unit_tests.py for more details. - -import { readLines } from "../../std/io/bufio.ts"; -import { assert, assertEquals } from "../../std/testing/asserts.ts"; -export { - assert, - assertThrows, - assertEquals, - assertMatch, - assertNotEquals, - assertStrictEq, - assertStrContains, - unreachable, - fail -} from "../../std/testing/asserts.ts"; - -interface TestPermissions { - read?: boolean; - write?: boolean; - net?: boolean; - env?: boolean; - run?: boolean; - plugin?: boolean; - hrtime?: boolean; -} - -export interface Permissions { - read: boolean; - write: boolean; - net: boolean; - env: boolean; - run: boolean; - plugin: boolean; - hrtime: boolean; -} - -const isGranted = async (name: Deno.PermissionName): Promise<boolean> => - (await Deno.permissions.query({ name })).state === "granted"; - -async function getProcessPermissions(): Promise<Permissions> { - return { - run: await isGranted("run"), - read: await isGranted("read"), - write: await isGranted("write"), - net: await isGranted("net"), - env: await isGranted("env"), - plugin: await isGranted("plugin"), - hrtime: await isGranted("hrtime") - }; -} - -const processPerms = await getProcessPermissions(); - -function permissionsMatch( - processPerms: Permissions, - requiredPerms: Permissions -): boolean { - for (const permName in processPerms) { - if ( - processPerms[permName as keyof Permissions] !== - requiredPerms[permName as keyof Permissions] - ) { - return false; - } - } - - return true; -} - -export const permissionCombinations: Map<string, Permissions> = new Map(); - -function permToString(perms: Permissions): string { - const r = perms.read ? 1 : 0; - const w = perms.write ? 1 : 0; - const n = perms.net ? 1 : 0; - const e = perms.env ? 1 : 0; - const u = perms.run ? 1 : 0; - const p = perms.plugin ? 1 : 0; - const h = perms.hrtime ? 1 : 0; - return `permR${r}W${w}N${n}E${e}U${u}P${p}H${h}`; -} - -function registerPermCombination(perms: Permissions): void { - const key = permToString(perms); - if (!permissionCombinations.has(key)) { - permissionCombinations.set(key, perms); - } -} - -function normalizeTestPermissions(perms: TestPermissions): Permissions { - return { - read: !!perms.read, - write: !!perms.write, - net: !!perms.net, - run: !!perms.run, - env: !!perms.env, - plugin: !!perms.plugin, - hrtime: !!perms.hrtime - }; -} - -// Wrap `TestFunction` in additional assertion that makes sure -// the test case does not leak async "ops" - ie. number of async -// completed ops after the test is the same as number of dispatched -// ops. Note that "unref" ops are ignored since in nature that are -// optional. -function assertOps(fn: Deno.TestFunction): Deno.TestFunction { - return async function asyncOpSanitizer(): Promise<void> { - const pre = Deno.metrics(); - await fn(); - const post = Deno.metrics(); - // We're checking diff because one might spawn HTTP server in the background - // that will be a pending async op before test starts. - assertEquals( - post.opsDispatchedAsync - pre.opsDispatchedAsync, - post.opsCompletedAsync - pre.opsCompletedAsync, - `Test case is leaking async ops. - Before: - - dispatched: ${pre.opsDispatchedAsync} - - completed: ${pre.opsCompletedAsync} - After: - - dispatched: ${post.opsDispatchedAsync} - - completed: ${post.opsCompletedAsync}` - ); - }; -} - -// Wrap `TestFunction` in additional assertion that makes sure -// the test case does not "leak" resources - ie. resource table after -// the test has exactly the same contents as before the test. -function assertResources(fn: Deno.TestFunction): Deno.TestFunction { - return async function resourceSanitizer(): Promise<void> { - const pre = Deno.resources(); - await fn(); - const post = Deno.resources(); - const msg = `Test case is leaking resources. - Before: ${JSON.stringify(pre, null, 2)} - After: ${JSON.stringify(post, null, 2)}`; - assertEquals(pre, post, msg); - }; -} - -interface UnitTestOptions { - skip?: boolean; - perms?: TestPermissions; -} - -export function unitTest(fn: Deno.TestFunction): void; -export function unitTest(options: UnitTestOptions, fn: Deno.TestFunction): void; -export function unitTest( - optionsOrFn: UnitTestOptions | Deno.TestFunction, - maybeFn?: Deno.TestFunction -): void { - assert(optionsOrFn, "At least one argument is required"); - - let options: UnitTestOptions; - let name: string; - let fn: Deno.TestFunction; - - if (typeof optionsOrFn === "function") { - options = {}; - fn = optionsOrFn; - name = fn.name; - assert(name, "Missing test function name"); - } else { - options = optionsOrFn; - assert(maybeFn, "Missing test function definition"); - assert( - typeof maybeFn === "function", - "Second argument should be test function definition" - ); - fn = maybeFn; - name = fn.name; - assert(name, "Missing test function name"); - } - - if (options.skip) { - return; - } - - const normalizedPerms = normalizeTestPermissions(options.perms || {}); - registerPermCombination(normalizedPerms); - if (!permissionsMatch(processPerms, normalizedPerms)) { - return; - } - - const testDefinition: Deno.TestDefinition = { - name, - fn: assertResources(assertOps(fn)) - }; - Deno.test(testDefinition); -} - -function extractNumber(re: RegExp, str: string): number | undefined { - const match = str.match(re); - - if (match) { - return Number.parseInt(match[1]); - } -} - -export async function parseUnitTestOutput( - reader: Deno.Reader, - print: boolean -): Promise<{ actual?: number; expected?: number; resultOutput?: string }> { - let expected, actual, result; - - for await (const line of readLines(reader)) { - if (!expected) { - // expect "running 30 tests" - expected = extractNumber(/running (\d+) tests/, line); - } else if (line.indexOf("test result:") !== -1) { - result = line; - } - - if (print) { - console.log(line); - } - } - - // Check that the number of expected tests equals what was reported at the - // bottom. - if (result) { - // result should be a string like this: - // "test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; ..." - actual = extractNumber(/(\d+) passed/, result); - } - - return { actual, expected, resultOutput: result }; -} - -export interface ResolvableMethods<T> { - resolve: (value?: T | PromiseLike<T>) => void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - reject: (reason?: any) => void; -} - -export type Resolvable<T> = Promise<T> & ResolvableMethods<T>; - -export function createResolvable<T>(): Resolvable<T> { - let methods: ResolvableMethods<T>; - const promise = new Promise<T>((resolve, reject): void => { - methods = { resolve, reject }; - }); - // TypeScript doesn't know that the Promise callback occurs synchronously - // therefore use of not null assertion (`!`) - return Object.assign(promise, methods!) as Resolvable<T>; -} - -unitTest(function permissionsMatches(): void { - assert( - permissionsMatch( - { - read: true, - write: false, - net: false, - env: false, - run: false, - plugin: false, - hrtime: false - }, - normalizeTestPermissions({ read: true }) - ) - ); - - assert( - permissionsMatch( - { - read: false, - write: false, - net: false, - env: false, - run: false, - plugin: false, - hrtime: false - }, - normalizeTestPermissions({}) - ) - ); - - assertEquals( - permissionsMatch( - { - read: false, - write: true, - net: true, - env: true, - run: true, - plugin: true, - hrtime: true - }, - normalizeTestPermissions({ read: true }) - ), - false - ); - - assertEquals( - permissionsMatch( - { - read: true, - write: false, - net: true, - env: false, - run: false, - plugin: false, - hrtime: false - }, - normalizeTestPermissions({ read: true }) - ), - false - ); - - assert( - permissionsMatch( - { - read: true, - write: true, - net: true, - env: true, - run: true, - plugin: true, - hrtime: true - }, - { - read: true, - write: true, - net: true, - env: true, - run: true, - plugin: true, - hrtime: true - } - ) - ); -}); - -unitTest( - { perms: { read: true } }, - async function parsingUnitTestOutput(): Promise<void> { - const cwd = Deno.cwd(); - const testDataPath = `${cwd}/tools/testdata/`; - - let result; - - // This is an example of a successful unit test output. - const f1 = await Deno.open(`${testDataPath}/unit_test_output1.txt`); - result = await parseUnitTestOutput(f1, false); - assertEquals(result.actual, 96); - assertEquals(result.expected, 96); - f1.close(); - - // This is an example of a silently dying unit test. - const f2 = await Deno.open(`${testDataPath}/unit_test_output2.txt`); - result = await parseUnitTestOutput(f2, false); - assertEquals(result.actual, undefined); - assertEquals(result.expected, 96); - f2.close(); - - // This is an example of compiling before successful unit tests. - const f3 = await Deno.open(`${testDataPath}/unit_test_output3.txt`); - result = await parseUnitTestOutput(f3, false); - assertEquals(result.actual, 96); - assertEquals(result.expected, 96); - f3.close(); - - // Check what happens on empty output. - const f = new Deno.Buffer(new TextEncoder().encode("\n\n\n")); - result = await parseUnitTestOutput(f, false); - assertEquals(result.actual, undefined); - assertEquals(result.expected, undefined); - } -); - -/* - * Ensure all unit test files (e.g. xxx_test.ts) are present as imports in - * cli/js/unit_tests.ts as it is easy to miss this out - */ -unitTest( - { perms: { read: true } }, - async function assertAllUnitTestFilesImported(): Promise<void> { - const directoryTestFiles = Deno.readdirSync("./cli/js") - .map(k => k.name) - .filter(file => file!.endsWith("_test.ts")); - const unitTestsFile: Uint8Array = Deno.readFileSync( - "./cli/js/unit_tests.ts" - ); - const importLines = new TextDecoder("utf-8") - .decode(unitTestsFile) - .split("\n") - .filter(line => line.startsWith("import") && line.includes("_test.ts")); - const importedTestFiles = importLines.map( - relativeFilePath => relativeFilePath.match(/\/([^\/]+)";/)![1] - ); - - directoryTestFiles.forEach(dirFile => { - if (!importedTestFiles.includes(dirFile!)) { - throw new Error( - "cil/js/unit_tests.ts is missing import of test file: cli/js/" + - dirFile - ); - } - }); - } -); |