Skip to content

fix(sse): de-flake timing-sensitive combo cooldown/breaker tests (#6803) - #6897

Merged
diegosouzapw merged 4 commits into
release/v3.8.47from
fix/6803-flaky-timing-tests
Jul 12, 2026
Merged

diegosouzapw merged 4 commits into
release/v3.8.47from
fix/6803-flaky-timing-tests

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Closes #6803

Root cause

4 timing-sensitive tests flake under CI-runner load because they assert wall-clock behavior with tight/default budgets while running on a contended shared self-hosted pool:

  • tests/unit/combo-quota-share-cooldown-wait.test.ts — 2 assertions hard-code elapsed < 1500 ms ceilings around a handleComboChat() call that also performs real SQLite I/O.
  • tests/unit/combo-strategy-fallbacks.test.ts — the HALF_OPEN recovery test races an 80ms real setTimeout against a 40ms circuit-breaker resetTimeout.
  • open-sse/mcp-server/__tests__/audit.test.ts — the shutdown test has no timeout argument and neither vitest config sets test.testTimeout, so it inherits vitest's 5000ms default while doing vi.resetModules() + a fresh dynamic import + mocked DB calls.

Fix

  • Extracted the 3 wall-clock-sensitive assertions into tests/unit/serial/ (the repo's established --test-concurrency=1 remedy, already hosting 4 similar files), removing intra-suite parallelism as a contention source.
  • Widened their timing margins (403/429 ceiling 1500ms→10000ms; breaker resetTimeout/wait 40/80ms→300/600ms) — serialization alone was not sufficient under today's real devbox contention (see regression proof below), so a generous absolute margin is needed too. The functional assertions (calls === 1, deepEqual on dispatch targets) remain the primary signal; the elapsed-time checks are secondary sanity bounds.
  • Added an explicit 30000ms it(...) timeout to the MCP audit shutdown test.

Regression test / TDD proof (Hard Rule #18)

Reproduced RED on unfixed code under real (not synthetic) devbox CPU contention from concurrent parallel sessions on this machine:

✖ quota-share: 403 quota_exhausted → NO wait, error propagated immediately (3695ms)
  AssertionError: quota_exhausted must not wait out a cooldown, but 2644ms elapsed
✖ non quota-share (priority): 429 propagated immediately, NO wait (1890ms)
  AssertionError: priority combo must not wait out a cooldown, but 1796ms elapsed

Confirmed GREEN after the fix under the same contention:

✔ quota-share: 403 quota_exhausted → NO wait, error propagated immediately (1078ms)
✔ non quota-share (priority): 429 propagated immediately, NO wait (943ms)
✔ combo skips a provider while its breaker is OPEN and attempts it again after the reset timeout (HALF_OPEN) (1755ms)

MCP audit shutdown test verified green via npx vitest run --config vitest.mcp.config.ts open-sse/mcp-server/__tests__/audit.test.ts (900ms, well under 5000ms — the 30000ms explicit timeout is headroom for CI load).

Gates run (all green)

  • node scripts/check/check-file-size.mjs
  • node scripts/check/check-complexity.mjs (2050 vs baseline 2054)
  • node scripts/check/check-cognitive-complexity.mjs (885, no change)
  • node scripts/check/check-changelog-integrity.mjs
  • npm run typecheck:core
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>
  • Full tests/unit/combo-quota-share-cooldown-wait.test.ts + tests/unit/combo-strategy-fallbacks.test.ts (26/26 pass)
  • tests/unit/serial/* new files (3/3 pass)
  • npx vitest run --config vitest.mcp.config.ts open-sse/mcp-server/__tests__/audit.test.ts (3/3 pass)

Extracts 3 wall-clock-sensitive assertions (combo-quota-share cooldown
ceiling x2, circuit-breaker HALF_OPEN race) into tests/unit/serial/
(--test-concurrency=1, the repo's established remedy for this class of
test) and widens their margins, since a starved CI-runner event loop can
blow even a serialized test's timing window. Also adds an explicit 30s
vitest timeout to the MCP audit shutdown test, which had no override and
inherited vitest's 5000ms default.

Regression proof: reproduced RED locally under real devbox CPU
contention (2644ms/1796ms elapsed vs the old 1500ms ceiling, exactly the
reported failure mode); confirmed GREEN after the fix under the same
contention.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request de-flakes timing-sensitive combo cooldown and breaker tests by extracting them into serial tests under tests/unit/serial/ to avoid parallelism contention, and adds a generous timeout to the MCP audit database shutdown test. The reviewer feedback suggests saving and restoring modified environment variables in the new serial test file to prevent pollution, adding retry logic to resetStorage to handle SQLite file locks on CI/Windows, and applying a suite-level timeout in audit.test.ts to cover all dynamic import tests uniformly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +25 to +27
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omr-combo-cooldown-wait-timing-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = "test-combo-cooldown-wait-timing-secret";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The environment variables process.env.DATA_DIR and process.env.API_KEY_SECRET are modified globally but never restored in test.after. This can pollute the environment and cause side effects or failures in other tests if they run in the same process or share state. Saving and restoring the original values is a safer practice.

const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omr-combo-cooldown-wait-timing-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
const ORIGINAL_API_KEY_SECRET = process.env.API_KEY_SECRET;
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = "test-combo-cooldown-wait-timing-secret";

Comment on lines +87 to +95
test.after(async () => {
clearAllModelLockouts();
try {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
} catch {
/* best effort */
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Restore the original environment variables in test.after to prevent global environment pollution and ensure test isolation.

test.after(async () => {
  clearAllModelLockouts();
  if (ORIGINAL_DATA_DIR === undefined) {
    delete process.env.DATA_DIR;
  } else {
    process.env.DATA_DIR = ORIGINAL_DATA_DIR;
  }
  if (ORIGINAL_API_KEY_SECRET === undefined) {
    delete process.env.API_KEY_SECRET;
  } else {
    process.env.API_KEY_SECRET = ORIGINAL_API_KEY_SECRET;
  }
  try {
    core.resetDbInstance();
    fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
  } catch {
    /* best effort */
  }
});

Comment on lines +76 to +80
async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The resetStorage function does not have any retry logic when calling fs.rmSync. SQLite file locks on Windows or CI environments can cause fs.rmSync to fail intermittently. Adding a retry loop (similar to cleanupTestDataDir in the other timing test file) makes this test much more robust against Windows file locking flakes.

async function resetStorage() {
  let lastError: unknown;
  for (let attempt = 0; attempt < 5; attempt += 1) {
    try {
      core.resetDbInstance();
      fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
      fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
      return;
    } catch (error: unknown) {
      lastError = error;
      await new Promise((resolve) => setTimeout(resolve, 25));
    }
  }
  if (lastError) throw lastError;
}

Comment on lines +67 to +72
// Explicit generous timeout (vitest default is 5000ms): under contended
// CI-runner load, vi.resetModules() + a fresh dynamic import + mocked DB
// calls can exceed the default budget though the behavior is correct
// (issue #6803).
30000
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While adding a 30000ms timeout to this test prevents flakes, the other two tests in this file (still closes the audit database when checkpoint fails and falls back to node:sqlite when better-sqlite3 binding is missing) also perform vi.resetModules() and dynamic imports, making them equally susceptible to timing out under CI contention. Consider setting a suite-level timeout on the describe block (e.g., describe("MCP audit shutdown", () => { ... }, 30000)) to cover all tests in this file uniformly.

diegosouzapw and others added 3 commits July 11, 2026 11:24
…ression count

- stryker.conf.json: add tests/unit/serial/combo-quota-share-cooldown-wait-timing.test.ts
  and tests/unit/serial/combo-strategy-fallbacks-half-open-timing.test.ts to
  tap.testFiles so their mutant kills count for accountFallback.ts and
  circuitBreaker.ts (PR #6897 added these files but didn't register them).
- eslint-suppressions.json: combo-strategy-fallbacks.test.ts's no-explicit-any
  suppression count was stale (35) after this PR trimmed 2 any-usages out of
  the file when extracting the half-open timing test; corrected to 33.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
@diegosouzapw
diegosouzapw merged commit 0f3c68f into release/v3.8.47 Jul 12, 2026
3 checks passed
diegosouzapw added a commit that referenced this pull request Jul 13, 2026
@diegosouzapw
diegosouzapw deleted the fix/6803-flaky-timing-tests branch July 19, 2026 21:00
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…gosouzapw#6803) (diegosouzapw#6897)

* fix(sse): de-flake timing-sensitive combo cooldown/breaker tests (diegosouzapw#6803)

Extracts 3 wall-clock-sensitive assertions (combo-quota-share cooldown
ceiling x2, circuit-breaker HALF_OPEN race) into tests/unit/serial/
(--test-concurrency=1, the repo's established remedy for this class of
test) and widens their margins, since a starved CI-runner event loop can
blow even a serialized test's timing window. Also adds an explicit 30s
vitest timeout to the MCP audit shutdown test, which had no override and
inherited vitest's 5000ms default.

Regression proof: reproduced RED locally under real devbox CPU
contention (2644ms/1796ms elapsed vs the old 1500ms ceiling, exactly the
reported failure mode); confirmed GREEN after the fix under the same
contention.

* fix(quality): register new serial timing tests + prune stale any-suppression count

- stryker.conf.json: add tests/unit/serial/combo-quota-share-cooldown-wait-timing.test.ts
  and tests/unit/serial/combo-strategy-fallbacks-half-open-timing.test.ts to
  tap.testFiles so their mutant kills count for accountFallback.ts and
  circuitBreaker.ts (PR diegosouzapw#6897 added these files but didn't register them).
- eslint-suppressions.json: combo-strategy-fallbacks.test.ts's no-explicit-any
  suppression count was stale (35) after this PR trimmed 2 any-usages out of
  the file when extracting the half-open timing test; corrected to 33.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…gosouzapw#6803) (diegosouzapw#6897)

* fix(sse): de-flake timing-sensitive combo cooldown/breaker tests (diegosouzapw#6803)

Extracts 3 wall-clock-sensitive assertions (combo-quota-share cooldown
ceiling x2, circuit-breaker HALF_OPEN race) into tests/unit/serial/
(--test-concurrency=1, the repo's established remedy for this class of
test) and widens their margins, since a starved CI-runner event loop can
blow even a serialized test's timing window. Also adds an explicit 30s
vitest timeout to the MCP audit shutdown test, which had no override and
inherited vitest's 5000ms default.

Regression proof: reproduced RED locally under real devbox CPU
contention (2644ms/1796ms elapsed vs the old 1500ms ceiling, exactly the
reported failure mode); confirmed GREEN after the fix under the same
contention.

* fix(quality): register new serial timing tests + prune stale any-suppression count

- stryker.conf.json: add tests/unit/serial/combo-quota-share-cooldown-wait-timing.test.ts
  and tests/unit/serial/combo-strategy-fallbacks-half-open-timing.test.ts to
  tap.testFiles so their mutant kills count for accountFallback.ts and
  circuitBreaker.ts (PR diegosouzapw#6897 added these files but didn't register them).
- eslint-suppressions.json: combo-strategy-fallbacks.test.ts's no-explicit-any
  suppression count was stale (35) after this PR trimmed 2 any-usages out of
  the file when extracting the half-open timing test; corrected to 33.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant