Files
scriptc/packages/compiler/src/backend/native-codegen.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

321 lines
12 KiB
TypeScript

import { chmod, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, expect, test } from "vitest";
import {
emitNativeArtifact,
NativeCodegenError,
validateNativeCodegenVersion,
} from "./native-codegen.js";
import { nativeArtifactDependenciesStillMatch } from "./native-toolchain.js";
import { LINUX_X64_GNU_TARGET, MACOS_ARM64_TARGET, WASM32_WASI_TARGET } from "./targets.js";
import { compilerReleaseVersion } from "../library/sidecar.js";
const dirs: string[] = [];
afterEach(async () => {
await Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
async function fakePackage(
options: {
protocol?: string;
packageVersion?: string;
emitFailure?: boolean;
emptyOutput?: boolean;
missingOutput?: boolean;
changePackageDuringEmit?: boolean;
} = {},
) {
const root = await mkdtemp(join(tmpdir(), "scriptc-native-helper-test-"));
dirs.push(root);
const packageJson = join(root, "package.json");
const bin = join(root, "bin", "scriptc-llvm-codegen");
const log = join(root, "calls.log");
await mkdir(join(root, "bin"));
await writeFile(packageJson, JSON.stringify({ name: MACOS_ARM64_TARGET.helperPackage }));
const version = JSON.stringify({
ok: true,
protocol_version: options.protocol ?? "1",
scriptc_package_version: options.packageVersion ?? compilerReleaseVersion(),
llvm_version: "22.1.8",
host_triple: "arm64-apple-darwin24.0.0",
targets: ["AArch64"],
supported_targets: [MACOS_ARM64_TARGET.llvmTriple],
default_target: MACOS_ARM64_TARGET.llvmTriple,
data_layout: MACOS_ARM64_TARGET.dataLayout,
});
await writeFile(
bin,
`#!/bin/sh
if [ "$1" = version ]; then
printf '%s\\n' '${version}'
exit 0
fi
printf '%s\\n' "$*" >> '${log}'
output=''
input=''
while [ "$#" -gt 0 ]; do
if [ "$1" = --output ]; then output="$2"; shift 2; continue; fi
if [ "$1" = --input ]; then input="$2"; shift 2; continue; fi
shift
done
${options.changePackageDuringEmit === true ? `printf '\\n' >> '${packageJson}'` : ""}
${
options.emitFailure === true
? 'printf \'%s\\n\' \'{"ok":false,"code":"verification_failed","message":"bad module"}\' >&2; exit 1'
: options.emptyOutput === true
? ': > "$output"'
: options.missingOutput === true
? ":"
: 'cp "$input" "$output"'
}
`,
);
await chmod(bin, 0o755);
return { packageJson, bin, log, root };
}
function request(
root: string,
packageJson: string,
output = join(root, "program.o"),
target = MACOS_ARM64_TARGET,
) {
return {
outputPath: output,
llvm: "define i32 @answer() { ret i32 42 }\n",
outputKind: "obj" as const,
sourcePath: "/source/app.ts",
target,
helperHost: { platform: "darwin" as const, arch: "arm64" },
resolvePackageJson: () => packageJson,
cacheRoot: join(root, "cache"),
};
}
test("resolves the build host's platform helper", async () => {
const pkg = await fakePackage();
const output = join(pkg.root, "linux.o");
await writeFile(pkg.packageJson, JSON.stringify({ name: LINUX_X64_GNU_TARGET.helperPackage }));
await writeFile(
pkg.bin,
(await readFile(pkg.bin, "utf8"))
.replaceAll(MACOS_ARM64_TARGET.llvmTriple, LINUX_X64_GNU_TARGET.helper.defaultTarget)
.replaceAll(MACOS_ARM64_TARGET.dataLayout, LINUX_X64_GNU_TARGET.helper.defaultDataLayout)
.replaceAll('"targets":["AArch64"]', '"targets":["X86"]')
.replaceAll(
`"supported_targets":["${MACOS_ARM64_TARGET.llvmTriple}"]`,
`"supported_targets":["${LINUX_X64_GNU_TARGET.llvmTriple}"]`,
),
);
await emitNativeArtifact({
...request(pkg.root, pkg.packageJson, output, LINUX_X64_GNU_TARGET),
helperHost: { platform: "linux", arch: "x64", linuxLibc: "gnu" },
});
expect(await readFile(output, "utf8")).toContain("define i32 @answer");
});
test("requires a portable target backend in addition to the host helper backend", async () => {
const pkg = await fakePackage();
await expect(
emitNativeArtifact({
...request(pkg.root, pkg.packageJson, join(pkg.root, "program.wasm"), WASM32_WASI_TARGET),
helperHost: { platform: "darwin", arch: "arm64" },
}),
).rejects.toMatchObject({ diagnosticCode: "SC3003", detailCode: "version_mismatch" });
});
test("accepts a WASI-capable helper whose default host target differs", () => {
const helper = WASM32_WASI_TARGET.hostHelpers?.["darwin-arm64"];
if (!helper) throw new Error("missing darwin-arm64 WASI helper fixture");
expect(
validateNativeCodegenVersion(
{
ok: true,
protocol_version: "1",
scriptc_package_version: compilerReleaseVersion(),
llvm_version: "22.1.8",
host_triple: "arm64-apple-darwin25.0.0",
targets: ["AArch64", "WebAssembly"],
supported_targets: [helper.defaultTarget, WASM32_WASI_TARGET.llvmTriple],
default_target: helper.defaultTarget,
data_layout: helper.defaultDataLayout,
},
WASM32_WASI_TARGET,
helper,
).targets,
).toContain("WebAssembly");
});
test("resolves a package helper, emits atomically, and caches by all native inputs", async () => {
const pkg = await fakePackage();
const first = join(pkg.root, "first.o");
const second = join(pkg.root, "second.o");
const firstArtifact = await emitNativeArtifact(request(pkg.root, pkg.packageJson, first));
const secondArtifact = await emitNativeArtifact(request(pkg.root, pkg.packageJson, second));
expect(await readFile(first, "utf8")).toContain("define i32 @answer");
expect(await readFile(second)).toEqual(await readFile(first));
const expectedMode = 0o666 & ~process.umask();
expect((await stat(first)).mode & 0o777).toBe(expectedMode);
expect((await stat(second)).mode & 0o777).toBe(expectedMode);
expect((await readFile(pkg.log, "utf8")).trim().split("\n")).toHaveLength(1);
expect(firstArtifact.dependencies.map((dependency) => dependency.path)).toEqual(
[pkg.bin, pkg.packageJson].sort(),
);
expect(secondArtifact.dependencies).toEqual(firstArtifact.dependencies);
});
test("chunked LLVM input preserves emitted bytes and the native cache identity", async () => {
const pkg = await fakePackage();
const first = request(pkg.root, pkg.packageJson, join(pkg.root, "chunks.o"));
const chunks = [first.llvm.slice(0, 17), first.llvm.slice(17, 23), first.llvm.slice(23)];
await emitNativeArtifact({ ...first, llvm: chunks });
const second = request(pkg.root, pkg.packageJson, join(pkg.root, "text.o"));
await emitNativeArtifact(second);
expect(await readFile(first.outputPath, "utf8")).toBe(first.llvm);
expect(await readFile(second.outputPath)).toEqual(await readFile(first.outputPath));
expect((await readFile(pkg.log, "utf8")).trim().split("\n")).toHaveLength(1);
});
test("returns the pre-emission helper snapshot when its package changes during emission", async () => {
const pkg = await fakePackage({ changePackageDuringEmit: true });
const artifact = await emitNativeArtifact(request(pkg.root, pkg.packageJson));
expect(await readFile(join(pkg.root, "program.o"), "utf8")).toContain("define i32 @answer");
expect(await nativeArtifactDependenciesStillMatch(artifact.dependencies)).toBe(false);
expect(
await stat(join(pkg.root, "cache", "native-codegen-v1")).then(
() => true,
() => false,
),
).toBe(false);
});
test("cache publication failures do not discard a valid requested artifact", async () => {
const pkg = await fakePackage();
const cacheRoot = join(pkg.root, "cache");
const output = join(pkg.root, "uncached-success.o");
await mkdir(cacheRoot, { mode: 0o700 });
// Block creation of the cache family below an otherwise valid cache root.
await writeFile(join(cacheRoot, "native-codegen-v1"), "not a directory\n");
await emitNativeArtifact({
...request(pkg.root, pkg.packageJson, output),
cacheRoot,
});
expect(await readFile(output, "utf8")).toContain("define i32 @answer");
expect((await readFile(pkg.log, "utf8")).trim().split("\n")).toHaveLength(1);
});
test("reports a missing platform package as an actionable installation diagnostic", async () => {
const root = await mkdtemp(join(tmpdir(), "scriptc-native-missing-test-"));
dirs.push(root);
await expect(
emitNativeArtifact({
...request(root, join(root, "missing.json")),
resolvePackageJson: () => {
throw new Error("missing");
},
}),
).rejects.toMatchObject({
diagnosticCode: "SC3003",
detailCode: "missing_package",
message: expect.stringContaining("optional dependencies"),
});
});
test.skipIf(process.platform === "win32")(
"reports unreadable or non-executable helper binaries as installation failures",
async () => {
for (const mode of [0o111, 0o644]) {
const pkg = await fakePackage();
await chmod(pkg.bin, mode);
await expect(emitNativeArtifact(request(pkg.root, pkg.packageJson))).rejects.toMatchObject({
diagnosticCode: "SC3003",
detailCode: "unusable_binary",
message: expect.stringContaining("reinstall scriptc"),
});
}
},
);
test.skipIf(process.platform === "win32")(
"reports helper identity execution failures as installation failures",
async () => {
const pkg = await fakePackage();
await writeFile(pkg.bin, "#!/definitely/missing/scriptc-interpreter\n");
await chmod(pkg.bin, 0o755);
await expect(emitNativeArtifact(request(pkg.root, pkg.packageJson))).rejects.toMatchObject({
diagnosticCode: "SC3003",
detailCode: "identity_probe_failed",
message: expect.stringContaining("identity check"),
});
},
);
test("rejects protocol and package-version mismatches before emission", async () => {
for (const options of [{ protocol: "99" }, { packageVersion: "9.9.9" }]) {
const pkg = await fakePackage(options);
await expect(emitNativeArtifact(request(pkg.root, pkg.packageJson))).rejects.toMatchObject({
diagnosticCode: "SC3003",
detailCode: "version_mismatch",
});
}
});
test("translates structured helper failures and preserves an existing output", async () => {
const pkg = await fakePackage({ emitFailure: true });
const output = join(pkg.root, "existing.o");
await writeFile(output, "caller artifact\n");
await expect(
emitNativeArtifact(request(pkg.root, pkg.packageJson, output)),
).rejects.toMatchObject({
diagnosticCode: "SC3004",
detailCode: "verification_failed",
message: expect.stringContaining("bad module"),
});
expect(await readFile(output, "utf8")).toBe("caller artifact\n");
});
test("rejects a successful helper that leaves an empty staged output", async () => {
const pkg = await fakePackage({ emptyOutput: true });
await expect(emitNativeArtifact(request(pkg.root, pkg.packageJson))).rejects.toMatchObject({
diagnosticCode: "SC3004",
detailCode: "empty_output",
});
});
test("rejects a successful helper that creates no staged output", async () => {
const pkg = await fakePackage({ missingOutput: true });
await expect(emitNativeArtifact(request(pkg.root, pkg.packageJson))).rejects.toMatchObject({
diagnosticCode: "SC3004",
detailCode: "empty_output",
message: expect.stringContaining("non-empty regular artifact"),
});
});
test("sanitized native artifacts fail before helper resolution", async () => {
const root = await mkdtemp(join(tmpdir(), "scriptc-native-sanitize-test-"));
dirs.push(root);
await expect(
emitNativeArtifact({
...request(root, join(root, "unused.json")),
sanitize: true,
resolvePackageJson: () => {
throw new Error("must not resolve");
},
}),
).rejects.toBeInstanceOf(NativeCodegenError);
await expect(
emitNativeArtifact({
...request(root, join(root, "unused.json")),
sanitize: true,
resolvePackageJson: () => {
throw new Error("must not resolve");
},
}),
).rejects.toMatchObject({ diagnosticCode: "SC3002", detailCode: "sanitize_unsupported" });
});