Lumi/scripts/verify-safe-files.js
2026-07-17 22:10:00 +02:00

88 lines
3.5 KiB
JavaScript

const assert = require("assert");
const fs = require("fs");
const os = require("os");
const path = require("path");
const { Worker } = require("worker_threads");
const {
writeJsonAtomicSync,
clearSafeFileDiagnostics,
getSafeFileDiagnostics
} = require("../src/services/safe-files");
async function run() {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-safe-files-"));
const target = path.join(directory, "runtime_state.json");
const originalRename = fs.renameSync;
try {
fs.writeFileSync(target, '{"state":"original"}\n');
const stale = `${target}.tmp`;
fs.writeFileSync(stale, "stale");
const old = new Date(Date.now() - 2 * 60 * 60 * 1000);
fs.utimesSync(stale, old, old);
clearSafeFileDiagnostics();
let transientFailures = 0;
fs.renameSync = (from, to) => {
if (to === target && String(from).endsWith(".tmp") && transientFailures < 2) {
transientFailures += 1;
throw Object.assign(new Error("temporarily locked"), { code: transientFailures === 1 ? "EPERM" : "EACCES" });
}
return originalRename(from, to);
};
writeJsonAtomicSync(target, { state: "recovered" }, { attempts: 4, delayMs: 1, staleTempAgeMs: 1000 });
assert.deepEqual(JSON.parse(fs.readFileSync(target, "utf8")), { state: "recovered" });
assert.equal(fs.existsSync(stale), false);
assert(getSafeFileDiagnostics().filter((entry) => entry.operation === "rename").length >= 2);
fs.writeFileSync(target, '{"state":"must-survive"}\n');
fs.renameSync = (from, to) => {
if (to === target && String(from).endsWith(".tmp")) {
throw Object.assign(new Error("replacement locked"), { code: "EPERM" });
}
return originalRename(from, to);
};
assert.throws(
() => writeJsonAtomicSync(target, { state: "must-not-land" }, { attempts: 2, delayMs: 1 }),
/replacement locked/
);
assert.deepEqual(JSON.parse(fs.readFileSync(target, "utf8")), { state: "must-survive" });
fs.renameSync = originalRename;
await Promise.all(Array.from({ length: 4 }, (_, worker) => runWorker(target, worker)));
const final = JSON.parse(fs.readFileSync(target, "utf8"));
assert(Number.isInteger(final.worker));
assert(Number.isInteger(final.write));
assert.equal(fs.readdirSync(directory).some((name) => name.endsWith(".tmp")), false);
} finally {
fs.renameSync = originalRename;
fs.rmSync(directory, { recursive: true, force: true });
}
console.log("Safe atomic file verification passed: transient lock retry, rollback, stale cleanup, and concurrent writes.");
}
function runWorker(target, worker) {
const helper = require.resolve("../src/services/safe-files");
const source = `
const { parentPort, workerData } = require("worker_threads");
const { writeJsonAtomicSync } = require(workerData.helper);
try {
for (let write = 0; write < 12; write += 1) {
writeJsonAtomicSync(workerData.target, { worker: workerData.worker, write });
}
parentPort.postMessage({ ok: true });
} catch (error) {
parentPort.postMessage({ ok: false, error: error.stack || error.message });
}
`;
return new Promise((resolve, reject) => {
const instance = new Worker(source, { eval: true, workerData: { helper, target, worker } });
instance.once("message", (message) => message.ok ? resolve() : reject(new Error(message.error)));
instance.once("error", reject);
});
}
run().catch((error) => {
console.error(error);
process.exitCode = 1;
});