Files
msd-core/tests/helpers/context-monitor-unlink-eperm-preload.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

115 lines
5.2 KiB
JavaScript

'use strict';
/**
* Preload fixture for #3709 review Blocker 2 — force `fs.unlinkSync` to throw
* EPERM inside the SPAWNED msd-context-monitor.js hook, so the PreCompact
* unlink-failure fallback branch is actually executed.
*
* `MSD_TEST_UNLINK_EPERM_MATCH` selects WHICH unlink fails: any path containing
* the substring throws EPERM; every other path unlinks for real. That lets one
* row fail only the warn sentinel (`-warned.json`) and another only the metrics
* bridge, so each half of the fallback is pinned separately.
*
* Loaded via `node --require <this file> hooks/msd-context-monitor.js` — the
* same seam as `shadow-report-throws-preload.cjs`, and for the same reasons:
* this is NOT a chmod/mode-bit trick (CONTRIBUTING.md: those no-op under root,
* so Docker/CI would pass the test with zero coverage), and NOT the in-process
* `withFaultyFs` seam (in-process-only — a spawned subprocess offers no shared
* memory to monkeypatch into). One-shot subprocess: no restoration needed.
*/
const fs = require('fs');
const realUnlinkSync = fs.unlinkSync;
const match = process.env.MSD_TEST_UNLINK_EPERM_MATCH || '';
if (match) {
fs.unlinkSync = function unlinkSyncWithInjectedEperm(p) {
if (String(p).includes(match)) {
const err = new Error(`EPERM: operation not permitted, unlink '${p}'`);
err.code = 'EPERM';
throw err;
}
return realUnlinkSync.apply(fs, arguments);
};
}
// `MSD_TEST_LSTAT_CLAIMS_FILE_MATCH`: make lstat CLAIM a regular file for
// matching paths — the lstat→open substitution-race shape (a symlink swapped in
// after the lstat), so the O_NOFOLLOW backstop is the guard actually under test
// (review of #3808, round 3, Minor 3). The real stat object is returned with
// only isFile overridden; everything else stays truthful.
const lstatMatch = process.env.MSD_TEST_LSTAT_CLAIMS_FILE_MATCH || '';
if (lstatMatch) {
const realLstatSync = fs.lstatSync;
fs.lstatSync = function lstatSyncClaimingFile(p) {
const st = realLstatSync.apply(fs, arguments);
if (String(p).includes(lstatMatch)) {
st.isFile = () => true;
// Engagement marker: without it, a match string that silently stops
// matching lets the REAL lstat refuse the symlink and every assertion
// in the substitution-race row still passes — without O_NOFOLLOW ever
// being the guard under test (Codex review of #3808, round 3). The row
// asserts this file exists.
try {
fs.writeFileSync(`${p}.msd-test-lstat-claimed`, '1');
} catch { /* marker is best-effort; the row will fail loudly without it */ }
}
return st;
};
}
// `MSD_TEST_SHRINK_AFTER_LSTAT_MATCH`: TRUNCATE a matching file immediately after
// `lstatSync` has measured it, so the subsequent `readSync` returns FEWER bytes than
// the size the guard allocated for. That is the shape round 11's Minor names — a
// concurrent legitimate writer truncating mid-write, independent of the planted-object
// case the rest of readSentinel guards — and it cannot be produced by ordinary file
// setup, because the shrink has to land inside the window between the two calls.
//
// Layered over the lstat wrapper above rather than folded into it: the two injections
// are selected by different env vars and a row may want either alone.
const shrinkMatch = process.env.MSD_TEST_SHRINK_AFTER_LSTAT_MATCH || '';
if (shrinkMatch) {
const beforeShrink = fs.lstatSync;
fs.lstatSync = function lstatSyncThenShrink(p) {
const st = beforeShrink.apply(fs, arguments);
if (String(p).includes(shrinkMatch) && st.isFile() && st.size > 1) {
// Truncate to a single byte: the read still succeeds, it just returns 1 instead
// of `st.size`, which is exactly the short read the guard must refuse.
//
// Engagement marker, same reason as the lstat-claim one above and learned the same way:
// the hook REWRITES this sentinel later in the same invocation, so a row that checked the
// file's size afterwards would see it back at full length and pass whether the truncation
// landed or not. The marker is the only durable evidence that this injection fired.
try {
fs.truncateSync(p, 1);
fs.writeFileSync(`${p}.msd-test-shrunk`, '1');
} catch { /* best effort — the row asserts the marker and fails loudly without it */ }
}
return st;
};
}
// `MSD_TEST_SHORT_WRITE_MATCH`: make `fs.writeSync` write only the FIRST BYTE for a
// matching fd's payload, once. A short write is permitted by the syscall and was
// previously discarded, so a truncated sentinel reached disk and every later read
// rejected it — the debounce accounting and the compaction watermark silently lost.
// The injection fires once so the retry loop under test can complete normally.
const shortWriteMatch = process.env.MSD_TEST_SHORT_WRITE_MATCH || '';
if (shortWriteMatch) {
const realWriteSync = fs.writeSync;
let fired = false;
fs.writeSync = function writeSyncShort(fd, buf, off, len) {
if (!fired && Buffer.isBuffer(buf) && len > 1) {
fired = true;
try { fs.writeFileSync(`${shortWriteMatch}.msd-test-short-write`, '1'); } catch { /* marker best effort */ }
return realWriteSync.call(fs, fd, buf, off, 1);
}
return realWriteSync.apply(fs, arguments);
};
}