Why
#11558 made suite-granularity shard plans correct by recovering modules that were missing from the initial xcodebuild -enumerate-tests output. That recovery still depended on simulator-based test enumeration, though: -enumerate-tests boots every test bundle’s runner to discover suites, and under simulator boot contention runners can fail to bootstrap with errors like Early unexpected exit, operation never finished bootstrapping.
When the bulk enumeration pass drops most modules, the per-module recovery path is reliable but pathologically slow. We saw plans where only 2 of 70 modules were enumerated initially, forcing 68 isolated re-enumerations and stretching the build phase toward an hour.
The durable fix is to remove client-side suite enumeration from the hot path and make the server produce a shard plan from deterministic build metadata plus historical test data.
What Changed
- The CLI no longer boots test bundles to enumerate suites while creating a shard plan. It reads the test modules from the built
.xctestrun file and sends those modules to the server.
- The server derives suite inventory per module. For each module in the current
.xctestrun, it selects the latest CI run on the build branch that included that module and has suite data; modules without branch history fall back to the project’s default branch. This keeps selective-testing runs from hiding modules skipped in the latest branch run.
- Suite durations still come from the existing 30-day historical timing query, so known suites remain balanced by historical runtime.
- For suite granularity, the final requested shard is the catch-all. Earlier shards receive their assigned suites as
-only-testing; the final shard receives -skip-testing entries for every suite assigned to earlier shards and no -only-testing, so newly added, renamed, or otherwise unknown suites still run.
- Module granularity remains based on the
.xctestrun module list and does not need a catch-all.
- The shard response schema now includes an optional
skip list, and the generated OpenAPI/Swift client files were updated for that field.
- The server gates the suite catch-all response by CLI version so older CLIs keep receiving shard shapes they can understand.
- The test-sharding docs now explain that suite plans use per-module branch-first historical inventory, default-branch fallback, and the final shard as the catch-all.
Compatibility
Older CLI versions do not know how to apply the server-provided skip identifiers. The server therefore only enables the suite catch-all behavior for CLIs at or after the supported canary version and preserves legacy suite shard reads for older versions.
This keeps existing released CLIs from receiving a shard that depends on -skip-testing support they do not have.
Validation
mise exec -- mix test test/tuist/shards_test.exs
mise exec -- mix test test/tuist_web/controllers/api/shards_controller_test.exs
mise exec -- mix test test/tuist_web/live/overview_live_test.exs --seed 530355
mise run cli:lint --fix
- Focused Swift sharding and CLI metadata header tests
- GitHub Actions are green on
770d00e7906 after merging the latest main.