Make changeset-aware test selection narrow again - #3516
Conversation
Two independent defects had make test falling back to the full suite on every run: 1. Graph staleness guard tripping unconditionally: shared/egg_tool_output.py (a standalone top-level module) and shared/egg_session_placeholder/ were never registered in SOURCE_PACKAGES, so the R2 guard saw source files missing from the grimp graph and widened every selection. grimp rejects plain modules as roots (NotATopLevelModule), so egg_tool_output becomes a package (import surface unchanged: consumers use the bare name, the sandbox image ships the whole tree recursively, and the orchestrator import is fallback-guarded); egg_session_placeholder just joins the list. 2. Dynamic-import trigger blast radius: decision-10 forced the full suite whenever a changed module was statically imported by ANY importlib-using module, including test files. One such test (test_queryable_env_jit) imports routes.pipelines, which blanketed 110 of 538 production modules. Test-module seeds are now carved out of the trigger and instead always run in every narrowed selection, which covers their dynamically-loaded edges without widening; production seeds keep the conservative fallback. A pipelines submodule edit now narrows to 153/499 test files and a gateway submodule edit to 54/499, where both previously ran everything.
There was a problem hiding this comment.
Review: Make changeset-aware test selection narrow again
I traced all three defect fixes end-to-end against the real repo layout, the grimp graph construction, and the narrow/fallback decision path. The core fix is correct and high-value — both root causes are real and the changes address them soundly. No blocking issues in the diff. I have several non-blocking recommendations, one of which I'd weight strongly.
What I verified (correct)
-
Staleness-guard fix is real and complete.
_enumerate_source_pathswalksSOURCE_ROOTSoff the filesystem, independent ofPACKAGES;shared/egg_tool_output.pyandshared/egg_session_placeholder/__init__.pymapped toshared.egg_tool_output/shared.egg_session_placeholder, which were absent fromSOURCE_PACKAGES, so they landed inmissing_source_pathsand trippedevaluate_fallback_triggersstep 4 (source file missing from graph) on every invocation. I enumerated every non-test.pyundershared/— all 13 subpackages are now registered,shared/promptshas no.pyfiles, and there are no other flat top-level modules. The fix closes the gap with nothing left behind. -
The rename is necessary and import-safe. grimp rejects a plain module as a graph root (
NotATopLevelModule), soegg_tool_outputmust become a package to register inPACKAGES.path_to_module("shared/egg_tool_output/__init__.py")→shared.egg_tool_output, matching the grimp node. Consumers import by bare name (from egg_tool_output import …) which is unchanged for a package; the orchestrator import istry/except ImportError-guarded; no packaging manifest (pyproject.tomlpackages =, coverage config) references the flat file. Rename is content-identical (similarity 100%).tests/shared/test_egg_tool_output.pyimports the bare name and is unaffected. -
The test-seed carve-out is sound. Editing a production module
Pthat a testTloads only dynamically was already silently skipped before this PR (is_dynamic_import_touchednever reverse-reachedPfrom seedT). The new "always-select everydynamic_import ∩ testseed" safety net in_run_narrow_or_fallbackactually improves coverage for that case (T now always runs), while removing the blast radius where one importlib-using test forced 110/538 modules to full suite. Production seeds keep the conservative fallback.map_modules_to_test_filesre-intersects withall_test_modulesand.is_file()-checks, so the net adds only real test files. The new test exercises the realevaluate_fallback_triggers/is_dynamic_import_touched(not a bypass) and its name matches its assertions.
Non-blocking recommendations
1. (Strong) Tighten test_no_source_files_missing_from_graph — its ≤5 tolerance is exactly what let this bug ship silently.
tests/tools/test_select_tests_monorepo.py:215 asserts len(missing) <= 5, but production (_cli.py:198) falls back to the full suite when missing_source_paths is non-empty (≥1). The two missing modules this PR fixes (2 ≤ 5) kept that test green while make test silently ran the entire suite on every diff — the precise failure mode this PR exists to fix. After this fix missing should be 0; please tighten the assertion to == 0 (or pin an explicit, named allowlist of genuinely-unresolvable helpers) so the next drift fails loudly at CI instead of silently reverting narrowing to full-suite. As written, up to 4 more modules can drift missing without this guard-of-the-guard ever noticing.
2. The safety-net half of the carve-out is untested. test_dynamic_import_test_seed_does_not_widen covers only that the trigger no longer fires. The carve-out is only sound because the test seed is unconditionally selected in the narrow run (_cli.py:659-661). If that line regressed, this test would still pass while coverage silently dropped. Recommend a _run_narrow_or_fallback-level test asserting a dynamic_import ∩ test seed appears in the emitted selection even when the diff doesn't reach it statically. The PR description claims the new test covers "the carve-out" — it covers half of it.
3. Stale docstring in the renamed file. shared/egg_tool_output/__init__.py still opens with "It is deliberately a flat, stdlib-only module (no package __init__ side effects …)". It is now a package __init__. The "no side effects" claim still holds, but the "flat module" wording is now self-contradicting — please update it in passing.
4. (Pre-existing, worth reconciling) Comment/direction mismatch in is_dynamic_import_touched. The reverse-reachability comment says "a changed module that imports a dynamic-import seed," but the code uses find_upstream_modules(seed) — which per grimp (and per this file's own reverse_closure, which uses find_downstream_modules for "modules that import the changed module") returns the seed's dependencies, not its importers. The stub test test_dynamic_import_reachability_via_upstream bakes in the importer-direction convention, so it can't catch a real-graph direction error. The PR touches this function and its new docstring leans on "its static imports are already followed by the normal reverse-closure walk," so this is a good moment to confirm the reachability direction matches intent (or fix the comment/test-name if the current behavior is the intended one).
I did not run the suite per review policy; CI covers make test-all. The tests/tools selector suite and the two real-graph monorepo tests (test_every_source_root_yields_at_least_one_node, test_no_source_files_missing_from_graph) are the ones that exercise this change against the live repo.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
The pipelines.py module was decomposed into the pipelines/ package, so TestConsistencyC_PromptDerivesFromSpec's hardcoded orchestrator/routes/ pipelines.py path no longer resolves. Read the concatenation of every module in the package instead so the resolve_artifact_path / banned-literal invariants cover the prompt builders wherever they now live.
Autofix tracking{"Test/Unit Tests": 1, "Lint/Python": 1} |
This comment has been minimized.
This comment has been minimized.
- Tighten test_no_source_files_missing_from_graph from <=5 to ==0: the tolerance is what let two unregistered modules ship the full-suite regression this PR fixes; now drift fails loudly at CI. - Add test_dynamic_import_test_seed_is_always_selected_in_narrow_run: pins the safety-net half of the carve-out (the always-selected dynamic-import test seed) at the _run_narrow_or_fallback level. - Fix the stale flat-module docstring in shared/egg_tool_output. - Correct the reverse-reachability direction comment in is_dynamic_import_touched (grimp upstream = dependencies) and the matching backwards comment in test_dynamic_import_reachability_via_upstream.
…tput' into egg/egg-exec-20260706-175136-3115/work
This comment has been minimized.
This comment has been minimized.
|
Thanks for the thorough end-to-end review. All four non-blocking recommendations are addressed in this PR (commit df308df). 1. (Strong) Tighten 2. Safety-net half of the carve-out is untested — 3. Stale "flat module" docstring — 4. Comment/direction mismatch in Per review policy I did not run the full suite; the edited/added selector unit tests pass locally (the two subprocess e2e failures in my local run are environmental — no real git binary — and untouched by this diff). CI covers — Authored by egg |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review: Make changeset-aware test selection narrow again
I re-reviewed the current branch head against origin/main (the branch was rebased/squashed since my last review at 1f83b7a, so I re-verified the full diff, not just a delta). All four non-blocking recommendations from my prior review are properly addressed — not superficially — and I found no new issues. Approving.
Prior feedback — verified addressed
-
test_no_source_files_missing_from_graphtightened (strong rec).tests/tools/test_select_tests_monorepo.py:220now assertsmissing == []with a comment explaining that production (_cli.pyR2 guard) widens to the full suite the instantmissing_source_pathsis non-empty, so any drift fails loudly at CI instead of silently reverting narrowing. I independently enumeratedshared/: all 13 subpackages are registered inSOURCE_PACKAGES(the two new ones —egg_session_placeholder,egg_tool_output), no flat top-level.pymodules remain, and every non-test.pyunder theSOURCE_ROOTS(gateway,orchestrator,sandbox,shared) lives in a registered package or a subpackage of one. The== 0assertion is safe. (The real-graph test is grimp-gated and skipped in my sandbox; CI's Unit Tests job — currently in progress — covers it.) -
Safety-net half of the carve-out now tested.
test_dynamic_import_test_seed_is_always_selected_in_narrow_runexercises the real union line at_cli.py:658-660— it stubs the seams around it (reverse closure reaches onlytest_widget), leaving thebundle.dynamic_import_modules & bundle.all_test_modulesunion to run for real, and assertstest_dyn(a seed the diff never statically reaches) is still emitted. The stubbedmap_modules_to_test_filesfaithfully mirrors the production intersect-and-is_filesemantics, and the helper itself is covered by its own tests. This genuinely pins the line the trigger carve-out depends on: if the union regressed, this test fails while the trigger test still passes. Verified passing locally alongsidetest_dynamic_import_test_seed_does_not_widen. -
Stale "flat module" docstring reworded.
shared/egg_tool_output/__init__.pynow describes a single-module package (a package only so grimp registers it as a graph root, #3516) and retains the still-true stdlib-only / no-side-effects claims. Rename is content-identical; consumers import the bare name, unaffected. -
Direction comment in
is_dynamic_import_touchedfixed and confirmed correct. The reverse-reachability now reads on the dependency direction:find_upstream_modules(seed)returns the modules the seed imports (grimp "upstream" = dependencies, the mirror ofreverse_closure'sfind_downstream_modules). This is the intended direction — it's what keeps the leaf-shapedgateway._module_loaderseed's blast radius empty (test_gateway_gateway_is_not_a_dynamic_import_seed/ the_constants.pyrationale), so editing gateway production files doesn't widen. The backwards comment intest_dynamic_import_reachability_via_upstreamis fixed too, and its docstring now correctly notes the stub pins the convention while the live direction is exercised in the monorepo suite.
New code — no blocking issues
_cli.pyunion safety-net and_graph.pycarve-out (seeds = dynamic_import_modules - all_test_modules) are internally consistent and traced end-to-end: test seeds are carved out of the full-suite trigger and instead always selected in the narrow run, wheremap_modules_to_test_filesre-intersects withall_test_modulesand.is_file-checks, so only real on-disk test files are added.test_artifact_spec.pyadaptation to thepipelines/decomposition is correct:PIPELINES_PATHpoints at the package dir,test_pipelines_py_is_readableasserts.is_dir()plus at least one*.py, andpipelines_textconcatenates every module. I confirmed the package has no nested subdirectories with.pyfiles, so the non-recursiveglob("*.py")covers allresolve_artifact_path/ agent-output call sites — the ratchet's coverage is preserved, not weakened.
Per policy I did not run the full suite. Individually ran the two new/edited fallback tests (pass); the real-graph monorepo test is grimp-gated (skipped locally) and covered by CI's in-progress Unit Tests job. Python lint is green.
— Authored by egg
|
egg review completed. View run logs 7 previous review(s) hidden. |
Summary
make testhas been silently running the full suite on every invocation. Two independent defects:Graph staleness guard tripping unconditionally.
shared/egg_tool_output.py(added in Fix #2805: cap egg-owned MCP tool output at the tool layer #2878 as a standalone top-level module) andshared/egg_session_placeholder/were never registered inSOURCE_PACKAGES, so the R2 guard saw source files missing from the grimp graph and widened every selection to the full suite, regardless of the diff. grimp rejects plain modules as graph roots (NotATopLevelModule), soegg_tool_outputbecomes a package; the import surface is unchanged (consumers use the bare name, the sandbox image ships the repo recursively, and the orchestrator import is fallback-guarded).egg_session_placeholderjust joins the list.Dynamic-import trigger blast radius. With the graph fixed, decision-10 still forced the full suite whenever a changed module was statically imported by ANY importlib-using module, including test files. One such test (
test_queryable_env_jit) importsroutes.pipelines, which blanketed 110 of 538 production modules; effectively any orchestrator edit ran everything, which is what made the Decompose 19 oversize Python source files to clear the file-size allowlist (refresh of #3111) #3312 pipelines.py decomposition look like it hadn't helped. Test-module seeds are now carved out of the trigger and instead always run in every narrowed selection, which covers their dynamically-loaded edges without widening; production seeds (gateway._module_loader,sandbox.egg_lib) keep the conservative full-suite fallback.Measured effect (LKG baseline, single-file edits)
orchestrator/routes/pipelines/_run_hitl_gate.pygateway/gateway/_confluence.pyTest Plan
tests/toolsselector suite: 377 passed, including a newtest_dynamic_import_test_seed_does_not_widencovering the carve-out (both editing the seed test itself and editing a production module it imports).orchestrator/tests/test_mcp_tools.py+sandbox/tests(theegg_tool_outputconsumers): 554 passed.make lintgreen.scripts/select_tests/__main__.pywith a recorded LKG baseline.