Skip to content

Commit 2645492

Browse files
committed
test: cover sync clobber snapshot collisions
1 parent 5734193 commit 2645492

2 files changed

Lines changed: 87 additions & 16 deletions

File tree

src/config/io.clobber-snapshot.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,23 @@ describe("config clobber snapshots", () => {
7575
}
7676
}
7777

78+
function touchClobberFilesByContentOrderSync(configPath: string): void {
79+
const dir = path.dirname(configPath);
80+
const prefix = `${path.basename(configPath)}.clobbered.`;
81+
for (const entry of fs.readdirSync(dir)) {
82+
if (!entry.startsWith(prefix)) {
83+
continue;
84+
}
85+
const targetPath = path.join(dir, entry);
86+
const match = /^polluted-(\d+)\n$/.exec(fs.readFileSync(targetPath, "utf-8"));
87+
if (!match) {
88+
continue;
89+
}
90+
const touchedAt = new Date(`2026-05-03T00:00:${match[1].padStart(2, "0")}.000Z`);
91+
fs.utimesSync(targetPath, touchedAt, touchedAt);
92+
}
93+
}
94+
7895
it("keeps concurrent async snapshots under the per-path cap by rotating oldest files", async () => {
7996
await withCase(async (configPath) => {
8097
const warn = vi.fn();
@@ -227,4 +244,42 @@ describe("config clobber snapshots", () => {
227244
expect(capWarnings).toHaveLength(1);
228245
});
229246
});
247+
248+
it("keeps rotating same-timestamp sync snapshots after the base artifact is reused", async () => {
249+
await withCase(async (configPath) => {
250+
const warn = vi.fn();
251+
const observedAt = "2026-05-03T00:00:00.000Z";
252+
253+
for (let index = 0; index < CONFIG_CLOBBER_SNAPSHOT_LIMIT; index++) {
254+
persistBoundedClobberedConfigSnapshotSync({
255+
deps: { fs, logger: { warn } },
256+
configPath,
257+
raw: `polluted-${index}\n`,
258+
observedAt,
259+
});
260+
}
261+
touchClobberFilesByContentOrderSync(configPath);
262+
263+
for (
264+
let index = CONFIG_CLOBBER_SNAPSHOT_LIMIT;
265+
index < CONFIG_CLOBBER_SNAPSHOT_LIMIT + 3;
266+
index++
267+
) {
268+
persistBoundedClobberedConfigSnapshotSync({
269+
deps: { fs, logger: { warn } },
270+
configPath,
271+
raw: `polluted-${index}\n`,
272+
observedAt,
273+
});
274+
}
275+
276+
const clobberFiles = await listClobberFiles(configPath);
277+
expect(clobberFiles).toHaveLength(CONFIG_CLOBBER_SNAPSHOT_LIMIT);
278+
const contents = await readClobberFileContents(configPath);
279+
expect(contents).not.toContain("polluted-0\n");
280+
expect(contents).not.toContain("polluted-1\n");
281+
expect(contents).not.toContain("polluted-2\n");
282+
expect(contents).toContain(`polluted-${CONFIG_CLOBBER_SNAPSHOT_LIMIT + 2}\n`);
283+
});
284+
});
230285
});

src/config/io.clobber-snapshot.ts

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,20 @@ function compareClobberedSiblings(
137137
);
138138
}
139139

140+
function createClobberedSiblingSnapshot(params: {
141+
dir: string;
142+
entry: string;
143+
prefix: string;
144+
mtimeMs: number;
145+
}): ClobberedSiblingSnapshot {
146+
return {
147+
name: params.entry,
148+
path: path.join(params.dir, params.entry),
149+
timestampKey: params.entry.slice(params.prefix.length).replace(/-\d{2}$/, ""),
150+
mtimeMs: params.mtimeMs,
151+
};
152+
}
153+
140154
async function listClobberedSiblings(
141155
deps: ConfigClobberSnapshotDeps,
142156
dir: string,
@@ -149,14 +163,15 @@ async function listClobberedSiblings(
149163
if (!entry.startsWith(prefix)) {
150164
continue;
151165
}
152-
const snapshotPath = path.join(dir, entry);
153-
const stat = await deps.fs.promises.stat(snapshotPath).catch(() => null);
154-
snapshots.push({
155-
name: entry,
156-
path: snapshotPath,
157-
timestampKey: entry.slice(prefix.length).replace(/-\d{2}$/, ""),
158-
mtimeMs: stat?.mtimeMs ?? 0,
159-
});
166+
const stat = await deps.fs.promises.stat(path.join(dir, entry)).catch(() => null);
167+
snapshots.push(
168+
createClobberedSiblingSnapshot({
169+
dir,
170+
entry,
171+
prefix,
172+
mtimeMs: stat?.mtimeMs ?? 0,
173+
}),
174+
);
160175
}
161176
return snapshots.toSorted(compareClobberedSiblings);
162177
} catch {
@@ -175,14 +190,15 @@ function listClobberedSiblingsSync(
175190
if (!entry.startsWith(prefix)) {
176191
continue;
177192
}
178-
const snapshotPath = path.join(dir, entry);
179-
const stat = deps.fs.statSync(snapshotPath, { throwIfNoEntry: false });
180-
snapshots.push({
181-
name: entry,
182-
path: snapshotPath,
183-
timestampKey: entry.slice(prefix.length).replace(/-\d{2}$/, ""),
184-
mtimeMs: stat?.mtimeMs ?? 0,
185-
});
193+
const stat = deps.fs.statSync(path.join(dir, entry), { throwIfNoEntry: false });
194+
snapshots.push(
195+
createClobberedSiblingSnapshot({
196+
dir,
197+
entry,
198+
prefix,
199+
mtimeMs: stat?.mtimeMs ?? 0,
200+
}),
201+
);
186202
}
187203
return snapshots.toSorted(compareClobberedSiblings);
188204
} catch {

0 commit comments

Comments
 (0)