// Copyright 2018-2024 the Deno authors. All rights reserved. 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; 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 { 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); export async function runNodeCompatTestCase( testCase: string, signal?: AbortSignal, ) { const v8Flags = ["--stack-size=4000"]; const testSource = await Deno.readTextFile(testCase); const envVars: Record = {}; 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", "--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 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 */ function parseFlags(source: string): string[] { const line = /^\/\/ Flags: (.+)$/um.exec(source); if (line == null) return []; return line[1].split(" "); }