diff options
-rw-r--r-- | cli/tests/integration_tests.rs | 9 | ||||
-rw-r--r-- | cli/tests/unit/README.md | 47 | ||||
-rw-r--r-- | cli/tests/unit/test_util.ts | 292 | ||||
-rwxr-xr-x | cli/tests/unit/unit_test_runner.ts | 319 | ||||
-rw-r--r-- | cli/tests/unit/unit_tests.ts | 77 | ||||
-rw-r--r-- | cli/tests/unit/webgpu_test.ts | 6 |
6 files changed, 26 insertions, 724 deletions
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index bb99cf49b..b5e5d1fa6 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -33,16 +33,17 @@ fn js_unit_tests_lint() { #[test] fn js_unit_tests() { let _g = util::http_server(); + let mut deno = util::deno_cmd() .current_dir(util::root_path()) - .arg("run") + .arg("test") .arg("--unstable") + .arg("--location=http://js-unit-tests/foo/bar") .arg("-A") - .arg("cli/tests/unit/unit_test_runner.ts") - .arg("--master") - .arg("--verbose") + .arg("cli/tests/unit") .spawn() .expect("failed to spawn script"); + let status = deno.wait().expect("failed to wait for the child process"); assert_eq!(Some(0), status.code()); assert!(status.success()); diff --git a/cli/tests/unit/README.md b/cli/tests/unit/README.md index 7f0b243fe..114bbc043 100644 --- a/cli/tests/unit/README.md +++ b/cli/tests/unit/README.md @@ -3,8 +3,8 @@ Files in this directory are unit tests for Deno runtime. Testing Deno runtime code requires checking API under different runtime -permissions (ie. running with different `--allow-*` flags). To accomplish this -all tests exercised are created using `unitTest()` function. +permissions. To accomplish this all tests exercised are created using +`unitTest()` function. ```ts import { unitTest } from "./test_util.ts"; @@ -24,51 +24,16 @@ unitTest( ); ``` -`unitTest` is a wrapper function that enhances `Deno.test()` API in several -ways: - -- ability to conditionally skip tests using `UnitTestOptions.skip`. -- ability to register required set of permissions for given test case using - `UnitTestOptions.perms`. -- sanitization of resources - ensuring that tests close all opened resources - preventing interference between tests. -- sanitization of async ops - ensuring that tests don't leak async ops by - ensuring that all started async ops are done before test finishes. - ## Running tests -`unit_test_runner.ts` is the main script used to run unit tests. - -Runner discovers required permissions combinations by loading -`cli/tests/unit/unit_tests.ts` and going through all registered instances of -`unitTest`. - There are three ways to run `unit_test_runner.ts`: ```sh -# Run all tests. Spawns worker processes for each discovered permission -# combination: -target/debug/deno run -A cli/tests/unit/unit_test_runner.ts --master - -# By default all output of worker processes is discarded; for debug purposes -# the --verbose flag preserves output from the worker. -target/debug/deno run -A cli/tests/unit/unit_test_runner.ts --master --verbose - -# Run subset of tests that don't require any permissions. -target/debug/deno run --unstable cli/tests/unit/unit_test_runner.ts - -# Run subset tests that require "net" and "read" permissions. -target/debug/deno run --unstable --allow-net --allow-read cli/tests/unit/unit_test_runner.ts - -# "worker" mode communicates with parent using TCP socket on provided address; -# after initial setup drops permissions to specified set. It shouldn't be used -# directly, only be "master" process. -target/debug/deno run -A cli/tests/unit/unit_test_runner.ts --worker --addr=127.0.0.1:4500 --perms=net,write,run - -# Run specific tests. -target/debug/deno run --unstable --allow-net cli/tests/unit/unit_test_runner.ts -- netTcpListenClose +# Run all tests. +target/debug/deno test --allow-all --unstable cli/tests/unit/ -RUST_BACKTRACE=1 cargo run -- run --unstable --allow-read --allow-write cli/tests/unit/unit_test_runner.ts -- netUnixDialListen +# Run a specific test module +target/debug/deno test --allow-all --unstable cli/tests/unit/files_test.ts ``` ### Http server diff --git a/cli/tests/unit/test_util.ts b/cli/tests/unit/test_util.ts index 0f967c0fb..7975f38e5 100644 --- a/cli/tests/unit/test_util.ts +++ b/cli/tests/unit/test_util.ts @@ -25,105 +25,6 @@ export type { Deferred } from "../../../test_util/std/async/deferred.ts"; export { readLines } from "../../../test_util/std/io/bufio.ts"; export { parse as parseArgs } from "../../../test_util/std/flags/mod.ts"; -export interface Permissions { - read: boolean; - write: boolean; - net: boolean; - env: boolean; - run: boolean; - plugin: boolean; - hrtime: boolean; -} - -export function fmtPerms(perms: Permissions): string { - const p = Object.keys(perms) - .filter((e): boolean => perms[e as keyof Permissions] === true) - .map((key) => `--allow-${key}`); - - if (p.length) { - return p.join(" "); - } - - return "<no permissions>"; -} - -const isGranted = async (name: Deno.PermissionName): Promise<boolean> => - (await Deno.permissions.query({ name })).state === "granted"; - -export 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"), - }; -} - -export 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); - } -} - -export async function registerUnitTests(): Promise<void> { - const processPerms = await getProcessPermissions(); - - 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)) { - continue; - } - - Deno.test(unitTestDefinition); - } -} - -function normalizeTestPermissions(perms: UnitTestPermissions): Permissions { - return { - read: !!perms.read, - write: !!perms.write, - net: !!perms.net, - run: !!perms.run, - env: !!perms.env, - plugin: !!perms.plugin, - hrtime: !!perms.hrtime, - }; -} - interface UnitTestPermissions { read?: boolean; write?: boolean; @@ -140,16 +41,8 @@ interface UnitTestOptions { perms?: UnitTestPermissions; } -interface UnitTestDefinition extends Deno.TestDefinition { - ignore: boolean; - only: boolean; - perms: Permissions; -} - type TestFunction = () => void | Promise<void>; -export const REGISTERED_UNIT_TESTS: UnitTestDefinition[] = []; - export function unitTest(fn: TestFunction): void; export function unitTest(options: UnitTestOptions, fn: TestFunction): void; export function unitTest( @@ -179,188 +72,25 @@ export function unitTest( assert(name, "Missing test function name"); } - const normalizedPerms = normalizeTestPermissions(options.perms || {}); - registerPermCombination(normalizedPerms); - - const unitTestDefinition: UnitTestDefinition = { + const testDefinition: Deno.TestDefinition = { name, fn, ignore: !!options.ignore, only: !!options.only, - perms: normalizedPerms, + permissions: Object.assign({ + read: false, + write: false, + net: false, + env: false, + run: false, + plugin: false, + hrtime: false, + }, options.perms), }; - REGISTERED_UNIT_TESTS.push(unitTestDefinition); -} - -const encoder = new TextEncoder(); - -// Replace functions with null, errors with their stack strings, and JSONify. -// deno-lint-ignore no-explicit-any -function serializeTestMessage(message: any): string { - return JSON.stringify({ - start: message.start && { - ...message.start, - tests: message.start.tests.map((test: Deno.TestDefinition) => ({ - ...test, - fn: null, - })), - }, - testStart: message.testStart && { ...message.testStart, fn: null }, - testEnd: message.testEnd && { - ...message.testEnd, - error: String(message.testEnd.error?.stack), - }, - end: message.end && { - ...message.end, - // deno-lint-ignore no-explicit-any - results: message.end.results.map((result: any) => ({ - ...result, - error: result.error?.stack, - })), - }, - }); + Deno.test(testDefinition); } -export async function reportToConn( - conn: Deno.Conn, - // deno-lint-ignore no-explicit-any - message: any, -): Promise<void> { - const line = serializeTestMessage(message); - const encodedMsg = encoder.encode(line + (message.end == null ? "\n" : "")); - // deno-lint-ignore no-deprecated-deno-api - await Deno.writeAll(conn, encodedMsg); - if (message.end != null) { - conn.closeWrite(); - } -} - -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, - }, - ), - ); -}); - -/* - * Ensure all unit test files (e.g. xxx_test.ts) are present as imports in - * cli/tests/unit/unit_tests.ts as it is easy to miss this out - */ -unitTest( - { perms: { read: true } }, - function assertAllUnitTestFilesImported(): void { - const directoryTestFiles = [...Deno.readDirSync("./cli/tests/unit/")] - .map((k) => k.name) - .filter( - (file) => - file!.endsWith(".ts") && - !file!.endsWith("unit_tests.ts") && - !file!.endsWith("test_util.ts") && - !file!.endsWith("unit_test_runner.ts"), - ); - const unitTestsFile: Uint8Array = Deno.readFileSync( - "./cli/tests/unit/unit_tests.ts", - ); - const importLines = new TextDecoder("utf-8") - .decode(unitTestsFile) - .split("\n") - .filter((line) => line.startsWith("import")); - const importedTestFiles = importLines.map( - (relativeFilePath) => relativeFilePath.match(/\/([^\/]+)";/)![1], - ); - - directoryTestFiles.forEach((dirFile) => { - if (!importedTestFiles.includes(dirFile!)) { - throw new Error( - "cil/tests/unit/unit_tests.ts is missing import of test file: cli/js/" + - dirFile, - ); - } - }); - }, -); - export function pathToAbsoluteFileUrl(path: string): URL { path = resolve(path); diff --git a/cli/tests/unit/unit_test_runner.ts b/cli/tests/unit/unit_test_runner.ts deleted file mode 100755 index 6b039f597..000000000 --- a/cli/tests/unit/unit_test_runner.ts +++ /dev/null @@ -1,319 +0,0 @@ -#!/usr/bin/env -S deno run --reload --allow-run -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import "./unit_tests.ts"; -import { - colors, - fmtPerms, - parseArgs, - permissionCombinations, - Permissions, - readLines, - REGISTERED_UNIT_TESTS, - registerUnitTests, - reportToConn, -} from "./test_util.ts"; - -// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol -const internalObj = Deno[Deno.internal]; -// deno-lint-ignore no-explicit-any -const reportToConsole = internalObj.reportToConsole as (message: any) => void; -// deno-lint-ignore no-explicit-any -const runTests = internalObj.runTests as (options: any) => Promise<any>; - -interface PermissionSetTestResult { - perms: Permissions; - passed: boolean; - // deno-lint-ignore no-explicit-any - endMessage: any; - permsStr: string; -} - -const PERMISSIONS: Deno.PermissionName[] = [ - "read", - "write", - "net", - "env", - "run", - "plugin", - "hrtime", -]; - -/** - * Take a list of permissions and revoke missing permissions. - */ -async function dropWorkerPermissions( - requiredPermissions: Deno.PermissionName[], -): Promise<void> { - const permsToDrop = PERMISSIONS.filter((p): boolean => { - return !requiredPermissions.includes(p); - }); - - for (const perm of permsToDrop) { - await Deno.permissions.revoke({ name: perm }); - } -} - -async function workerRunnerMain( - addrStr: string, - permsStr: string, - filter?: string, -): Promise<void> { - const [hostname, port] = addrStr.split(":"); - const addr = { hostname, port: Number(port) }; - - let perms: Deno.PermissionName[] = []; - if (permsStr.length > 0) { - perms = permsStr.split(",") as Deno.PermissionName[]; - } - // Setup reporter - const conn = await Deno.connect(addr); - // Drop current process permissions to requested set - await dropWorkerPermissions(perms); - // Register unit tests that match process permissions - await registerUnitTests(); - // Execute tests - await runTests({ - exitOnFail: false, - filter, - reportToConsole: false, - onMessage: reportToConn.bind(null, conn), - }); -} - -function spawnWorkerRunner( - verbose: boolean, - addr: string, - perms: Permissions, - filter?: string, -): Deno.Process { - // run subsequent tests using same deno executable - const permStr = Object.keys(perms) - .filter((permName): boolean => { - return perms[permName as Deno.PermissionName] === true; - }) - .join(","); - - const cmd = [ - Deno.execPath(), - "run", - "--unstable", // TODO(ry) be able to test stable vs unstable - "--location=http://js-unit-tests/foo/bar", - "-A", - "cli/tests/unit/unit_test_runner.ts", - "--worker", - `--addr=${addr}`, - `--perms=${permStr}`, - ]; - - if (filter) { - cmd.push("--"); - cmd.push(filter); - } - - const ioMode = verbose ? "inherit" : "null"; - - const p = Deno.run({ - cmd, - stdin: ioMode, - stdout: ioMode, - stderr: ioMode, - }); - - return p; -} - -async function runTestsForPermissionSet( - listener: Deno.Listener, - addrStr: string, - verbose: boolean, - perms: Permissions, - filter?: string, -): Promise<PermissionSetTestResult> { - const permsFmt = fmtPerms(perms); - console.log(`Running tests for: ${permsFmt}`); - const workerProcess = spawnWorkerRunner(verbose, addrStr, perms, filter); - // Wait for worker subprocess to go online - const conn = await listener.accept(); - - let expectedPassedTests; - // deno-lint-ignore no-explicit-any - let endMessage: any; - - try { - for await (const line of readLines(conn)) { - // deno-lint-ignore no-explicit-any - const message = JSON.parse(line) as any; - reportToConsole(message); - if (message.start != null) { - expectedPassedTests = message.start.tests.length; - } else if (message.end != null) { - endMessage = message.end; - } - } - } finally { - // Close socket to worker. - conn.close(); - } - - if (expectedPassedTests == null) { - throw new Error("Worker runner didn't report start"); - } - - if (endMessage == null) { - throw new Error("Worker runner didn't report end"); - } - - const workerStatus = await workerProcess.status(); - if (!workerStatus.success) { - throw new Error( - `Worker runner exited with status code: ${workerStatus.code}`, - ); - } - - workerProcess.close(); - - const passed = expectedPassedTests === endMessage.passed + endMessage.ignored; - - return { - perms, - passed, - permsStr: permsFmt, - endMessage, - }; -} - -async function masterRunnerMain( - verbose: boolean, - filter?: string, -): Promise<void> { - console.log( - "Discovered permission combinations for tests:", - permissionCombinations.size, - ); - - for (const perms of permissionCombinations.values()) { - console.log("\t" + fmtPerms(perms)); - } - - const testResults = new Set<PermissionSetTestResult>(); - const addr = { hostname: "127.0.0.1", port: 4510 }; - const addrStr = `${addr.hostname}:${addr.port}`; - const listener = Deno.listen(addr); - - for (const perms of permissionCombinations.values()) { - const result = await runTestsForPermissionSet( - listener, - addrStr, - verbose, - perms, - filter, - ); - testResults.add(result); - } - - // if any run tests returned non-zero status then whole test - // run should fail - let testsPassed = true; - - for (const testResult of testResults) { - const { permsStr, endMessage } = testResult; - console.log(`Summary for ${permsStr}`); - reportToConsole({ end: endMessage }); - testsPassed = testsPassed && testResult.passed; - } - - if (!testsPassed) { - console.error("Unit tests failed"); - Deno.exit(1); - } - - 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 - -Run tests matching current process permissions: - - deno --allow-write unit_test_runner.ts - - deno --allow-net --allow-hrtime unit_test_runner.ts - - deno --allow-write unit_test_runner.ts -- testWriteFile - -Run "master" process that creates "worker" processes -for each discovered permission combination: - - deno -A unit_test_runner.ts --master - -Run worker process for given permissions: - - deno -A unit_test_runner.ts --worker --perms=net,read,write --addr=127.0.0.1:4500 - - -OPTIONS: - --master - Run in master mode, spawning worker processes for - each discovered permission combination - - --worker - Run in worker mode, requires "perms" and "addr" flags, - should be run with "-A" flag; after setup worker will - drop permissions to required set specified in "perms" - - --perms=<perm_name>... - Set of permissions this process should run tests with, - - --addr=<addr> - Address of TCP socket for reporting - -ARGS: - -- <filter>... - Run only tests with names matching filter, must - be used after "--" -`; - -function assertOrHelp(expr: unknown): asserts expr { - if (!expr) { - console.log(HELP); - Deno.exit(1); - } -} - -async function main(): Promise<void> { - const args = parseArgs(Deno.args, { - boolean: ["master", "worker", "verbose"], - "--": true, - }); - - if (args.help) { - console.log(HELP); - return; - } - - const filter = args["--"][0]; - - // Master mode - if (args.master) { - return masterRunnerMain(args.verbose, filter); - } - - // Worker mode - if (args.worker) { - assertOrHelp(typeof args.addr === "string"); - assertOrHelp(typeof args.perms === "string"); - return workerRunnerMain(args.addr, args.perms, filter); - } - - // Running tests matching current process permissions - await registerUnitTests(); - await runTests({ filter }); -} - -main(); diff --git a/cli/tests/unit/unit_tests.ts b/cli/tests/unit/unit_tests.ts deleted file mode 100644 index f538ab9b6..000000000 --- a/cli/tests/unit/unit_tests.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -// This test is executed as part of unit test suite. -// -// Test runner automatically spawns subprocesses for each required permissions combination. - -import "./abort_controller_test.ts"; -import "./blob_test.ts"; -import "./body_test.ts"; -import "./buffer_test.ts"; -import "./build_test.ts"; -import "./chmod_test.ts"; -import "./chown_test.ts"; -import "./console_test.ts"; -import "./copy_file_test.ts"; -import "./custom_event_test.ts"; -import "./dir_test.ts"; -import "./opcall_test.ts"; -import "./error_stack_test.ts"; -import "./event_test.ts"; -import "./event_target_test.ts"; -import "./fetch_test.ts"; -import "./file_test.ts"; -import "./filereader_test.ts"; -import "./files_test.ts"; -import "./filter_function_test.ts"; -import "./format_error_test.ts"; -import "./fs_events_test.ts"; -import "./get_random_values_test.ts"; -import "./globals_test.ts"; -import "./headers_test.ts"; -import "./http_test.ts"; -import "./internals_test.ts"; -import "./io_test.ts"; -import "./link_test.ts"; -import "./make_temp_test.ts"; -import "./metrics_test.ts"; -import "./mkdir_test.ts"; -import "./net_test.ts"; -import "./os_test.ts"; -import "./permissions_test.ts"; -import "./path_from_url_test.ts"; -import "./process_test.ts"; -import "./progressevent_test.ts"; -import "./real_path_test.ts"; -import "./read_dir_test.ts"; -import "./read_text_file_test.ts"; -import "./read_file_test.ts"; -import "./read_link_test.ts"; -import "./remove_test.ts"; -import "./rename_test.ts"; -import "./request_test.ts"; -import "./resources_test.ts"; -import "./response_test.ts"; -import "./signal_test.ts"; -import "./stat_test.ts"; -import "./stdio_test.ts"; -import "./streams_deprecated.ts"; -import "./symlink_test.ts"; -import "./sync_test.ts"; -import "./text_encoding_test.ts"; -import "./testing_test.ts"; -import "./timers_test.ts"; -import "./tls_test.ts"; -import "./truncate_test.ts"; -import "./tty_test.ts"; -import "./umask_test.ts"; -import "./url_test.ts"; -import "./url_search_params_test.ts"; -import "./utime_test.ts"; -import "./worker_types.ts"; -import "./write_file_test.ts"; -import "./write_text_file_test.ts"; -import "./performance_test.ts"; -import "./version_test.ts"; -import "./websocket_test.ts"; -import "./webgpu_test.ts"; diff --git a/cli/tests/unit/webgpu_test.ts b/cli/tests/unit/webgpu_test.ts index d40851dfa..7b761ee3c 100644 --- a/cli/tests/unit/webgpu_test.ts +++ b/cli/tests/unit/webgpu_test.ts @@ -179,10 +179,11 @@ unitTest({ }); const encoder = device.createCommandEncoder(); + const view = texture.createView(); const renderPass = encoder.beginRenderPass({ colorAttachments: [ { - view: texture.createView(), + view, storeOp: "store", loadValue: [0, 1, 0, 1], }, @@ -204,7 +205,8 @@ unitTest({ dimensions, ); - device.queue.submit([encoder.finish()]); + const bundle = encoder.finish(); + device.queue.submit([bundle]); await outputBuffer.mapAsync(1); const data = new Uint8Array(outputBuffer.getMappedRange()); |