






















@@ -43,6 +43,38 @@ describe("config clobber snapshots", () => {
4343);
4444}
454546+async function findClobberFileByContent(
47+configPath: string,
48+expectedContent: string,
49+): Promise<string> {
50+const dir = path.dirname(configPath);
51+for (const entry of await listClobberFiles(configPath)) {
52+const candidatePath = path.join(dir, entry);
53+if ((await fsp.readFile(candidatePath, "utf-8")) === expectedContent) {
54+return candidatePath;
55+}
56+}
57+throw new Error(`Missing clobber snapshot with content ${expectedContent}`);
58+}
59+60+async function touchClobberFilesByContentOrder(configPath: string): Promise<void> {
61+const dir = path.dirname(configPath);
62+const filesWithContents = await Promise.all(
63+(await listClobberFiles(configPath)).map(async (entry) => ({
64+ entry,
65+content: await fsp.readFile(path.join(dir, entry), "utf-8"),
66+})),
67+);
68+for (const file of filesWithContents) {
69+const match = /^polluted-(\d+)\n$/.exec(file.content);
70+if (!match) {
71+continue;
72+}
73+const touchedAt = new Date(`2026-05-03T00:00:${match[1].padStart(2, "0")}.000Z`);
74+await fsp.utimes(path.join(dir, file.entry), touchedAt, touchedAt);
75+}
76+}
77+4678it("keeps concurrent async snapshots under the per-path cap by rotating oldest files", async () => {
4779await withCase(async (configPath) => {
4880const warn = vi.fn();
@@ -97,6 +129,77 @@ describe("config clobber snapshots", () => {
97129});
98130});
99131132+it("rotates async snapshots by artifact timestamp rather than mutable mtime", async () => {
133+await withCase(async (configPath) => {
134+const warn = vi.fn();
135+136+for (let index = 0; index < CONFIG_CLOBBER_SNAPSHOT_LIMIT; index++) {
137+await persistBoundedClobberedConfigSnapshot({
138+deps: { fs, logger: { warn } },
139+ configPath,
140+raw: `polluted-${index}\n`,
141+observedAt: `2026-05-03T00:00:${String(index).padStart(2, "0")}.000Z`,
142+});
143+}
144+145+const oldestPath = await findClobberFileByContent(configPath, "polluted-0\n");
146+const future = new Date("2026-05-03T01:00:00.000Z");
147+await fsp.utimes(oldestPath, future, future);
148+149+await persistBoundedClobberedConfigSnapshot({
150+deps: { fs, logger: { warn } },
151+ configPath,
152+raw: "polluted-latest\n",
153+observedAt: "2026-05-03T00:01:00.000Z",
154+});
155+156+const clobberFiles = await listClobberFiles(configPath);
157+expect(clobberFiles).toHaveLength(CONFIG_CLOBBER_SNAPSHOT_LIMIT);
158+const contents = await readClobberFileContents(configPath);
159+expect(contents).not.toContain("polluted-0\n");
160+expect(contents).toContain("polluted-latest\n");
161+expect(warn.mock.calls).toHaveLength(1);
162+});
163+});
164+165+it("keeps rotating same-timestamp async snapshots after the base artifact is reused", async () => {
166+await withCase(async (configPath) => {
167+const warn = vi.fn();
168+const observedAt = "2026-05-03T00:00:00.000Z";
169+170+for (let index = 0; index < CONFIG_CLOBBER_SNAPSHOT_LIMIT; index++) {
171+await persistBoundedClobberedConfigSnapshot({
172+deps: { fs, logger: { warn } },
173+ configPath,
174+raw: `polluted-${index}\n`,
175+ observedAt,
176+});
177+}
178+await touchClobberFilesByContentOrder(configPath);
179+180+for (
181+let index = CONFIG_CLOBBER_SNAPSHOT_LIMIT;
182+index < CONFIG_CLOBBER_SNAPSHOT_LIMIT + 3;
183+index++
184+) {
185+await persistBoundedClobberedConfigSnapshot({
186+deps: { fs, logger: { warn } },
187+ configPath,
188+raw: `polluted-${index}\n`,
189+ observedAt,
190+});
191+}
192+193+const clobberFiles = await listClobberFiles(configPath);
194+expect(clobberFiles).toHaveLength(CONFIG_CLOBBER_SNAPSHOT_LIMIT);
195+const contents = await readClobberFileContents(configPath);
196+expect(contents).not.toContain("polluted-0\n");
197+expect(contents).not.toContain("polluted-1\n");
198+expect(contents).not.toContain("polluted-2\n");
199+expect(contents).toContain(`polluted-${CONFIG_CLOBBER_SNAPSHOT_LIMIT + 2}\n`);
200+});
201+});
202+100203it("rotates sync snapshots so the latest clobbered config is preserved", async () => {
101204await withCase(async (configPath) => {
102205const warn = vi.fn();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。