Fix: stop per-file vitest budgets undercutting the integration project defaults - #478
Open
AmaadMartin wants to merge 5 commits into
Open
Conversation
added 5 commits
August 1, 2026 15:30
…budget The file declared its own TEST_EXECUTION_TIMEOUT = 40000 and passed it to every it(), which replaces the 60s 'integration' project budget rather than raising it. The discovery test then paid four esbuild bundles inside that shrunken window, which is what timed out on loaded macOS/Windows runners. Drop the constant so the tests inherit the project budget, and warm the loader in beforeAll so the bundling is charged to the 120s hook budget instead. Assertions are unchanged.
…egration Each of these files passed a flat constant below the 60s 'integration' project testTimeout to install-, compile- or subprocess-bound work: agent_dirname 40s, build_setup 20s, script_js 60s (a restatement) and run_skill_script 40s. A per-test timeout argument replaces the project budget rather than raising it, so these were silent downgrades. The run_skill_script note about needing to outlast UnsafeLocalCodeExecutor's 30s default is kept and re-anchored to the project budget, which satisfies it.
…the budget
The previous wording ('matches the largest per-file timeout in the repo')
is stale now that those per-file constants are gone, and it did not say
that a per-test argument replaces this value rather than adding to it.
Five sites still passed a timeout argument below the integration project floors: webui_test's beforeAll (20s) and its suite-level describe (20s), and the server-start beforeAll in the three a2a suites (60s vs the 120s hook default). All boot an HTTP/A2A server, which is exactly the work the 120s hook budget exists for. The two a2a TEST_TIMEOUT constants stay because startFailureTimeout still reads them, but are renamed SERVER_START_TIMEOUT_MS so the name no longer implies a vitest budget.
…dgets The rule was stated twice in different terms - the hook block said a hook must not pass its own argument, the test block said a per-test argument may raise. Same vitest semantics, so state it once above both constants. Fold in the one durable claim from the comment that outlived run_skill_script_tool_test's constant: the test budget must stay above UnsafeLocalCodeExecutor's default 30s timeoutSeconds so the executor's own error surfaces first. It constrains INTEGRATION_TEST_TIMEOUT_MS, so it belongs where someone editing 60000 will read it. Also drop 'esbuild-bundling' from the test-budget description: bundling is hoisted into the discovery beforeAll and is covered by the hook budget, so claiming the 60s test budget is sized for it was inaccurate.
This was referenced Aug 2, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
N/A — no public issue tracks this flake.
Problem:
tests/integration/app_loader/app_loader_test.ts > 'AgentLoader discovery and loading integration' > 'should discover apps vs agents across directories and standalone files'intermittently fails on themacos-latestandwindows-latestlegs of thevalidationworkflow withError: Test timed out in 40000ms.A plain re-run of the same commit passes, and no source file the test exercises changed — it is a budget problem, not a logic bug.The mechanism is not "40s is too small". It is that the file declares its own budget and thereby shrinks the one the repo already agreed on:
vitest.config.tssetstestTimeout: 60000andhookTimeout: 120000for theintegrationproject, precisely because these suites are install- and compile-bound.app_loader_test.tsdeclaredconst TEST_EXECUTION_TIMEOUT = 40000and passed it as the explicit per-call timeout argument to everyit()in the file. A per-call timeout argument replaces the project value rather than raising it, so the file lowered the test budget from 60s to 40s on exactly the work the default exists to protect.loader.listApps(), which reachesAgentLoader.preloadAgents()and esbuild-bundles all four discovered entrypoints (bundle: true,packages: 'bundle', the whole@google/adkgraph, perdev/src/utils/agent_loader.ts). Measured locally on an idle machine, that is 23,898 ms of the 40,000 ms budget spent before the first assertion runs.Solution: a deletion, not a timeout bump.
TEST_EXECUTION_TIMEOUTand every per-test timeout argument that used it, so the tests inherit the project budget (60s tests / 120s hooks).loader.preloadAgents()in the discoverybeforeAll, where it is charged to the 120s hook budget.AgentFile.load()is memoized too, so the subsequentlistApps()/listAgents()in the test body are in-memory lookups returning identical results.tests/integrationfor the same defect (table below). After this change no file undertests/integrationpasses a vitest timeout argument below the project floors — verified by grep, see Testing.vitest.config.ts: a per-hook or per-test argument replaces the budget rather than adding to it, so add one only to raise a specific operation, never to restate or lower one. The previous wording ("matches the largest per-file timeout in the repo") is stale once those constants are gone, and the two blocks had drifted into stating the same vitest semantics as two different rules.No production source is touched. Every
expect(...)is semantically unchanged.tests/integration/app_loader/app_loader_test.tsTEST_EXECUTION_TIMEOUT = 40000+ 4it()argstests/integration/agent_loader/agent_dirname_test.tsTEST_EXECUTION_TIMEOUT = 40000+ 1it()argtests/integration/build_setup/build_setup_test.tsTEST_EXECUTION_TIMEOUT = 20000+ 4it()argsts_esmWindows flake)tests/integration/skills/script_js/agent_test.tsTEST_EXECUTION_TIMEOUT = 60000+ 1it()argtests/integration/tools/run_skill_script_tool_test.tsTEST_EXECUTION_TIMEOUT = 40000+ 4it()argstests/integration/adk_web/webui_test.tsbeforeAll(..., 20000)and a suite-leveldescribe(..., 20000)serveDebugUIserver start this PR exists to de-flaketests/integration/a2a/basic/a2a_agent_test.tsbeforeAll(..., 60000)tests/integration/a2a/stream/stream_test.tsbeforeAll(..., TEST_TIMEOUT)startFailureTimeoutbut renamedSERVER_START_TIMEOUT_MStests/integration/a2a/input_required/input_required_test.tsbeforeAll(..., TEST_TIMEOUT)The one durable claim in
run_skill_script_tool_test.ts's comment — the vitest budget must outlastUnsafeLocalCodeExecutor's default 30stimeoutSecondsso the executor's own error surfaces first — is a constraint onINTEGRATION_TEST_TIMEOUT_MS, so it moved into that docblock where anyone editing60000will read it. Its stale opening clause ("can exceed vitest's 5000ms default") went with the constant.Why the
preloadAgents()hoist stays even though the budget already rose. DeletingTEST_EXECUTION_TIMEOUTraises this test 40s → 60s on its own, so the hoist is a second, independent change and it is fair to ask whether it earns its four lines. It does: the bundling measures 23,898 ms on an idle local machine, and the runners where this flakes are slow enough that the suite's vitest collect phase alone was measured in the hundreds of seconds. A 2.5x slowdown puts bundling alone past 60s. Bundling is fixture setup, so it belongs in the hook that has the 120s budget, not in a test body whose every assertion is atoHaveLengthon an in-memory array. The corollary is that the 60s test budget is not sized for esbuild bundling, so that clause was removed from theINTEGRATION_TEST_TIMEOUT_MSdescription rather than left to contradict this hunk.Collision check.
gh pr list --repo AmaadMartin/adk-js --state open --limit 1000(378 open PRs) surfaced several adjacent ones:fix/integration-hook-timeout-single-source— removes the hook timeout arguments in four of the same files and carries thebuild_setup~70s cold-install measurement into thevitest.config.tsJSDoc. It does not touch any per-testit()budget, so it does not fix the reported failure (which is a test timeout, not a hook timeout). Rather than duplicate it, this PR is stacked on Fix: align integration install hooks on the project-wide hook timeout #405's branch and supplies the other half: the per-test downgrades and the warm-up. Base isfix/integration-hook-timeout-single-source, notmain.fix/app-loader-discovery-timeout-flake— overlaps: it also addspreloadAgents()to the discoverybeforeAll, but pairs it with a newFIXTURE_SETUP_TIMEOUT = 180000on the hooks, which is the pattern Fix: align integration install hooks on the project-wide hook timeout #405 removes. It conflicts with Fix: align integration install hooks on the project-wide hook timeout #405 and leaves the 40s per-test budget in place.fix/app-loader-test-execution-timeout— raises the same constant to 60000 instead of deleting it, so the duplicate declaration survives and can drift again.discoveryfixture'snpm install/node_modulesresolution. Deliberately not attempted here (see Out of scope) so this diff rebases cleanly on top of whichever lands.run_skill_script_tool_test.tsbut add their own new named constants rather than editingTEST_EXECUTION_TIMEOUT; they will conflict textually, not semantically.fix/webui-test-named-hook-timeouts— overlaps onwebui_test.ts: it keeps the 20s hook budget and gives it a name plus a fail-fast rationale, and adds a 10safterAllbudget. This PR takes the opposite position and deletes the argument, because a 20s cap on aserveDebugUIserver boot is a downgrade of the 120s hook floor of exactly the kind that produced the flake being fixed. Whichever lands second should resolve in favour of one rule, not two.Audited and deliberately left unchanged:
tests/integration/test_api_server.ts(DEFAULT_TIMEOUT = 60000): a server-readiness poll budget, not a vitest budget.startFailureTimeout: 60000in the three a2a suites: also not a vitest budget — it is how long the server helper waits before throwing its own error. Left at 60000 so it still fires inside the 120s hook budget, which is the point of removing the hook argument.dev/test/utils/agent_loader_test.tsandtests/cross_language/**: real instances of the same pattern, but in theunit:devandcross-languageprojects, which have no project-level budgets at all. Fixing those means choosing new numbers for two other projects; out of scope here.Out of scope (intentional): removing the per-fixture
npm installin favour of the workspace-rootnode_modules. That is the cleaner long-term fix and is already in flight (#276, #299); duplicating it here would guarantee a conflict.Accepted trade-offs:
AgentFileLoadingError) throw during discovery now reports as abeforeAllfailure rather than a test failure. Discovery results are still asserted in the test body, so a wrong-set-of-apps regression still fails the test — proven by mutation 3 below.Formatting note: removing the third argument makes each
it(...)call fit Prettier's canonical single-line-callback form, so the diff re-indents the test bodies.git diff -wreduces the whole change to 42 insertions / 93 deletions across 10 files. The only assertion line whose text changes is oneexpect(response.toString()).toContain('Devtools verification successful')that Prettier un-wrapped because it now fits in 80 columns; the call and its argument are identical.Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
This change adds zero lines of production code, so there is no new code to cover and no unit test to add. Coverage cannot move:
git diff --name-onlyagainst the stacked base lists only files undertests/integration/plus the two docblocks invitest.config.ts; no*/src/**line and no unit test is added or removed, and thecoverage.thresholdsblock invitest.config.ts(statements: 86, branches: 87, functions: 88, lines: 86) is byte-identical. The burden of proof is therefore on showing that the budget actually changed and the cost actually moved.Unit Tests:
[x] I have added or updated unit tests for my change. — N/A by construction: no production code is added. Explained above rather than silently omitted.
[x] All unit tests pass locally. — no unit test file is in the diff.
Commands run locally on the pushed commit:
Results:
app_loader_test.ts: 6 passed (322.06s wall, dominated by the per-fixturenpm installin the hooks).agent_dirname_test.ts+skills/script_js/agent_test.ts+tools/run_skill_script_tool_test.ts: 12 passed, 4 skipped (253.24s). The 4 skips are theit.skipIf(!IS_WINDOWS)PowerShell/cmd cases, skipped on Linux exactly as before.build_setup_test.ts: 20 passed, 4 skipped (535.11s). Slowest test 5,808 ms (ts_esm > should build and run agent successfully) — comfortably inside the old 20s budget on an idle machine, which is why this one only flakes on a loaded Windows runner.adk_web/webui_test.ts+tools/run_skill_script_tool_test.ts: 16 passed, 4 skipped (14.01s).}, <number>)/}, <CONST>)timeout argument remains anywhere undertests/integration(startFailureTimeout:andtoHaveLength(n)excluded, neither being a vitest budget).npm run lint: clean.npm run format:check: "All matched files use Prettier code style!".npm run ts:check: 280 pre-existing errors, all in files this PR does not touch (core/test/**, two unrelatedtests/integration/**files). Identical count before and after the change (git stashA/B), so this PR neither adds nor fixes one. None of the ten touched files appears in the error list.Mutation 1 — prove the budget moved. Temporarily set
INTEGRATION_TEST_TIMEOUT_MS = 1invitest.config.tsand run the discovery test:TEST_EXECUTION_TIMEOUT = 40000): the test passes in 20,109 ms. A 1 ms project budget has no effect whatsoever — the per-file constant wins outright.AssertionError→Error: Test timed out in 1ms.That is the proof the tests now inherit the project budget instead of overriding it. Reverted afterwards.
Mutation 2 — prove the cost moved. Remove
await loader.preloadAgents();from thebeforeAlland compare vitest's reported duration for'should discover apps vs agents across directories and standalone files':On an idle local machine the test body was already consuming ~60% of the old 40s budget; that 23.9s delta is the flake margin this change buys. Reverted afterwards.
Mutation 3 — prove the assertions still bite. With the warm-up in place, temporarily rename
tests/integration/app_loader/discovery/standalone_app.tstostandalone_app.txtand re-run. The test fails on the assertion, not on a timeout:So the warm-up did not turn the test into a no-op — a genuinely wrong discovery result still fails the test body. Reverted afterwards.
Manual End-to-End (E2E) Tests:
Expect 6 passing tests and a reported duration for the discovery test in the low milliseconds (the bundling now happens in
beforeAll). To see the old behaviour, delete theawait loader.preloadAgents();line and re-run — the same test's duration jumps to ~24s.CI status: absent, validated locally instead.
.github/workflows/validation.yamlis gated onpull_request: branches: [main], and this PR is stacked onfix/integration-hook-timeout-single-source, so therun-testsjob (matrixubuntu-latest,windows-latest,macos-latest) does not trigger. The commands and results above were run locally on the exact pushed commit. The workflow will run once #405 merges and this PR retargetsmain.Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.