1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-28 16:20:57 -05:00
denoland-deno/tests/node_compat/test.ts
2024-08-20 15:14:37 -04:00

210 lines
6.6 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// deno-lint-ignore-file no-console
/**
* This script will run the test files specified in the configuration file.
*
* Each test file will be run independently (in a separate process as this is
* what Node.js is doing) and we wait until it completes. If the process reports
* an abnormal code, the test is reported and the test suite will fail
* immediately.
*
* Some tests check for presence of certain `process.exitCode`.
* Some tests depends on directories/files created by other tests - they must
* all share the same working directory.
*/
import { magenta } from "@std/fmt/colors";
import { pooledMap } from "@std/async/pool";
import { dirname, fromFileUrl, join } from "@std/path";
import { assertEquals, fail } from "@std/assert";
import {
config,
getPathsFromTestSuites,
partitionParallelTestPaths,
} from "./common.ts";
// If the test case is invoked like
// deno test -A tests/node_compat/test.ts -- <test-names>
// Use the <test-names> as filters
const filters = Deno.args;
const hasFilters = filters.length > 0;
const toolsPath = dirname(fromFileUrl(import.meta.url));
const testPaths = partitionParallelTestPaths(
getPathsFromTestSuites(config.tests).concat(
getPathsFromTestSuites(config.ignore),
),
);
const cwd = new URL(".", import.meta.url);
const windowsIgnorePaths = new Set(
getPathsFromTestSuites(config.windowsIgnore),
);
const darwinIgnorePaths = new Set(
getPathsFromTestSuites(config.darwinIgnore),
);
const decoder = new TextDecoder();
let testSerialId = 0;
function parseFlags(source: string): string[] {
const line = /^\/\/ Flags: (.+)$/um.exec(source);
if (line == null) return [];
return line[1].split(" ");
}
async function runTest(t: Deno.TestContext, path: string): Promise<void> {
// If filter patterns are given and any pattern doesn't match
// to the file path, then skip the case
if (
filters.length > 0 &&
filters.every((pattern) => !path.includes(pattern))
) {
return;
}
const ignore =
(Deno.build.os === "windows" && windowsIgnorePaths.has(path)) ||
(Deno.build.os === "darwin" && darwinIgnorePaths.has(path));
await t.step({
name: `Node.js compatibility "${path}"`,
ignore,
sanitizeOps: false,
sanitizeResources: false,
sanitizeExit: false,
fn: async () => {
const testCase = join(toolsPath, "test", path);
const v8Flags = ["--stack-size=4000"];
const testSource = await Deno.readTextFile(testCase);
const envVars: Record<string, string> = {};
const knownGlobals: string[] = [];
parseFlags(testSource).forEach((flag) => {
switch (flag) {
case "--expose_externalize_string":
v8Flags.push("--expose-externalize-string");
knownGlobals.push("createExternalizableString");
break;
case "--expose-gc":
v8Flags.push("--expose-gc");
knownGlobals.push("gc");
break;
default:
break;
}
});
if (knownGlobals.length > 0) {
envVars["NODE_TEST_KNOWN_GLOBALS"] = knownGlobals.join(",");
}
// TODO(nathanwhit): once we match node's behavior on executing
// `node:test` tests when we run a file, we can remove this
const usesNodeTest = testSource.includes("node:test");
const args = [
usesNodeTest ? "test" : "run",
"-A",
"--quiet",
//"--unsafely-ignore-certificate-errors",
"--unstable-unsafe-proto",
"--unstable-bare-node-builtins",
"--unstable-fs",
"--v8-flags=" + v8Flags.join(),
];
if (usesNodeTest) {
// deno test typechecks by default + we want to pass script args
args.push("--no-check", "runner.ts", "--", testCase);
} else {
args.push("runner.ts", testCase);
}
// Pipe stdout in order to output each test result as Deno.test output
// That way the tests will respect the `--quiet` option when provided
const command = new Deno.Command(Deno.execPath(), {
args,
env: {
TEST_SERIAL_ID: String(testSerialId++),
...envVars,
},
cwd,
stdout: "piped",
stderr: "piped",
}).spawn();
const warner = setTimeout(() => {
console.error(`Test is running slow: ${testCase}`);
}, 2 * 60_000);
const killer = setTimeout(() => {
console.error(
`Test ran far too long, terminating with extreme prejudice: ${testCase}`,
);
command.kill();
}, 10 * 60_000);
const { code, stdout, stderr } = await command.output();
clearTimeout(warner);
clearTimeout(killer);
if (code !== 0) {
// If the test case failed, show the stdout, stderr, and instruction
// for repeating the single test case.
if (stdout.length) {
console.log(decoder.decode(stdout));
}
const stderrOutput = decoder.decode(stderr);
const repeatCmd = magenta(
`./target/debug/deno test --config tests/config/deno.json -A tests/node_compat/test.ts -- ${path}`,
);
const msg = `"${magenta(path)}" failed:
${stderrOutput}
You can repeat only this test with the command:
${repeatCmd}
`;
console.log(msg);
fail(msg);
} else if (hasFilters) {
// Even if the test case is successful, shows the stdout and stderr
// when test case filtering is specified.
if (stdout.length) console.log(decoder.decode(stdout));
if (stderr.length) console.log(decoder.decode(stderr));
}
},
});
}
Deno.test("Node.js compatibility", async (t) => {
for (const path of testPaths.sequential) {
await runTest(t, path);
}
const testPool = pooledMap(
navigator.hardwareConcurrency,
testPaths.parallel,
(path) => runTest(t, path),
);
const testCases = [];
for await (const testCase of testPool) {
testCases.push(testCase);
}
await Promise.all(testCases);
});
function checkConfigTestFilesOrder(testFileLists: Array<string[]>) {
for (const testFileList of testFileLists) {
const sortedTestList = JSON.parse(JSON.stringify(testFileList));
sortedTestList.sort((a: string, b: string) =>
a.toLowerCase().localeCompare(b.toLowerCase())
);
assertEquals(
testFileList,
sortedTestList,
"File names in `config.json` are not correct order.",
);
}
}
if (!hasFilters) {
Deno.test("checkConfigTestFilesOrder", function () {
checkConfigTestFilesOrder([
...Object.keys(config.ignore).map((suite) => config.ignore[suite]),
...Object.keys(config.tests).map((suite) => config.tests[suite]),
]);
});
}