mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
path.posix, path.win32 and the bare path module are one module object
each, the handle process.getBuiltinModule answers, so require('path/posix'),
path.posix and the target platform's bare path compare as Node's do, a
namespace beside other values in one slot is untyped, and path[family]
reads that object. Functions read through them take checked arguments:
each string parameter passes Node's validateString under its own name
(basename's suffix first), resolve validates paths[i] only as far as its
last-first walk reads them, parse answers an untyped { root, dir, base,
ext, name }, format follows _format over any object, and
toNamespacedPath answers a non-string as it is. A JavaScript call whose
arguments are not strings, or omit one, goes through the same checks.
Typed slots keep the typed function values.
path.win32.relative lowercases both resolved paths with
String.prototype.toLowerCase and walks them by UTF-16 unit, comparing
segments instead when lowercasing changed a length, as Node does. It
folded ASCII only, so a path with İ or Ⱥ in a different case gave a
longer relative path. The function moves into its own runtime unit,
scr_path_win32_relative.c, which links with the regex unit whose case
mapping it uses. scr_path.c stays free of it, so library and
cross-target builds that link the path functions without the regex unit
still link.
A JavaScript binding whose inferred type holds a union of functions and
arrays (a test table of [fn, cases] rows, or one destructured row) takes
the checked-dynamic kind that dynFallbackType gives the unmappable
spellings of such tables. Its rows keep their functions' names, and
for-of, forEach and destructuring read the items as untyped values.
TypeScript bindings keep their typed layout.
75 lines
3.0 KiB
TypeScript
75 lines
3.0 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { mkdir } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
import { beforeAll, expect, test } from "vitest";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const testDir = import.meta.dirname;
|
|
const bin = join(testDir, "build", "test_path");
|
|
const vendorDir = join(testDir, "../vendor/quickjs-ng");
|
|
|
|
// Compiles the C-side oracle test once. Built with ASan + the RC audit so
|
|
// the oracle run also proves the win32 path functions neither leak nor
|
|
// double-free across ~38k calls.
|
|
beforeAll(async () => {
|
|
await mkdir(join(testDir, "build"), { recursive: true });
|
|
await execFileAsync("clang", [
|
|
"-std=c11",
|
|
"-O1",
|
|
"-Wall",
|
|
"-Wextra",
|
|
"-fsanitize=address",
|
|
"-DSCR_RC_AUDIT",
|
|
"-I",
|
|
vendorDir,
|
|
"-o",
|
|
bin,
|
|
join(testDir, "test_path.c"),
|
|
join(testDir, "../src/scr_path.c"),
|
|
// win32.relative lowercases through the regex unit's case mapping; the
|
|
// regex unit brings scr_assert.c and the vendored engine (regex.test.ts).
|
|
join(testDir, "../src/scr_path_win32_relative.c"),
|
|
join(testDir, "../src/scr_regex.c"),
|
|
join(testDir, "../src/scr_assert.c"),
|
|
join(vendorDir, "libregexp.c"),
|
|
join(vendorDir, "libunicode.c"),
|
|
// join/resolve take a packed string[]; the array module and its own
|
|
// dependencies join the link (the array.test.ts set).
|
|
join(testDir, "../src/scr_string.c"),
|
|
join(testDir, "../src/scr_number.c"),
|
|
join(testDir, "../src/scr_array.c"),
|
|
join(testDir, "../src/scr_arena.c"),
|
|
// Checked-value collection equality shares the native JSON value runtime.
|
|
join(testDir, "../src/scr_json.c"),
|
|
join(testDir, "../src/scr_map.c"),
|
|
join(testDir, "../src/scr_union.c"),
|
|
join(testDir, "../src/scr_bigint.c"),
|
|
join(testDir, "../src/scr_closure.c"),
|
|
join(testDir, "../src/scr_bytes.c"),
|
|
join(testDir, "../src/scr_shared.c"),
|
|
join(testDir, "../src/scr_lib.c"),
|
|
join(testDir, "../src/scr_hash.c"),
|
|
join(testDir, "../src/scr_console.c"),
|
|
join(testDir, "../src/scr_url.c"),
|
|
join(testDir, "../src/scr_url_params.c"),
|
|
join(testDir, "../src/scr_error.c"),
|
|
join(testDir, "../src/scr_exception.c"),
|
|
join(testDir, "../src/scr_object.c"),
|
|
join(testDir, "../src/scr_cycle.c"),
|
|
join(testDir, "../src/scr_alloc.c"),
|
|
...(process.platform === "linux" ? ["-D_GNU_SOURCE", "-lm"] : []),
|
|
]);
|
|
});
|
|
|
|
// Runs against the committed case file (generated once from Node v24's
|
|
// path.win32 via gen-path-cases.mjs — see that file to regenerate).
|
|
// Covers normalize/join/resolve/relative/dirname/basename/extname/
|
|
// isAbsolute/toNamespacedPath over drive-letter roots, UNC and \\?\ / \\.\
|
|
// device paths, the Windows reserved device names, mixed separators, and
|
|
// a seeded fuzz corpus; the file pins Node's byte-exact answers.
|
|
test("path.win32 functions match Node on committed oracle cases", async () => {
|
|
const { stderr } = await execFileAsync(bin, [join(testDir, "path-cases.txt")]);
|
|
expect(stderr.trim()).toMatch(/^(\d+)\/\1 cases passed$/);
|
|
});
|