Skip to content

ci: shard Python tests and reuse combined coverage for Sonar - #3989

Merged
huangruiteng merged 4 commits into
mainfrom
codex/python-ci-speed
Sep 6, 2026
Merged

huangruiteng merged 4 commits into
mainfrom
codex/python-ci-speed

Conversation

@huangruiteng

@huangruiteng huangruiteng commented Sep 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Run the full Python collection on two hosted runners, retaining two xdist workers per runner. Use pytest-split, not a bespoke scheduler or hand-maintained directory list.
  • Keep lint, type checks, TypeScript qualification and CLI output budgets in a parallel job. Preserve the required pytest check as a fail-closed aggregator: all upstream jobs must succeed, both coverage files must exist, and combined coverage must meet the unchanged 19.6% floor.
  • Reuse the same run's combined XML in a callable Sonar workflow. Remove the second full pytest run. Keep read-only permissions, step-scoped Sonar token handling, successful missing-token skips, and non-blocking scanning; no privileged trigger or cross-run artifact download.
  • Consolidate three identical immutable-checkout maintainability scans into one, retaining every distinct assertion and all temporary-repository negative cases.
  • Retain the earlier scheduler test fix: inject the already-supported sleep callable instead of monkeypatching a default bound at definition time. Production behavior is unchanged; three CLI calls and the requested 60-second cadence remain asserted.

Scheduling tradeoff

The workflow now uses the union of the previous Python/Sonar path filters. App-only and Sonar-configuration changes also run the Python lane, including forks without a Sonar token. This avoids a second routing framework and ensures coverage belongs to the exact analyzed run. Local full pytest remains unsharded by default. Timing-history-based balancing is not introduced in this iteration.

Evidence

  • Previous same-runner auto/work-stealing experiment did not establish a win: primary pytest 794.51s versus a recent 773.00s baseline. That setting was reverted; this PR does not claim that adding workers to one host helps.
  • Immediate pre-sharding head ba3da4808: Python run passed 6040 tests / 25 skips; pytest 804.83s, Linux job 14m57s. Sonar run independently repeated them in 904.90s.
  • Same-host, same-interpreter scan group: three builds 27.11s; consolidated build 6.33s. Warm-cache variance applies; this is not a whole-CI speed claim.
  • Same-host scheduler regression: 60.71s before versus 0.61s with injected sleep.
  • Complete collection proof: 6081 items = 3041 + 3040, with zero overlap, omissions or extras. Two former tests were consolidated, not silently skipped; new workflow regressions cover the replacement orchestration.
  • 34 focused tests passed; final 17 workflow tests rerun after adding each-missing-artifact cases. Real pytest-split/xdist runs in distinct fixture roots prove disjoint execution, portable coverage union, insufficient individual coverage and fail-closed aggregation. All 16 upstream status combinations are tested using the workflow's actual gate shell.
  • Ruff configured repository scope, mypy (21 files), actionlint, YAML parsing, diff hygiene and public-boundary scan passed.
  • Exact quality receipt after refreshing the base: cqr_4f8d5981c6fd511a8c93, fingerprint 4f8d5981c6fd511a8c9322c579548ac4c2b2808c03ec5ebe206e13da246f8628, 9 files, valid; safe-fix allowed/not used, zero blockers/warnings; one resolved Sonar mode-conflict advisory and one remaining scan-scope advisory below.

Hosted outcome

Run 34016648962 tested head 58646c326 as merge ff3f69b7589ed16a6e7d14c2b4d0d9107a399080 against base 8c08efd3a. Both shards and the aggregate checked out that same merge commit.

Measurement Before Two-runner candidate
Workflow creation to required pytest success, including queue/setup/aggregation 15m01s 9m53s (34.2% shorter)
Pytest execution 804.83s 492.85s and 431.68s in parallel
Test outcomes 6040 passed / 25 skipped 6087 passed / 25 skipped
Combined coverage 72.83% 72.85%
Duplicate Sonar pytest invocation 904.90s None

The second runner started approximately one minute later than the first; that delay is included. This is one hosted sample, not strict same-source/hardware A/B: main advanced with 31 additional tests after the local collection proof. No changed PR path overlaps the new base changes. The full Python workflow, Windows tests, other build checks and final premerge passed. Final premerge ran 4 direct checks, 9 catalog canaries, 8 risk-profile smokes and one public-boundary check, with zero failures or manual holds. No merge requested or performed for this CI change.

Sonar platform repair and remaining scope limitation

Both the baseline and first candidate scanner attempt reported an existing Automatic Analysis/CI conflict. With explicit owner authorization, the project's Automatic Analysis was switched Off; no organization settings or quality gate thresholds changed. Only the Sonar job was rerun, reusing artifact 9984231555 from the completed pytest aggregator. It took 3m26s, explicitly imported coverage.xml, and logged ANALYSIS SUCCESSFUL and EXECUTION SUCCESS. Server background task AaB1fPSRbMgFd3KsTHju is Success; the separate PR SonarCloud Code Analysis check and quality gate now pass. The earlier Python timing is unaffected by this Sonar-only retry. Do not treat the first rejected 33-second scan as successful analysis latency.

The existing sonar-project.properties analyzes loopx,apps,scripts with tests as test sources; it does not include .github. Automatic analysis previously reported two dependency-version-lock findings on the new workflow install steps (lines 113 and 154). Those dependency locks have not been fixed, waived or marked false positive; the warnings disappear from the CI scan because of its pre-existing scope, not because this PR fixes dependency pinning. Expanding workflow scanning and introducing a shared Python test lockfile remain explicit follow-up work. A later, separate main-branch background task failure is not the successful PR analysis task.

No runtime code, reduced coverage floor, private state, credentials, raw logs or generated timing data are included. The bounded future-facing pass removes duplicate test and coverage ownership rather than adding production caching.

Signed-off-by: huangruiteng <huangrt01@163.com>
Signed-off-by: huangruiteng <huangrt01@163.com>
@huangruiteng huangruiteng changed the title ci: speed up Python tests with capped work stealing ci: remove an unintended 60-second test wait and expose slow tests Sep 6, 2026
Signed-off-by: huangruiteng <huangrt01@163.com>
Signed-off-by: huangruiteng <huangrt01@163.com>
@huangruiteng huangruiteng changed the title ci: remove an unintended 60-second test wait and expose slow tests ci: shard Python tests and reuse combined coverage for Sonar Sep 6, 2026
@huangruiteng
huangruiteng merged commit a07f217 into main Sep 6, 2026
20 checks passed
@huangruiteng
huangruiteng deleted the codex/python-ci-speed branch September 6, 2026 06:58
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