diff options
Diffstat (limited to 'cli/js/tests/unit_test_runner.ts')
-rwxr-xr-x | cli/js/tests/unit_test_runner.ts | 257 |
1 files changed, 194 insertions, 63 deletions
diff --git a/cli/js/tests/unit_test_runner.ts b/cli/js/tests/unit_test_runner.ts index a5b7c3a48..f018fb59e 100755 --- a/cli/js/tests/unit_test_runner.ts +++ b/cli/js/tests/unit_test_runner.ts @@ -2,42 +2,187 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import "./unit_tests.ts"; import { + assert, + readLines, permissionCombinations, - parseUnitTestOutput, - Permissions + Permissions, + registerUnitTests, + SocketReporter, + fmtPerms } from "./test_util.ts"; -interface TestResult { - perms: string; - output?: string; - result: number; +interface PermissionSetTestResult { + perms: Permissions; + passed: boolean; + stats: Deno.TestStats; + permsStr: string; + duration: number; } -function permsToCliFlags(perms: Permissions): string[] { - return Object.keys(perms) - .map(key => { - if (!perms[key as keyof Permissions]) return ""; +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(args: string[]): Promise<void> { + const addrArg = args.find(e => e.includes("--addr")); + assert(typeof addrArg === "string", "Missing --addr argument"); + const addrStr = addrArg.split("=")[1]; + const [hostname, port] = addrStr.split(":"); + const addr = { hostname, port: Number(port) }; + + let perms: Deno.PermissionName[] = []; + const permsArg = args.find(e => e.includes("--perms")); + assert(typeof permsArg === "string", "Missing --perms argument"); + const permsStr = permsArg.split("=")[1]; + if (permsStr.length > 0) { + perms = permsStr.split(",") as Deno.PermissionName[]; + } + // Setup reporter + const conn = await Deno.connect(addr); + const socketReporter = new SocketReporter(conn); + // Drop current process permissions to requested set + await dropWorkerPermissions(perms); + // Register unit tests that match process permissions + await registerUnitTests(); + // Execute tests + await Deno.runTests({ + failFast: false, + exitOnFail: false, + reporter: socketReporter + }); + // Notify parent process we're done + socketReporter.close(); +} - const cliFlag = key.replace( - /\.?([A-Z])/g, - (x, y): string => `-${y.toLowerCase()}` - ); - return `--allow-${cliFlag}`; +function spawnWorkerRunner(addr: string, perms: Permissions): Deno.Process { + // run subsequent tests using same deno executable + const permStr = Object.keys(perms) + .filter((permName): boolean => { + return perms[permName as Deno.PermissionName] === true; }) - .filter((e): boolean => e.length > 0); + .join(","); + + const args = [ + Deno.execPath(), + "run", + "-A", + "cli/js/tests/unit_test_runner.ts", + "--", + "--worker", + `--addr=${addr}`, + `--perms=${permStr}` + ]; + + const p = Deno.run({ + args, + stdin: "null", + stdout: "piped", + stderr: "null" + }); + + return p; } -function fmtPerms(perms: Permissions): string { - let fmt = permsToCliFlags(perms).join(" "); +async function runTestsForPermissionSet( + reporter: Deno.ConsoleTestReporter, + perms: Permissions +): Promise<PermissionSetTestResult> { + const permsFmt = fmtPerms(perms); + console.log(`Running tests for: ${permsFmt}`); + const addr = { hostname: "127.0.0.1", port: 4510 }; + const addrStr = `${addr.hostname}:${addr.port}`; + const workerListener = Deno.listen(addr); + + const workerProcess = spawnWorkerRunner(addrStr, perms); + + // Wait for worker subprocess to go online + const conn = await workerListener.accept(); + + let err; + let hasThrown = false; + let expectedPassedTests; + let endEvent; + + try { + for await (const line of readLines(conn)) { + const msg = JSON.parse(line); + + if (msg.kind === Deno.TestEvent.Start) { + expectedPassedTests = msg.tests; + await reporter.start(msg); + continue; + } + + if (msg.kind === Deno.TestEvent.Result) { + await reporter.result(msg); + continue; + } + + endEvent = msg; + await reporter.end(msg); + break; + } + } catch (e) { + hasThrown = true; + err = e; + } finally { + workerListener.close(); + } + + if (hasThrown) { + throw err; + } - if (!fmt) { - fmt = "<no permissions>"; + if (typeof expectedPassedTests === "undefined") { + throw new Error("Worker runner didn't report start"); } - return fmt; + if (typeof endEvent === "undefined") { + 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 === endEvent.stats.passed; + + return { + perms, + passed, + permsStr: permsFmt, + duration: endEvent.duration, + stats: endEvent.stats + }; } -async function main(): Promise<void> { +async function masterRunnerMain(): Promise<void> { console.log( "Discovered permission combinations for tests:", permissionCombinations.size @@ -47,57 +192,31 @@ async function main(): Promise<void> { console.log("\t" + fmtPerms(perms)); } - const testResults = new Set<TestResult>(); + const testResults = new Set<PermissionSetTestResult>(); + const consoleReporter = new Deno.ConsoleTestReporter(); for (const perms of permissionCombinations.values()) { - const permsFmt = fmtPerms(perms); - console.log(`Running tests for: ${permsFmt}`); - const cliPerms = permsToCliFlags(perms); - // run subsequent tests using same deno executable - const args = [ - Deno.execPath(), - "run", - ...cliPerms, - "cli/js/tests/unit_tests.ts" - ]; - - const p = Deno.run({ - args, - stdout: "piped" - }); - - const { actual, expected, resultOutput } = await parseUnitTestOutput( - p.stdout!, - true - ); - - let result = 0; - - if (!actual && !expected) { - console.error("Bad cli/js/tests/unit_test.ts output"); - result = 1; - } else if (expected !== actual) { - result = 1; - } - - testResults.add({ - perms: permsFmt, - output: resultOutput, - result - }); + const result = await runTestsForPermissionSet(consoleReporter, perms); + testResults.add(result); } // if any run tests returned non-zero status then whole test // run should fail - let testsFailed = false; + let testsPassed = true; for (const testResult of testResults) { - console.log(`Summary for ${testResult.perms}`); - console.log(testResult.output + "\n"); - testsFailed = testsFailed || Boolean(testResult.result); + const { permsStr, stats, duration } = testResult; + console.log(`Summary for ${permsStr}`); + await consoleReporter.end({ + kind: Deno.TestEvent.End, + stats, + duration, + results: [] + }); + testsPassed = testsPassed && testResult.passed; } - if (testsFailed) { + if (!testsPassed) { console.error("Unit tests failed"); Deno.exit(1); } @@ -105,4 +224,16 @@ async function main(): Promise<void> { console.log("Unit tests passed"); } +async function main(): Promise<void> { + const args = Deno.args; + + const isWorker = args.includes("--worker"); + + if (isWorker) { + return await workerRunnerMain(args); + } + + return await masterRunnerMain(); +} + main(); |