test: cover sync clobber snapshot collisions

This commit is contained in:
Peter Steinberger
2026-05-15 07:12:50 +01:00
parent 5734193fdf
commit 2645492fde
2 changed files with 87 additions and 16 deletions

View File

@@ -75,6 +75,23 @@ describe("config clobber snapshots", () => {
}
}
function touchClobberFilesByContentOrderSync(configPath: string): void {
const dir = path.dirname(configPath);
const prefix = `${path.basename(configPath)}.clobbered.`;
for (const entry of fs.readdirSync(dir)) {
if (!entry.startsWith(prefix)) {
continue;
}
const targetPath = path.join(dir, entry);
const match = /^polluted-(\d+)\n$/.exec(fs.readFileSync(targetPath, "utf-8"));
if (!match) {
continue;
}
const touchedAt = new Date(`2026-05-03T00:00:${match[1].padStart(2, "0")}.000Z`);
fs.utimesSync(targetPath, touchedAt, touchedAt);
}
}
it("keeps concurrent async snapshots under the per-path cap by rotating oldest files", async () => {
await withCase(async (configPath) => {
const warn = vi.fn();
@@ -227,4 +244,42 @@ describe("config clobber snapshots", () => {
expect(capWarnings).toHaveLength(1);
});
});
it("keeps rotating same-timestamp sync snapshots after the base artifact is reused", async () => {
await withCase(async (configPath) => {
const warn = vi.fn();
const observedAt = "2026-05-03T00:00:00.000Z";
for (let index = 0; index < CONFIG_CLOBBER_SNAPSHOT_LIMIT; index++) {
persistBoundedClobberedConfigSnapshotSync({
deps: { fs, logger: { warn } },
configPath,
raw: `polluted-${index}\n`,
observedAt,
});
}
touchClobberFilesByContentOrderSync(configPath);
for (
let index = CONFIG_CLOBBER_SNAPSHOT_LIMIT;
index < CONFIG_CLOBBER_SNAPSHOT_LIMIT + 3;
index++
) {
persistBoundedClobberedConfigSnapshotSync({
deps: { fs, logger: { warn } },
configPath,
raw: `polluted-${index}\n`,
observedAt,
});
}
const clobberFiles = await listClobberFiles(configPath);
expect(clobberFiles).toHaveLength(CONFIG_CLOBBER_SNAPSHOT_LIMIT);
const contents = await readClobberFileContents(configPath);
expect(contents).not.toContain("polluted-0\n");
expect(contents).not.toContain("polluted-1\n");
expect(contents).not.toContain("polluted-2\n");
expect(contents).toContain(`polluted-${CONFIG_CLOBBER_SNAPSHOT_LIMIT + 2}\n`);
});
});
});

View File

@@ -137,6 +137,20 @@ function compareClobberedSiblings(
);
}
function createClobberedSiblingSnapshot(params: {
dir: string;
entry: string;
prefix: string;
mtimeMs: number;
}): ClobberedSiblingSnapshot {
return {
name: params.entry,
path: path.join(params.dir, params.entry),
timestampKey: params.entry.slice(params.prefix.length).replace(/-\d{2}$/, ""),
mtimeMs: params.mtimeMs,
};
}
async function listClobberedSiblings(
deps: ConfigClobberSnapshotDeps,
dir: string,
@@ -149,14 +163,15 @@ async function listClobberedSiblings(
if (!entry.startsWith(prefix)) {
continue;
}
const snapshotPath = path.join(dir, entry);
const stat = await deps.fs.promises.stat(snapshotPath).catch(() => null);
snapshots.push({
name: entry,
path: snapshotPath,
timestampKey: entry.slice(prefix.length).replace(/-\d{2}$/, ""),
mtimeMs: stat?.mtimeMs ?? 0,
});
const stat = await deps.fs.promises.stat(path.join(dir, entry)).catch(() => null);
snapshots.push(
createClobberedSiblingSnapshot({
dir,
entry,
prefix,
mtimeMs: stat?.mtimeMs ?? 0,
}),
);
}
return snapshots.toSorted(compareClobberedSiblings);
} catch {
@@ -175,14 +190,15 @@ function listClobberedSiblingsSync(
if (!entry.startsWith(prefix)) {
continue;
}
const snapshotPath = path.join(dir, entry);
const stat = deps.fs.statSync(snapshotPath, { throwIfNoEntry: false });
snapshots.push({
name: entry,
path: snapshotPath,
timestampKey: entry.slice(prefix.length).replace(/-\d{2}$/, ""),
mtimeMs: stat?.mtimeMs ?? 0,
});
const stat = deps.fs.statSync(path.join(dir, entry), { throwIfNoEntry: false });
snapshots.push(
createClobberedSiblingSnapshot({
dir,
entry,
prefix,
mtimeMs: stat?.mtimeMs ?? 0,
}),
);
}
return snapshots.toSorted(compareClobberedSiblings);
} catch {