deno/tests/node_compat/common.ts

204 lines
5.2 KiB
TypeScript

// Copyright 2018-2025 the Deno authors. MIT license.
import { partition } from "@std/collections/partition";
import { join } from "@std/path";
import * as JSONC from "@std/jsonc";
import { walk } from "@std/fs/walk";
import { relative } from "@std/path/posix/relative";
/**
* The test suite matches the folders inside the `test` folder inside the
* node repo
*
* Each test suite contains a list of files (which can be paths
* or a regex to match) that will be pulled from the node repo
*/
type TestSuites = Record<string, string[]>;
interface Config {
/** Ignored files won't regenerated by the update script */
ignore: TestSuites;
/**
* The files that will be run by the test suite
*
* The files to be generated with the update script must be listed here as well,
* but they won't be regenerated if they are listed in the `ignore` configuration
*/
tests: TestSuites;
windowsIgnore: TestSuites;
darwinIgnore: TestSuites;
}
export const config: Config = JSONC.parse(
await Deno.readTextFile(new URL("./config.jsonc", import.meta.url)),
) as unknown as Config;
export const ignoreList = Object.entries(config.ignore).reduce(
(total: RegExp[], [suite, paths]) => {
paths.forEach((path) => total.push(new RegExp(join(suite, path))));
return total;
},
[/package\.json/],
);
export function getPathsFromTestSuites(suites: TestSuites): string[] {
const testPaths: string[] = [];
for (const [dir, paths] of Object.entries(suites)) {
if (
["parallel", "internet", "pummel", "sequential", "pseudo-tty"].includes(
dir,
)
) {
for (const path of paths) {
testPaths.push(join(dir, path));
}
}
}
return testPaths;
}
const PARALLEL_PATTERN = /^parallel[\/\\]/;
export function partitionParallelTestPaths(
testPaths: string[],
): { parallel: string[]; sequential: string[] } {
const partitions = partition(testPaths, (p) => !!p.match(PARALLEL_PATTERN));
return { parallel: partitions[0], sequential: partitions[1] };
}
export const NODE_IGNORED_TEST_DIRS = [
"addons",
"async-hooks",
"cctest",
"common",
"doctool",
"embedding",
"fixtures",
"fuzzers",
"js-native-api",
"node-api",
"overlapped-checker",
"report",
"testpy",
"tick-processor",
"tools",
"v8-updates",
"wasi",
"wpt",
];
export const VENDORED_NODE_TEST = new URL(
"./runner/suite/test/",
import.meta.url,
);
export const NODE_COMPAT_TEST_DEST_URL = new URL(
"./test/",
import.meta.url,
);
export async function getNodeTests(): Promise<string[]> {
const paths: string[] = [];
const rootPath = VENDORED_NODE_TEST.href.slice(7);
for await (
const item of walk(VENDORED_NODE_TEST, { exts: [".js"] })
) {
const path = relative(rootPath, item.path);
if (NODE_IGNORED_TEST_DIRS.every((dir) => !path.startsWith(dir))) {
paths.push(path);
}
}
return paths.sort();
}
export async function getDenoTests() {
const paths: string[] = [];
const rootPath = NODE_COMPAT_TEST_DEST_URL.href.slice(7);
for await (
const item of walk(NODE_COMPAT_TEST_DEST_URL, { exts: [".js"] })
) {
const path = relative(rootPath, item.path);
paths.push(path);
}
return paths.sort();
}
let testSerialId = 0;
const cwd = new URL(".", import.meta.url);
/** Checks if the test file uses `node:test` module */
export function usesNodeTestModule(testSource: string): boolean {
return testSource.includes("'node:test'");
}
export const RUN_ARGS = [
"-A",
"--quiet",
"--unstable-unsafe-proto",
"--unstable-bare-node-builtins",
"--unstable-node-globals",
];
export const TEST_ARGS = [
"test",
...RUN_ARGS,
"--no-check",
"--unstable-detect-cjs",
];
export async function runNodeCompatTestCase(
testCase: string,
signal?: AbortSignal,
) {
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",
"createExternalizableTwoByteString",
);
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(",");
}
const usesNodeTest = usesNodeTestModule(testSource);
const args = [
...(usesNodeTest ? TEST_ARGS : RUN_ARGS),
"--v8-flags=" + v8Flags.join(),
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
return new Deno.Command(Deno.execPath(), {
args,
env: {
TEST_SERIAL_ID: String(testSerialId++),
...envVars,
},
cwd,
stdout: "piped",
stderr: "piped",
signal,
}).spawn();
}
/** Parses the special "Flags:"" syntax in Node.js test files */
export function parseFlags(source: string): string[] {
const line = /^\/\/ Flags: (.+)$/um.exec(source);
if (line == null) return [];
return line[1].split(" ");
}