Skip to content

Commit 59faa47

Browse files
authored
perf(test): eliminate SQLite fsync and real rate-limit backoff wait in tests (#8553)
Profiling queue-lifecycle-guards.test.ts (33.7s) and backfill.test.ts (19.2s) found two more real per-test costs, confirmed via a CPU profile showing wall time going to idle (real I/O wait), not compute. SQLite fsyncs on every autocommit write by default; TestD1Database's clones are real temp files (needed for the template-clone trick from the earlier fix), so every app .prepare(...).run() paid a real fsync. journal_mode=MEMORY + synchronous=OFF are safe for a throwaway clone unlinked as soon as it's open -- crash-consistency durability is meaningless for data nothing outlives the test to read back. Benchmarked: 50 inserts, 13.9ms default vs 0.6ms tuned. fetchWithGitHubRetry's exponential backoff (500/1000/2000ms real setTimeout) is legitimate production behavior for a genuine 403/429, but several tests deliberately drive a fixture through the full retry loop to assert the resulting sync state, not the delay -- paying the full 3.5s every time. client.ts gains setGithubRateLimitRetrySleepCapMsForTest(), capping only what is actually awaited, not rateLimitRetryMs's return value -- its dedicated pure-function backoff-math test stays untouched and still exercises the real numbers. Both wired once, suite-wide, via a new test/helpers/vitest-setup.ts (vitest.config.ts setupFiles) -- same ...ForTest convention as clearInstallationTokenCacheForTest, applied automatically so every current AND future test with this shape benefits without opting in. queue-lifecycle-guards.test.ts: 33.7s -> 7.2s. backfill.test.ts: 19.2s -> 1.7s.
1 parent ec6f6e6 commit 59faa47

5 files changed

Lines changed: 51 additions & 2 deletions

File tree

src/github/backfill.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2424,6 +2424,20 @@ function toPullRequestFileRecordFromGitHub(repoFullName: string, pullNumber: num
24242424
// manualReview alone — it might be a human's own hold, not just a stale bot one) — so a guardrail-configured
24252425
// repo's PR could otherwise sit "held for manual review" indefinitely over nothing but a fetch timing gap.
24262426
const REVIEW_FILES_EMPTY_RETRY_DELAY_MS = 500;
2427+
// Test-only override (#test-hotspots): the dedicated retry tests (backfill-2.test.ts) pin retry BEHAVIOR
2428+
// (attempt count, which response wins), never the wall-clock delay itself. Any OTHER test whose fetch stub
2429+
// happens to return an empty files array (a common, often-incidental stub shape) pays this real 500ms sleep
2430+
// unknowingly — confirmed as a major contributor to queue-lifecycle-guards.test.ts's slowest tests. Same
2431+
// `...ForTest` convention as clearInstallationTokenCacheForTest / setMaxChunksPerRepoForTest; the whole
2432+
// suite defaults it near-zero via test/helpers/vitest-setup.ts (vitest.config.ts's setupFiles), so this
2433+
// fires for every current AND future test with this shape, not just ones an author remembers to opt in.
2434+
let reviewFilesEmptyRetryDelayMsOverride: number | null = null;
2435+
export function reviewFilesEmptyRetryDelayMs(): number {
2436+
return reviewFilesEmptyRetryDelayMsOverride ?? REVIEW_FILES_EMPTY_RETRY_DELAY_MS;
2437+
}
2438+
export function setReviewFilesEmptyRetryDelayMsForTest(value: number | null): void {
2439+
reviewFilesEmptyRetryDelayMsOverride = value;
2440+
}
24272441

24282442
const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
24292443

@@ -2457,7 +2471,7 @@ export async function fetchAndStorePullRequestFilesForReview(
24572471
const fetchOnce = () => fetchPullRequestFiles(env, repoFullName, pullNumber, token, warnings, admissionKey, "live_review").catch(() => [] as GitHubFilePayload[]);
24582472
let files = await fetchOnce();
24592473
if (files.length === 0) {
2460-
await sleep(REVIEW_FILES_EMPTY_RETRY_DELAY_MS);
2474+
await sleep(reviewFilesEmptyRetryDelayMs());
24612475
files = await fetchOnce();
24622476
}
24632477
if (warnings.length > 0) {

src/github/client.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,20 @@ export function isGitHubResponseCacheReplay(response: Response): boolean {
390390
const GITHUB_RATE_LIMIT_MAX_RETRIES = 3;
391391
const GITHUB_RATE_LIMIT_MAX_DELAY_MS = 8_000;
392392

393-
const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
393+
// Test-only override (#test-hotspots): several tests deliberately drive fetchWithGitHubRetry through its
394+
// FULL rate-limit backoff (up to 500+1000+2000=3500ms real wall time) to assert the resulting sync state
395+
// (rate_limited status, capped segments) -- not the delay itself. This caps what's actually AWAITED
396+
// without touching rateLimitRetryMs's own return value, so its dedicated pure-function correctness test
397+
// (github-app.test.ts, asserting exact backoff math) stays exercising the real numbers unmodified. Same
398+
// `...ForTest` convention as reviewFilesEmptyRetryDelayMs; the suite defaults it near-zero suite-wide via
399+
// test/helpers/vitest-setup.ts.
400+
let githubRateLimitRetrySleepCapMsOverride: number | null = null;
401+
export function setGithubRateLimitRetrySleepCapMsForTest(value: number | null): void {
402+
githubRateLimitRetrySleepCapMsOverride = value;
403+
}
404+
405+
const sleep = (ms: number): Promise<void> =>
406+
new Promise((resolve) => setTimeout(resolve, githubRateLimitRetrySleepCapMsOverride ?? ms));
394407

395408
/** Does this GitHub response signal a rate limit (primary or secondary)? 403/429 with a Retry-After header, an
396409
* exhausted x-ratelimit-remaining, or a secondary-limit/abuse body. A 403 with NONE of these is a real

test/helpers/d1.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ export class TestD1Database {
8686
);
8787
copyFileSync(getMigratedTemplatePath(), clonePath);
8888
this.db = new DatabaseSync(clonePath);
89+
// #test-hotspots: SQLite's defaults (journal_mode=DELETE, synchronous=FULL) fsync on every
90+
// autocommit write -- each of the app's `.prepare(...).run()` calls is its own implicit
91+
// transaction, so a write-heavy test (audit events, PR upserts, gate results) pays a real disk
92+
// fsync per statement. Under concurrent CI/local load this dominated wall time far more than a
93+
// quiet-machine benchmark suggests (queue-lifecycle-guards.test.ts: ~1000ms on several tests).
94+
// Both are safe to disable for a throwaway per-test clone that's unlinked as soon as it's open:
95+
// crash-consistency durability is meaningless for data nothing outlives the test to read back.
96+
this.db.exec("PRAGMA journal_mode = MEMORY; PRAGMA synchronous = OFF;");
8997
state.clonePaths.push(clonePath);
9098
if (!state.exitSweepRegistered) {
9199
state.exitSweepRegistered = true;

test/helpers/vitest-setup.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Runs once per test file (Vitest's setupFiles, distinct from globalSetup which runs once for the
2+
// whole run in the main process — this needs to touch each file's own module registry). Defaults
3+
// production retry/backoff delays that exist for real network-timing reasons to near-zero so a test
4+
// whose stub incidentally triggers one (e.g. an empty-files fetch stub tripping
5+
// fetchAndStorePullRequestFilesForReview's empty-retry) doesn't pay real wall-clock time for it
6+
// (#test-hotspots). The delay CONSTANT and its production default are untouched — this only sets the
7+
// `...ForTest` override every such helper already exposes; a dedicated test asserting the retry's own
8+
// behavior (attempt count, precedence) still exercises the identical code path, just without the sleep.
9+
import { setReviewFilesEmptyRetryDelayMsForTest } from "../../src/github/backfill";
10+
import { setGithubRateLimitRetrySleepCapMsForTest } from "../../src/github/client";
11+
12+
setReviewFilesEmptyRetryDelayMsForTest(0);
13+
setGithubRateLimitRetrySleepCapMsForTest(0);

vitest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default defineConfig({
1717
globals: true,
1818
testTimeout: 15000,
1919
globalSetup: ["./test/helpers/vitest-global-setup-node-version.ts"],
20+
setupFiles: ["./test/helpers/vitest-setup.ts"],
2021
// Retry a failed test once before failing the run. The loopover gate auto-CLOSES a contributor PR
2122
// on a red required CI, so a single transient flake must not kill an honest PR; a deterministic
2223
// failure still fails both attempts (and vitest flags the retried test as flaky so it stays visible).

0 commit comments

Comments
 (0)