From 545c110833e19d7486efc3474f367ebc4bcbd958 Mon Sep 17 00:00:00 2001 From: garrytan Date: Wed, 28 Oct 2026 13:48:43 +0000 Subject: [PATCH] test(flaky): qa-fix-loop late-timeout freezes its clock through setup and registers the outer budget (GSTA-139) The #3413 qa-only template: the late scenario's real setup ran inside its 1000 ms mocked work budget and a 1160 ms native deadline, so under load the attempt never started (0/10 under 24 busy loops, attempts length 0). The clock now stands still from the body's start until the capture begins, the capture gets exactly 1000 ms, and the native deadline is the registered 6080 ms outer budget. Gate edit: captureBudgetMs is asserted equal to 1000. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- test/qa-fix-loop-fixture.test.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test/qa-fix-loop-fixture.test.ts b/test/qa-fix-loop-fixture.test.ts index 15956eee7..54267fb7b 100644 --- a/test/qa-fix-loop-fixture.test.ts +++ b/test/qa-fix-loop-fixture.test.ts @@ -21,7 +21,9 @@ const scenario = ${JSON.stringify(scenario)}; const attempts = [], records = [], servers = []; const late = scenario === 'late-timeout'; const workMs = 1000, drainMs = 80; -let finalized = false, outerMs; +const realNow = Date.now; +let finalized = false, outerMs, frozenAt; +if (late) Date.now = () => frozenAt ?? realNow(); const collector = { addTest: entry => { records.push({ ...entry, afterFinalization: finalized }); if (scenario === 'recording') throw new Error('QA recorder failure'); @@ -46,10 +48,10 @@ mock.module(path.join(root, 'test/helpers/e2e-helpers.ts'), () => ({ testConcurrentIfSelected: (id, body, budget) => { if (id !== 'qa-fix-loop') return; outerMs = budget; - // Keep the actual native outer deadline, scaling only its 5s recording - // margin to80ms. The pre-fix equal work/outer deadline stays equal. - const nativeBudget = late ? (budget >= workMs + 5000 ? budget - 5000 + 80 : budget) : 5000; - test(id, body, nativeBudget); + // The late attempt's clock stands still through its real setup, so the + // mocked work budget reaches the capture whole; the deadline is the + // registered outer budget. + test(id, late ? () => { frozenAt = realNow(); return body(); } : body, late ? budget : 5000); }, copyDirSync: (source, target) => fs.cpSync(source, target, { recursive: true }), setupBrowseShims: cwd => fs.mkdirSync(path.join(cwd, 'browse/bin'), { recursive: true }), @@ -62,6 +64,7 @@ mock.module(path.join(root, 'test/helpers/e2e-helpers.ts'), () => ({ mock.module(path.join(root, 'test/helpers/session-runner.ts'), () => ({ SESSION_DRAIN_GRACE_MS: late ? drainMs : 5000, runSkillTest: async options => { + frozenAt = undefined; const cwd = options.workingDirectory; const git = (...args) => execFileSync('git', args, { cwd, encoding: 'utf8', timeout: 5000 }); const initial = fs.readFileSync(path.join(cwd, 'index.html'), 'utf8'); @@ -102,6 +105,7 @@ afterAll(async () => { // behind child exit; this reproduces the observed late recording race. if (late) await new Promise(resolve => setTimeout(resolve, 200)); fs.writeFileSync(${JSON.stringify(factsPath)}, JSON.stringify({ attempts, records, servers, outerMs })); + Date.now = realNow; }); await import(path.join(root, 'test/skill-e2e-qa-workflow.test.ts')); `); @@ -152,8 +156,7 @@ await import(path.join(root, 'test/skill-e2e-qa-workflow.test.ts')); } if (scenario === 'late-timeout') { expect(outerMs).toBe(1000 + 80 + 5000); - expect(attempts[0].captureBudgetMs).toBeGreaterThan(0); - expect(attempts[0].captureBudgetMs).toBeLessThan(1000); + expect(attempts[0].captureBudgetMs).toBe(1000); expect(records[0].exit_reason).toBe('timeout'); expect(child.stderr).not.toContain('Unhandled error between tests'); }