summaryrefslogtreecommitdiff
path: root/tools/node_compat/setup.ts
diff options
context:
space:
mode:
Diffstat (limited to 'tools/node_compat/setup.ts')
-rwxr-xr-xtools/node_compat/setup.ts240
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();