diff options
Diffstat (limited to 'tools/node_compat/setup.ts')
-rwxr-xr-x | tools/node_compat/setup.ts | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/tools/node_compat/setup.ts b/tools/node_compat/setup.ts new file mode 100755 index 000000000..3cac72019 --- /dev/null +++ b/tools/node_compat/setup.ts @@ -0,0 +1,240 @@ +#!/usr/bin/env -S deno run --allow-read=. --allow-write=. --allow-net=nodejs.org +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** This script downloads Node.js source tarball, extracts it and copies the + * test files according to the config file `cli/tests/node_compat/config.json` + */ + +import { Foras, gunzip } from "https://deno.land/x/denoflate@2.0.2/deno/mod.ts"; +import { Untar } from "../../test_util/std/archive/untar.ts"; +import { walk } from "../../test_util/std/fs/walk.ts"; +import { + dirname, + fromFileUrl, + join, + sep, +} from "../../test_util/std/path/mod.ts"; +import { ensureFile } from "../../test_util/std/fs/ensure_file.ts"; +import { Buffer } from "../../test_util/std/io/buffer.ts"; +import { copy } from "../../test_util/std/streams/copy.ts"; +import { readAll } from "../../test_util/std/streams/read_all.ts"; +import { writeAll } from "../../test_util/std/streams/write_all.ts"; +import { withoutAll } from "../../test_util/std/collections/without_all.ts"; +import { relative } from "../../test_util/std/path/posix.ts"; + +import { config, ignoreList } from "../../cli/tests/node_compat/common.ts"; + +const encoder = new TextEncoder(); + +const NODE_VERSION = config.nodeVersion; +const NODE_NAME = "node-v" + NODE_VERSION; +const NODE_ARCHIVE_NAME = `${NODE_NAME}.tar.gz`; + +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 NODE_TARBALL_URL = + `https://nodejs.org/dist/v${NODE_VERSION}/${NODE_ARCHIVE_NAME}`; +const NODE_VERSIONS_ROOT = new URL("versions/", import.meta.url); +const NODE_TARBALL_LOCAL_URL = new URL(NODE_ARCHIVE_NAME, NODE_VERSIONS_ROOT); +// local dir url where we copy the node tests +const NODE_LOCAL_ROOT_URL = new URL(NODE_NAME, NODE_VERSIONS_ROOT); +const NODE_LOCAL_TEST_URL = new URL(NODE_NAME + "/test/", NODE_VERSIONS_ROOT); +const NODE_COMPAT_TEST_DEST_URL = new URL( + "../../cli/tests/node_compat/test/", + import.meta.url, +); + +Foras.initSyncBundledOnce(); + +async function getNodeTests(): Promise<string[]> { + const paths: string[] = []; + const rootPath = NODE_LOCAL_TEST_URL.href.slice(7); + for await ( + const item of walk(NODE_LOCAL_TEST_URL, { 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() { + const 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(`<!-- deno-fmt-ignore-file --> +# Remaining Node Tests + +NOTE: This file should not be manually edited. Please edit 'cli/tests/node_compat/config.json' and run 'tools/node_compat/setup.ts' 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`, + ), + ); + } + file.close(); +} + +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); + } +} + +async function decompressTests() { + console.log(`Decompressing ${NODE_ARCHIVE_NAME}...`); + + const compressedFile = await Deno.open(NODE_TARBALL_LOCAL_URL); + + const buffer = new Buffer(gunzip(await readAll(compressedFile))); + compressedFile.close(); + + const tar = new Untar(buffer); + const outFolder = dirname(fromFileUrl(NODE_TARBALL_LOCAL_URL)); + const testsFolder = `${NODE_NAME}/test`; + + for await (const entry of tar) { + if (entry.type !== "file") continue; + if (!entry.fileName.startsWith(testsFolder)) continue; + const path = join(outFolder, entry.fileName); + await ensureFile(path); + const file = await Deno.open(path, { + create: true, + truncate: true, + write: true, + }); + await copy(entry, file); + file.close(); + } +} + +/** 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(NODE_LOCAL_TEST_URL, { skip: ignoreList })) { + const fragments = entry.path.split(sep); + // suite is the directory name after test/. For example, if the file is + // "node-v18.12.1/test/fixtures/policy/main.mjs" + // then suite is "fixtures/policy" + const suite = fragments.slice(fragments.indexOf(NODE_NAME) + 2, -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}`, NODE_LOCAL_TEST_URL), + ); + 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 "node/_tools/setup.ts". Do not modify this file manually + +`), + ); + await srcFile.readable.pipeTo(destFile.writable); + } +} + +/** Downloads Node tarball */ +async function downloadFile() { + console.log( + `Downloading ${NODE_TARBALL_URL} in "${NODE_TARBALL_LOCAL_URL}" ...`, + ); + const response = await fetch(NODE_TARBALL_URL); + if (!response.ok) { + throw new Error(`Request failed with status ${response.status}`); + } + await ensureFile(NODE_TARBALL_LOCAL_URL); + const file = await Deno.open(NODE_TARBALL_LOCAL_URL, { + truncate: true, + write: true, + create: true, + }); + await response.body.pipeTo(file.writable); +} + +// main + +try { + Deno.lstatSync(NODE_TARBALL_LOCAL_URL); +} catch (e) { + if (!(e instanceof Deno.errors.NotFound)) { + throw e; + } + await downloadFile(); +} + +try { + Deno.lstatSync(NODE_LOCAL_ROOT_URL); +} catch (e) { + if (!(e instanceof Deno.errors.NotFound)) { + throw e; + } + await decompressTests(); +} + +await clearTests(); +await copyTests(); +await updateToDo(); |