Files
scriptc/tests/harness/runtime-resolver.test.ts
Chris Tate c08ab955ae Enforce linting and consistent source formatting
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks.
- Preserve compiler import boundaries and protected fixtures while cleaning up lint findings.
- Standardize source formatting and validate import, fixture, and embedded-source protections.
2026-10-05 17:51:24 -05:00

470 lines
15 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { tmpdir } from "node:os";
import { dirname, join, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";
import { expect, test } from "vitest";
import { analyze, compile } from "@scriptc/compiler";
import {
cjsResolvePaths,
resolveCjsRuntime,
} from "../../packages/compiler/src/frontend/cjs-resolve.js";
import { FrontendInputTracker } from "../../packages/compiler/src/frontend/input-tracker.js";
const root = fileURLToPath(new URL("../..", import.meta.url));
const entry = join(root, "tests/fixtures/self-hosting/runtime-resolver.ts");
interface Request {
from: string;
request: string;
paths: string[] | null;
lookup: boolean;
}
function makeCases(directory: string): Request[] {
const cases: Request[] = [];
const main = join(directory, "app/main.cjs");
function file(
path: string,
text = 'throw new Error("resolution must never evaluate code");',
): void {
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, text);
}
function pkg(
name: string,
config: unknown,
files: string[] = ["index.js"],
parent = "app/node_modules",
): string {
const path = join(directory, parent, name);
file(join(path, "package.json"), JSON.stringify(config));
for (const name of files) file(join(path, name));
return path;
}
function add(
requests: string[],
from = main,
paths: string[] | null = null,
lookup = false,
): void {
for (const request of requests) cases.push({ from, request, paths, lookup });
}
file(main);
file(
join(directory, "app/package.json"),
JSON.stringify({
name: "application",
exports: { ".": "./main.cjs", "./self": "./same.js", "./missing": "./absent.js" },
imports: {
"#same": "./same.js",
"#conditional": { types: "./wrong.d.ts", require: "./same.js", default: "./wrong.js" },
"#pattern/*": "./parts/*.js",
"#/prefix/*": "./parts/*.js",
"#blocked": null,
"#dependency": "dual",
"#dependency-subpath": "legacy/sub",
"#dependency-main": "legacy",
"#missing": "missing-package",
"#missing-file": "./absent.js",
"#builtin": "fs",
"#node-prefix": "node:fs",
"#invalid-name": ".",
"#outside": "../outside.js",
"#self": "application/self",
"#bad-main": "bad-main",
"#no-entry": "no-entry",
},
}),
);
for (const name of [
"same.js",
"same.json",
"same.node",
"data.json",
"addon.node",
"explicit.mjs",
"explicit.cjs",
"typed.ts",
"parts/a.js",
"hash#file.js",
"space name.js",
])
file(join(directory, "app", name));
file(join(directory, "app/folder/index.json"), "{}");
file(join(directory, "app/only-mjs/index.mjs"));
file(join(directory, "app/only-cjs/index.cjs"));
file(join(directory, "outside.js"));
pkg(
"dual",
{
exports: {
".": { types: "./wrong.d.ts", import: "./wrong.mjs", require: "./right.cjs" },
"./sub": "./feature.js",
},
},
["right.cjs", "feature.js", "wrong.d.ts", "wrong.mjs"],
);
pkg("legacy", { main: "start" }, ["start.js", "sub.js", "index.js"]);
pkg("module-field", { module: "wrong.mjs", main: "index.js" }, ["index.js", "wrong.mjs"]);
pkg("directory-main", { main: "entry" }, ["entry/index.json"]);
pkg("nested-main", { main: "entry" }, ["entry/index.js", "entry/wrong.js"]);
file(join(directory, "app/node_modules/nested-main/entry/package.json"), '{"main":"wrong.js"}');
pkg("bad-main", { main: "missing" }, []);
pkg("no-main", {}, ["index.node"]);
pkg("null-exports", { exports: null, main: "index.js" });
pkg("self-null", { name: "self-null", exports: null });
pkg("no-entry", {}, []);
for (const [index, value] of [null, 123, false, "str", [], {}].entries()) {
pkg(`imports-${index}`, { imports: value });
add(["#missing"], join(directory, `app/node_modules/imports-${index}/main.cjs`));
}
for (const [index, value] of [true, null, 123, [], "text"].entries()) {
pkg(`invalid-config-${index}`, value);
add([`invalid-config-${index}`]);
}
file(join(directory, "app/node_modules/bom/package.json"), '\ufeff{"main":"index.js"}');
file(join(directory, "app/node_modules/bom/index.js"));
file(join(directory, "app/node_modules/manifest-free/index.js"));
pkg("@scope/pkg", { exports: { ".": "./index.js", "./sub": "./sub.js" } }, [
"index.js",
"sub.js",
]);
pkg(
"patterns",
{
exports: {
"./*": "./general/*.js",
"./long/*": "./long/*.js",
"./long/*.js": "./specific/*.js",
"./exact": "./exact.js",
},
},
["general/a.js", "long/a.js", "specific/a.js", "exact.js"],
);
pkg(
"conditions",
{
exports: {
".": { node: { require: "./node.js" }, default: "./default.js" },
"./default-first": { default: "./default.js", require: "./node.js" },
"./null-first": { require: null, default: "./default.js" },
"./nested-unmatched": { node: { unknown: "./node.js" }, default: "./default.js" },
"./sync": { "module-sync": "./node.js", default: "./default.js" },
"./addons": { "node-addons": "./node.js", default: "./default.js" },
},
},
["node.js", "default.js"],
);
pkg("arrays", {
exports: {
".": ["../invalid", null, "./index.js"],
"./invalid": ["../bad", "../worse"],
"./null": ["../bad", null],
"./missing": ["./missing.js", "./index.js"],
"./unmatched": [{ unknown: "./missing.js" }, "./index.js"],
"./empty": [],
},
});
for (const [name, value] of [
["invalid-target", "outside"],
["invalid-number", 123],
["mixed-map", { ".": "./index.js", require: "./index.js" }],
["numeric-condition", { "0": "./index.js", require: "./index.js" }],
["encoded-separator", "./a%2fb.js"],
["traversal-target", "./x/../index.js"],
["encoded-traversal", "./%2e%2e/outside.js"],
["node-modules-target", "./node_modules/a.js"],
["directory-target", "./entry"],
["extension-target", "./index"],
["malformed-escape", "./%E0.js"],
] as const)
pkg(name, { exports: value }, ["index.js", "entry/index.js"]);
file(join(directory, "app/node_modules/malformed/package.json"), "{ broken");
pkg("scoped-errors", {
name: "scoped-errors",
exports: { "./num": { "0": "./index.js" }, "./mix": { require: "outside" } },
});
const linked = pkg("linked", { exports: "./index.js" }, ["index.js"], "store");
symlinkSync(
linked,
join(directory, "app/node_modules/linked"),
process.platform === "win32" ? "junction" : "dir",
);
pkg("preferred", { main: "index.js" }, ["index.js"], "vendor/node_modules");
pkg("preferred", { main: "index.js" }, ["index.js"]);
pkg("nested", {}, ["index.js"], "app/node_modules/outer/node_modules");
file(join(directory, "app/node_modules/outer/main.cjs"));
file(join(directory, "app/node_modules/outer/package.json"), '{"name":"outer"}');
file(join(directory, "standalone/main.cjs"));
add([
"fs",
"node:fs",
"node:test",
"node:missing",
"missing-package",
"./missing",
"./same",
"./same.js",
"./data",
"./addon",
"./explicit.mjs",
"./explicit",
"./explicit.cjs",
"./typed",
"./typed.ts",
"./folder",
"./folder/",
"./only-mjs",
"./only-cjs",
"./hash#file.js",
"./space name.js",
"../outside",
"./same/",
"",
".",
"..",
]);
add([join(directory, "app/same.js"), join(directory, "app/folder"), join(directory, "absent")]);
add([
"dual",
"dual/sub",
"dual/private",
"legacy",
"legacy/sub",
"module-field",
"directory-main",
"nested-main",
"bad-main",
"no-main",
"null-exports",
"manifest-free",
"@scope/pkg",
"@scope/pkg/sub",
"@scope/pkg/private",
"linked",
]);
add([
"patterns/a",
"patterns/long/a",
"patterns/long/a.js",
"patterns/exact",
"patterns/missing",
"patterns/../x",
"patterns/%2e%2e/x",
"patterns/node_modules/x",
]);
add([
"conditions",
"conditions/default-first",
"conditions/null-first",
"conditions/nested-unmatched",
"conditions/sync",
"conditions/addons",
]);
add([
"arrays",
"arrays/invalid",
"arrays/null",
"arrays/missing",
"arrays/unmatched",
"arrays/empty",
]);
add([
"invalid-target",
"invalid-number",
"mixed-map",
"numeric-condition",
"encoded-separator",
"traversal-target",
"encoded-traversal",
"node-modules-target",
"directory-target",
"extension-target",
"malformed",
"malformed-escape",
"bom",
]);
add(["application", "application/self", "application/private", "application/missing"]);
add([
"#same",
"#conditional",
"#pattern/a",
"#/prefix/a",
"#blocked",
"#dependency",
"#dependency-subpath",
"#dependency-main",
"#missing",
"#missing-file",
"#builtin",
"#node-prefix",
"#invalid-name",
"#outside",
"#self",
"#unknown",
"#",
"#pattern/",
]);
add(
["scoped-errors/num", "scoped-errors/mix"],
join(directory, "app/node_modules/scoped-errors/inside.cjs"),
);
add(["self-null"], join(directory, "app/node_modules/self-null/main.cjs"));
add(["#bad-main", "#no-entry"]);
add(["nested", "dual", "#same"], join(directory, "app/node_modules/outer/main.cjs"));
add(["preferred"], main, [join(directory, "vendor"), join(directory, "app")]);
add(["preferred"], main, [join(directory, "app"), join(directory, "vendor")]);
add(["preferred", "./same.js", "fs", join(directory, "app/same.js")], main, []);
add(["./same.js"], main, [join(directory, "app")]);
add(["#same", "application/self"], main, []);
add(["./same.js", "./absent"], join(directory, "app") + sep);
add(["./same.js"], join(directory, "app") + sep, null, true);
add(["#unknown", "#"], join(directory, "standalone/main.cjs"));
add(
[
"fs",
"node:test",
"./same",
"../other",
"...",
"..name",
".name",
"bare",
"@scope/pkg",
"/absolute",
"#same",
],
main,
null,
true,
);
add(
["nested", "../child", "#same"],
join(directory, "app/node_modules/outer/main.cjs"),
null,
true,
);
if (process.platform === "win32") {
for (const from of [
"C:\\main.cjs",
"C:\\work\\node_modules\\package\\main.cjs",
"\\\\server\\share\\work\\main.cjs",
"\\\\server\\share\\main.cjs",
]) {
add(["bare", "./file", "..\\file"], from, null, true);
}
}
return cases;
}
function oracle(request: Request): unknown {
const require = createRequire(request.from);
if (request.lookup) return require.resolve.paths(request.request);
try {
return {
ok: true,
value:
request.paths === null
? require.resolve(request.request)
: require.resolve(request.request, { paths: request.paths }),
};
} catch (error) {
const e = error as NodeJS.ErrnoException;
return { ok: false, error: { name: e.name, code: e.code ?? "", message: e.message } };
}
}
function workspace(): string {
return mkdtempSync(
join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-runtime-resolver-"),
);
}
test("filesystem runtime resolver agrees with pinned Node across package and path rules", () => {
const directory = workspace();
try {
for (const request of makeCases(directory)) {
const actual = request.lookup
? cjsResolvePaths(request.from, request.request)
: resolveCjsRuntime(request.from, request.request, request.paths ?? undefined);
expect.soft(actual, JSON.stringify(request)).toEqual(oracle(request));
}
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
test("runtime lookup tracks failed candidates as well as selected files", () => {
const directory = workspace();
try {
makeCases(directory);
const tracker = new FrontendInputTracker();
tracker.run(() => resolveCjsRuntime(join(directory, "app/main.cjs"), "./data"));
const probes = tracker.snapshot().probes;
expect(probes).toContainEqual({
op: "kind",
path: join(directory, "app/data.js"),
kind: "missing",
});
expect(probes).toContainEqual({
op: "realpath",
path: join(directory, "app/data.json"),
target: realpathSync(join(directory, "app/data.json")),
});
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
for (const backend of ["llvm"] as const) {
test(`native runtime resolver: ${backend} matches pinned Node without evaluating modules`, async () => {
const directory = workspace();
try {
const requests = makeCases(directory);
const input = join(directory, "requests.json");
writeFileSync(input, JSON.stringify(requests));
const { coverage } = analyze(entry, { dynamic: false });
expect(coverage.preflightFailed).toBe(false);
expect(coverage.stats.statementsFailed).toBe(0);
expect(coverage.stats.statementsIsland).toBe(0);
expect(coverage.stats.functionsSkipped).toBe(0);
const built = await compile(entry, {
outDir: directory,
outPath: join(directory, process.platform === "win32" ? "resolver.exe" : "resolver"),
backend,
dynamic: false,
sanitize: process.env["SCRIPTC_SAN"] === "1",
});
if (!built.ok)
throw new Error(built.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
const run = spawnSync(built.binaryPath, [input], {
cwd: root,
timeout: 30_000,
maxBuffer: 4 * 1024 * 1024,
});
expect(run.error).toBeUndefined();
expect(run.signal).toBeNull();
expect(run.status, run.stderr.toString()).toBe(0);
expect(run.stderr.toString()).toBe("");
const lines = run.stdout.toString().trimEnd().split("\n");
expect(lines).toHaveLength(requests.length);
for (const [index, request] of requests.entries()) {
const expected = oracle(request);
// The native host uses its own executable prefix for the last legacy
// global directory; all other search entries must match Node exactly.
if (request.lookup && Array.isArray(expected) && expected.length > 1) {
const executable = realpathSync(built.binaryPath);
const prefix =
process.platform === "win32" ? dirname(executable) : dirname(dirname(executable));
expected[expected.length - 1] = resolve(prefix, "lib", "node");
}
expect(JSON.parse(lines[index]!), JSON.stringify(request)).toEqual(expected);
}
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
}