#!/usr/bin/env -S deno run --allow-read=. --allow-write=. --allow-run=git --config=tests/config/deno.json // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // deno-lint-ignore-file no-console /** This copies the test files according to the config file `tests/node_compat/config.jsonc` */ import { walk } from "@std/fs/walk"; import { SEPARATOR } from "@std/path/constants"; import { ensureFile } from "@std/fs/ensure-file"; import { writeAll } from "@std/io/write-all"; import { withoutAll } from "@std/collections/without-all"; import { relative } from "@std/path/posix/relative"; import { version } from "./suite/node_version.ts"; import { config, ignoreList } from "../common.ts"; const encoder = new TextEncoder(); const NODE_VERSION = version; 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("./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 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 nodeTests = await getNodeTests(); const portedTests = getDenoTests(); const remainingTests = withoutAll(nodeTests, portedTests); const numPorted = portedTests.length; const numMissing = remainingTests.length; const numTotal = nodeTests.length; const portedPercentage = (numPorted / numTotal * 100).toFixed(2); const remainingPercentage = (numMissing / numTotal * 100).toFixed(2); await file.write(encoder.encode(` # Remaining Node Tests ${numPorted} tests out of ${numTotal} have been ported from Node ${NODE_VERSION} (${portedPercentage}% ported, ${remainingPercentage}% remaining). NOTE: This file should not be manually edited. Please edit \`tests/node_compat/config.json\` and run \`deno task setup\` in \`tests/node_compat/runner\` dir instead. `)); for (const test of remainingTests) { 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(SEPARATOR); // 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") + 4, -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 \`tests/node_compat/runner/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); } }