Skip to content

Commit 7dd3126

Browse files
committed
test(run-engine): cover the future-head window guard a mutation audit found bare
A variant whose head is scheduled in the future reports 'notReady' so pass 1 declines to spend a window slot on it. That was added deliberately and its comment calls it load-bearing, but deleting the report left all four vtime suites green: the behaviour was defended by comment and by nothing else. Found by mutating the production Lua one change at a time and rerunning the suites, on the principle that a green run against a broken invariant is a hole. Seven of eight mutations were caught, some by more than twenty assertions; this was the one that walked through. It is the same shape as the concurrency-gate bug fixed in the previous commit, which is the uncomfortable part: the guard for one half of the problem had no test while the other half was actively broken. The test mirrors the gated one. Enough future-headed variants to fill the pass-1 window, sorted ahead on tag, and a servable variant behind them that fair order says to serve first and age order says to serve last. Verified in both directions: it passes against the real script and fails against the mutation with 'expected old to be owed', which is precisely the silent fall back to age order.
1 parent 346ad05 commit 7dd3126

1 file changed

Lines changed: 74 additions & 0 deletions

File tree

‎internal-packages/run-engine/src/run-queue/tests/ckVtimeGatedWindow.test.ts‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,78 @@ describe("CK vtime: gated variants and the pass-1 window", () => {
150150
},
151151
60_000
152152
);
153+
// The sibling case, and a mutation audit found nothing was testing it: a variant whose
154+
// head is scheduled in the future already reports 'notReady' so pass 1 declines to spend
155+
// a slot on it. Deleting that report left the whole vtime suite green, meaning the
156+
// behaviour was load-bearing by comment only. Same shape as the gated test above: enough
157+
// unservable variants sorted ahead on tag to eat the window, and a servable one behind
158+
// them that fair order says to serve first.
159+
redisTest(
160+
"future-headed variants must not spend the fair pass's budget",
161+
async ({ redisContainer }) => {
162+
const MAX = 2; // window = 6
163+
const FUTURE = 8;
164+
const queue = createQueue(redisContainer, "runqueue:test:futurewin:");
165+
166+
try {
167+
const env = baseEnv;
168+
await queue.updateEnvConcurrencyLimits(env);
169+
const shard = testOptions.keys.masterQueueShardForEnvironment(env.id, 2);
170+
const t0 = Date.now() - 5_000_000;
171+
const future = Date.now() + 60 * 60 * 1000;
172+
173+
// Unservable: heads scheduled an hour out. Nothing can serve these this call.
174+
for (let i = 0; i < FUTURE; i++) {
175+
await queue.enqueueMessage({
176+
env,
177+
message: makeMessage({
178+
runId: `f-${i}`,
179+
concurrencyKey: `future-${i}`,
180+
timestamp: future + i,
181+
}),
182+
workerQueue: env.id,
183+
skipDequeueProcessing: true,
184+
});
185+
}
186+
187+
// Lowest tag among servable variants, newest head: fair order first, age order last.
188+
await queue.enqueueMessage({
189+
env,
190+
message: makeMessage({
191+
runId: "owed-0",
192+
concurrencyKey: "owed",
193+
timestamp: t0 + 900_000,
194+
}),
195+
workerQueue: env.id,
196+
skipDequeueProcessing: true,
197+
});
198+
await queue.enqueueMessage({
199+
env,
200+
message: makeMessage({ runId: "old-0", concurrencyKey: "old", timestamp: t0 + 100 }),
201+
workerQueue: env.id,
202+
skipDequeueProcessing: true,
203+
});
204+
205+
const ckv = testOptions.keys.ckVtimeKeyFromQueue(variantName("owed"));
206+
for (let i = 0; i < FUTURE; i++) await queue.redis.zadd(ckv, 0, variantName(`future-${i}`));
207+
await queue.redis.zadd(ckv, 1, variantName("owed"));
208+
await queue.redis.zadd(ckv, 5, variantName("old"));
209+
210+
const served: string[] = [];
211+
for (let c = 0; c < 4 && served.length < 1; c++) {
212+
for (const m of await queue.testDequeueFromMasterQueue(shard, env.id, MAX)) {
213+
served.push(m.message.concurrencyKey as string);
214+
await queue.acknowledgeMessage(env.organization.id, m.messageId, {
215+
skipDequeueProcessing: true,
216+
});
217+
}
218+
}
219+
220+
expect(served[0]).toBe("owed");
221+
} finally {
222+
await queue.quit();
223+
}
224+
},
225+
60_000
226+
);
153227
});

0 commit comments

Comments
 (0)