0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

Update CLI for unit_test_runner.ts (#4352)

* drop server guard before unit test result check

To prevent cascading test failures when js_unit_test http server
guard is dropped before asserting that tests were successful.
This is really a band-aid and doesn't solve underlying issue with
http server.

* Update CLI for unit_test_runner.ts

* Change cli/js/tests/unit_test_runner.ts command line interface to work in 3
  modes:
  - "one-off" - run tests that match permissions of currently running
    process
  - "master" - run tests for all possible permission combinations, by
   spawning subprocesses running in "worker" mode and communicating via
   TCP socket; requires elevated permissions
  - "worker" - run tests for set of permissions provided by CLI arg;
  requires elevated permissions to setup TCP connection to "master";
  after initial setup process drops permissions to given set

* Support filtering of tests by string passed after "--" CLI arg

* Update cli/js/tests/README.md
This commit is contained in:
Bartek Iwańczuk 2020-03-14 11:53:20 +01:00 committed by GitHub
parent 0f6acf2753
commit d6bbbdda75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 171 additions and 31 deletions

View file

@ -37,11 +37,58 @@ ways:
- sanitization of async ops - ensuring that tests don't leak async ops by - sanitization of async ops - ensuring that tests don't leak async ops by
ensuring that all started async ops are done before test finishes ensuring that all started async ops are done before test finishes
`unit_test_runner.ts` is main script used to run unit tests. ## Running tests
`unit_test_runner.ts` is the main script used to run unit tests.
Runner discoveres required permissions combinations by loading Runner discoveres required permissions combinations by loading
`cli/js/tests/unit_tests.ts` and going through all registered instances of `cli/js/tests/unit_tests.ts` and going through all registered instances of
`unitTest`. For each discovered permission combination a new Deno process is `unitTest`.
created with respective `--allow-*` flags which loads
`cli/js/tests/unit_tests.ts` and executes all `unitTest` that match runtime There are three ways to run `unit_test_runner.ts`:
permissions.
- run tests matching current process permissions
```
// run tests that don't require any permissions
target/debug/deno unit_test_runner.ts
// run tests with "net" permission
target/debug/deno --allow-net unit_test_runner.ts
target/debug/deno --allow-net --allow-read unit_test_runner.ts
```
- run all tests - "master" mode, that spawns worker processes for each
discovered permission combination:
```
target/debug/deno -A unit_test_runner.ts --master
```
By default all output of worker processes is discarded; for debug purposes
`--verbose` flag can be provided to preserve output from worker
```
target/debug/deno -A unit_test_runner.ts --master --verbose
```
- "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 -A unit_test_runner.ts --worker --addr=127.0.0.1:4500 --perms=net,write,run
```
### Filtering
Runner supports basic test filtering by name:
```
target/debug/deno unit_test_runner.ts -- netAccept
target/debug/deno -A unit_test_runner.ts --master -- netAccept
```
Filter string must be specified after "--" argument

View file

@ -13,6 +13,7 @@ export {
fail fail
} from "../../../std/testing/asserts.ts"; } from "../../../std/testing/asserts.ts";
export { readLines } from "../../../std/io/bufio.ts"; export { readLines } from "../../../std/io/bufio.ts";
export { parse as parseArgs } from "../../../std/flags/mod.ts";
export interface Permissions { export interface Permissions {
read: boolean; read: boolean;

View file

@ -2,13 +2,13 @@
// 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 {
assert,
readLines, readLines,
permissionCombinations, permissionCombinations,
Permissions, Permissions,
registerUnitTests, registerUnitTests,
SocketReporter, SocketReporter,
fmtPerms fmtPerms,
parseArgs
} from "./test_util.ts"; } from "./test_util.ts";
interface PermissionSetTestResult { interface PermissionSetTestResult {
@ -44,17 +44,15 @@ async function dropWorkerPermissions(
} }
} }
async function workerRunnerMain(args: string[]): Promise<void> { async function workerRunnerMain(
const addrArg = args.find(e => e.includes("--addr")); addrStr: string,
assert(typeof addrArg === "string", "Missing --addr argument"); permsStr: string,
const addrStr = addrArg.split("=")[1]; filter?: string
): Promise<void> {
const [hostname, port] = addrStr.split(":"); const [hostname, port] = addrStr.split(":");
const addr = { hostname, port: Number(port) }; const addr = { hostname, port: Number(port) };
let perms: Deno.PermissionName[] = []; 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) { if (permsStr.length > 0) {
perms = permsStr.split(",") as Deno.PermissionName[]; perms = permsStr.split(",") as Deno.PermissionName[];
} }
@ -69,13 +67,19 @@ async function workerRunnerMain(args: string[]): Promise<void> {
await Deno.runTests({ await Deno.runTests({
failFast: false, failFast: false,
exitOnFail: false, exitOnFail: false,
reporter: socketReporter reporter: socketReporter,
only: filter
}); });
// Notify parent process we're done // Notify parent process we're done
socketReporter.close(); socketReporter.close();
} }
function spawnWorkerRunner(addr: string, perms: Permissions): Deno.Process { function spawnWorkerRunner(
verbose: boolean,
addr: string,
perms: Permissions,
filter?: string
): Deno.Process {
// run subsequent tests using same deno executable // run subsequent tests using same deno executable
const permStr = Object.keys(perms) const permStr = Object.keys(perms)
.filter((permName): boolean => { .filter((permName): boolean => {
@ -88,25 +92,33 @@ function spawnWorkerRunner(addr: string, perms: Permissions): Deno.Process {
"run", "run",
"-A", "-A",
"cli/js/tests/unit_test_runner.ts", "cli/js/tests/unit_test_runner.ts",
"--",
"--worker", "--worker",
`--addr=${addr}`, `--addr=${addr}`,
`--perms=${permStr}` `--perms=${permStr}`
]; ];
if (filter) {
args.push("--");
args.push(filter);
}
const ioMode = verbose ? "inherit" : "null";
const p = Deno.run({ const p = Deno.run({
args, args,
stdin: "null", stdin: ioMode,
stdout: "piped", stdout: ioMode,
stderr: "null" stderr: ioMode
}); });
return p; return p;
} }
async function runTestsForPermissionSet( async function runTestsForPermissionSet(
verbose: boolean,
reporter: Deno.ConsoleTestReporter, reporter: Deno.ConsoleTestReporter,
perms: Permissions perms: Permissions,
filter?: string
): Promise<PermissionSetTestResult> { ): Promise<PermissionSetTestResult> {
const permsFmt = fmtPerms(perms); const permsFmt = fmtPerms(perms);
console.log(`Running tests for: ${permsFmt}`); console.log(`Running tests for: ${permsFmt}`);
@ -114,7 +126,7 @@ async function runTestsForPermissionSet(
const addrStr = `${addr.hostname}:${addr.port}`; const addrStr = `${addr.hostname}:${addr.port}`;
const workerListener = Deno.listen(addr); const workerListener = Deno.listen(addr);
const workerProcess = spawnWorkerRunner(addrStr, perms); const workerProcess = spawnWorkerRunner(verbose, addrStr, perms, filter);
// Wait for worker subprocess to go online // Wait for worker subprocess to go online
const conn = await workerListener.accept(); const conn = await workerListener.accept();
@ -182,7 +194,10 @@ async function runTestsForPermissionSet(
}; };
} }
async function masterRunnerMain(): Promise<void> { async function masterRunnerMain(
verbose: boolean,
filter?: string
): Promise<void> {
console.log( console.log(
"Discovered permission combinations for tests:", "Discovered permission combinations for tests:",
permissionCombinations.size permissionCombinations.size
@ -196,7 +211,12 @@ async function masterRunnerMain(): Promise<void> {
const consoleReporter = new Deno.ConsoleTestReporter(); const consoleReporter = new Deno.ConsoleTestReporter();
for (const perms of permissionCombinations.values()) { for (const perms of permissionCombinations.values()) {
const result = await runTestsForPermissionSet(consoleReporter, perms); const result = await runTestsForPermissionSet(
verbose,
consoleReporter,
perms,
filter
);
testResults.add(result); testResults.add(result);
} }
@ -224,16 +244,87 @@ async function masterRunnerMain(): Promise<void> {
console.log("Unit tests passed"); console.log("Unit tests passed");
} }
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> { async function main(): Promise<void> {
const args = Deno.args; const args = parseArgs(Deno.args, {
boolean: ["master", "worker", "verbose"],
"--": true
});
const isWorker = args.includes("--worker"); if (args.help) {
console.log(HELP);
if (isWorker) { return;
return await workerRunnerMain(args);
} }
return await masterRunnerMain(); const filter = args["--"][0];
// Master mode
if (args.master) {
return await masterRunnerMain(args.verbose, filter);
}
// Worker mode
if (args.worker) {
assertOrHelp(typeof args.addr === "string");
assertOrHelp(typeof args.perms === "string");
return await workerRunnerMain(args.addr, args.perms, filter);
}
// Running tests matching current process permissions
await registerUnitTests();
await Deno.runTests({
failFast: false,
exitOnFail: true,
only: filter
});
} }
main(); main();

View file

@ -274,12 +274,13 @@ fn js_unit_tests() {
.arg("--reload") .arg("--reload")
.arg("-A") .arg("-A")
.arg("cli/js/tests/unit_test_runner.ts") .arg("cli/js/tests/unit_test_runner.ts")
.arg("--master")
.spawn() .spawn()
.expect("failed to spawn script"); .expect("failed to spawn script");
let status = deno.wait().expect("failed to wait for the child process"); let status = deno.wait().expect("failed to wait for the child process");
drop(g);
assert_eq!(Some(0), status.code()); assert_eq!(Some(0), status.code());
assert!(status.success()); assert!(status.success());
drop(g);
} }
#[test] #[test]