mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
scr_fs_write_once cached whether the calling thread blocks SIGPIPE and SIGXFSZ in an SCR_TL variable. SCR_TL is empty in builds without thread instances, so a pool thread's answer applied to the main thread too, and a later writeSync to a closed pipe ended the process with SIGPIPE instead of throwing EPIPE. The cache is _Thread_local now. The fs-write-sync fixture covers a write stream followed by writeSync on a closed stdout.
100 lines
4.4 KiB
TypeScript
100 lines
4.4 KiB
TypeScript
/* fs.writeSync and write-stream contracts that need harness control over the
|
|
* native process's descriptors. The ordinary byte/error behavior stays in
|
|
* differential corpus 2685; this test controls stdout's pipe state,
|
|
* nonblocking mode, and the process file-size limit to pin the signal-backed
|
|
* descriptor errors, on the calling thread (writeSync) and on the fs pool's
|
|
* threads (write streams). */
|
|
import { execFileSync, spawn } from "node:child_process";
|
|
import { join } from "node:path";
|
|
import { describe, expect, test } from "vitest";
|
|
import { compile } from "@scriptc/compiler";
|
|
|
|
const repoRoot = join(import.meta.dirname, "../..");
|
|
const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests");
|
|
const sanitize = process.env["SCRIPTC_SAN"] === "1";
|
|
|
|
describe.skipIf(process.platform === "win32")(
|
|
`fs.writeSync descriptor errors${sanitize ? " (sanitized)" : ""}`,
|
|
() => {
|
|
test("writes preserve descriptor error semantics", async () => {
|
|
const outDir = join(cacheDir, `fs-write-sync${sanitize ? "-san" : ""}`);
|
|
const result = await compile(join(repoRoot, "tests/fixtures/fs-write-sync-sigpipe.ts"), {
|
|
outPath: join(outDir, "sigpipe"),
|
|
outDir,
|
|
sanitize,
|
|
backend: "llvm",
|
|
});
|
|
if (!result.ok) {
|
|
throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
|
|
}
|
|
const launcher = join(outDir, "descriptor-launcher");
|
|
execFileSync("clang", [
|
|
"-std=c11",
|
|
join(repoRoot, "tests/fixtures/fs-write-sync-launch.c"),
|
|
"-o",
|
|
launcher,
|
|
]);
|
|
|
|
const run = async (command: string, args: string[], closeStdout = false) => {
|
|
const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
child.stdout!.setEncoding("utf8");
|
|
child.stderr!.setEncoding("utf8");
|
|
let stdout = "";
|
|
let stderr = "";
|
|
child.stdout!.on("data", (chunk: string) => {
|
|
stdout += chunk;
|
|
});
|
|
child.stderr!.on("data", (chunk: string) => {
|
|
stderr += chunk;
|
|
});
|
|
if (closeStdout) child.stdout!.destroy();
|
|
const outcome = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(
|
|
(resolve, reject) => {
|
|
child.on("error", reject);
|
|
child.on("close", (code, signal) => resolve({ code, signal }));
|
|
},
|
|
);
|
|
return { outcome, stdout, stderr };
|
|
};
|
|
|
|
const current = await run(result.binaryPath, [], true);
|
|
expect(current.outcome).toEqual({ code: 0, signal: null });
|
|
expect(current.stderr).toBe("caught: Error EPIPE\n");
|
|
|
|
const positioned = await run(result.binaryPath, ["positioned"]);
|
|
expect(positioned.outcome).toEqual({ code: 0, signal: null });
|
|
expect(positioned.stdout).toBe("");
|
|
expect(positioned.stderr).toBe("caught: Error ESPIPE ESPIPE: invalid seek, write\n");
|
|
|
|
const nonblock = await run(launcher, ["nonblock", result.binaryPath, "eagain"]);
|
|
expect(nonblock.outcome).toEqual({ code: 0, signal: null });
|
|
expect(nonblock.stdout).toBe("");
|
|
expect(nonblock.stderr).toBe(
|
|
"caught: Error EAGAIN EAGAIN: resource temporarily unavailable, write\n",
|
|
);
|
|
|
|
const fileLimit = await run(launcher, ["rlimit", result.binaryPath, "efbig"]);
|
|
expect(fileLimit.outcome).toEqual({ code: 0, signal: null });
|
|
expect(fileLimit.stdout).toBe("");
|
|
expect(fileLimit.stderr).toBe(
|
|
"current first: 512\n" +
|
|
"current caught: Error EFBIG EFBIG: file too large, write\n" +
|
|
"positioned first: 512\n" +
|
|
"positioned caught: Error EFBIG EFBIG: file too large, write\n",
|
|
);
|
|
|
|
const streamPipe = await run(result.binaryPath, ["stream-sigpipe"], true);
|
|
expect(streamPipe.outcome).toEqual({ code: 0, signal: null });
|
|
expect(streamPipe.stderr).toBe("stream caught: Error EPIPE EPIPE: broken pipe, write\n");
|
|
|
|
const thenSync = await run(result.binaryPath, ["stream-then-sync-sigpipe"], true);
|
|
expect(thenSync.outcome).toEqual({ code: 0, signal: null });
|
|
expect(thenSync.stderr).toBe("sync caught: Error EPIPE\n");
|
|
|
|
const streamLimit = await run(launcher, ["rlimit", result.binaryPath, "stream-efbig"]);
|
|
expect(streamLimit.outcome).toEqual({ code: 0, signal: null });
|
|
expect(streamLimit.stderr).toBe("stream caught: Error EFBIG EFBIG: file too large, write\n");
|
|
});
|
|
},
|
|
);
|