#!/usr/bin/env -S deno run --allow-read=. --allow-write=. --allow-run=git // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. /** This copies the test files according to the config file `tests/node_compat/config.jsonc` */ import { walk } from "@std/fs/walk.ts"; import { sep } from "@std/path/mod.ts"; import { ensureFile } from "@std/fs/ensure_file.ts"; import { writeAll } from "@std/streams/write_all.ts"; import { withoutAll } from "@std/collections/without_all.ts"; import { relative } from "@std/path/posix.ts"; import { config, ignoreList } from "../../tests/node_compat/common.ts"; const encoder = new TextEncoder(); const NODE_VERSION = config.nodeVersion; 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", ]; const VENDORED_NODE_TEST = new URL("node/test/", import.meta.url); const NODE_COMPAT_TEST_DEST_URL = new URL( "../../tests/node_compat/test/", import.meta.url, ); 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(); } function getDenoTests() { return Object.entries(config.tests) .filter(([testDir]) => !NODE_IGNORED_TEST_DIRS.includes(testDir)) .flatMap(([testDir, tests]) => tests.map((test) => testDir + "/" + test)); } async function updateToDo() { using file = await Deno.open(new URL("./TODO.md", import.meta.url), { write: true, create: true, truncate: true, }); const missingTests = withoutAll(await getNodeTests(), await getDenoTests()); await file.write(encoder.encode(` # Remaining Node Tests NOTE: This file should not be manually edited. Please edit \`tests/node_compat/config.json\` and run \`deno task setup\` in \`tools/node_compat\` dir instead. Total: ${missingTests.length} `)); for (const test of missingTests) { await file.write( encoder.encode( `- [${test}](https://github.com/nodejs/node/tree/v${NODE_VERSION}/test/${test})\n`, ), ); } } async function clearTests() { console.log("Cleaning up previous tests"); for await ( const file of walk(NODE_COMPAT_TEST_DEST_URL, { includeDirs: false, skip: ignoreList, }) ) { await Deno.remove(file.path); } } /** Checks if file has entry in config.json */ function hasEntry(file: string, suite: string) { return Array.isArray(config.tests[suite]) && config.tests[suite].includes(file); } async function copyTests() { console.log("Copying test files..."); for await (const entry of walk(VENDORED_NODE_TEST, { skip: ignoreList })) { const fragments = entry.path.split(sep); // suite is the directory name after test/. For example, if the file is // "node_compat/node/test/fixtures/policy/main.mjs" // then suite is "fixtures/policy" const suite = fragments.slice(fragments.indexOf("node_compat") + 3, -1) .join("/"); if (!hasEntry(entry.name, suite)) { continue; } const dest = new URL(`${suite}/${entry.name}`, NODE_COMPAT_TEST_DEST_URL); await ensureFile(dest); const destFile = await Deno.open(dest, { create: true, truncate: true, write: true, }); const srcFile = await Deno.open( new URL(`${suite}/${entry.name}`, VENDORED_NODE_TEST), ); // Add header to js files if (dest.pathname.endsWith("js")) { await writeAll( destFile, encoder.encode(`// deno-fmt-ignore-file // deno-lint-ignore-file // Copyright Joyent and Node contributors. All rights reserved. MIT license. // Taken from Node ${NODE_VERSION} // This file is automatically generated by \`tools/node_compat/setup.ts\`. Do not modify this file manually. `), ); } await srcFile.readable.pipeTo(destFile.writable); } } // main await clearTests(); await copyTests(); await updateToDo(); if (Deno.args[0] === "--check") { const cmd = new Deno.Command("git", { args: ["status", "-s"] }); const { stdout } = await cmd.output(); if (stdout.length > 0) { console.log("The following files have been changed:"); console.log(new TextDecoder().decode(stdout)); Deno.exit(1); } }