Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/e2e-test-budgets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/core': patch
---

Fix flaky timing-sensitive tests: stall-proof budgets for the events-consumer deferred-check tests and a sibling-matched budget for the TTL-expiration abort e2e test.
6 changes: 5 additions & 1 deletion packages/core/e2e/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4188,7 +4188,11 @@ describe('e2e', () => {

test(
'distributedAbortController - TTL expiration triggers signal',
{ timeout: 30_000 },
// Same budget as the sibling distributedAbortController tests: the 3s
// TTL is trivial, but cold starts plus queue backlog on a fresh prod
// deployment routinely pushed run start + stream delivery past the
// tighter 30s this test used to get.
{ timeout: 60_000 },
async () => {
const controllerId = `test-expire-${Math.random().toString(36).slice(2)}`;

Expand Down
16 changes: 12 additions & 4 deletions packages/core/src/events-consumer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,12 @@ describe('EventsConsumer', () => {
});
});

describe('duplicate event classes', () => {
// The deferred check reaches its outcome through a multi-stage timer chain
// (promise queue → setTimeout(0) → idle poll → delay timer), and loaded CI
// runners with coarse timers — Windows especially — can starve that chain
// for whole seconds. The polls below return as soon as their assertions
// hold, so a generous test budget costs healthy runs nothing.
describe('duplicate event classes', { timeout: 30_000 }, () => {
// Nothing here waits on the window for its result — a duplicate is stepped
// over in the pass that offers it — so run at the shortest legal delay and
// let the assertions that a check did NOT fire be cheap.
Expand All @@ -761,8 +766,11 @@ describe('EventsConsumer', () => {
// and the negatives alongside them are then evaluated at the moment the
// check is known to have fired, which is what the assertions mean.
function afterDeferredCheck(assertions: () => void): Promise<void> {
// The timeout bounds a stalled runner, not the expected path: a healthy
// run satisfies the assertions within a few windows. 2s (the previous
// bound) was regularly starved through on Windows CI runners.
return vi.waitFor(assertions, {
timeout: MIN_DEFERRED_CHECK_DELAY_MS * 200,
timeout: 15_000,
interval: MIN_DEFERRED_CHECK_DELAY_MS,
});
}
Expand Down Expand Up @@ -961,7 +969,7 @@ describe('EventsConsumer', () => {
expect(onDuplicateEvent).not.toHaveBeenCalled();
});

await vi.waitFor(() => {
await afterDeferredCheck(() => {
expect(onUnconsumedEvent).toHaveBeenCalledWith(events[3]);
});
});
Expand Down Expand Up @@ -989,7 +997,7 @@ describe('EventsConsumer', () => {
expect(onDuplicateEvent).not.toHaveBeenCalled();
});

await vi.waitFor(() => {
await afterDeferredCheck(() => {
expect(onUnconsumedEvent).toHaveBeenCalledWith(events[2]);
});
});
Expand Down
Loading