feat(test): support sharding tests across CI runners - #1707
peter-trost wants to merge 12 commits into
Conversation
Add `--shard-index` and `--total-shards` to `very_good test` and `very_good dart test`, so a test suite can be split across multiple CI runners with a `strategy.matrix`. The test optimizer already discovers every test file, so sharding is a partition of that list rather than new machinery. Files are sorted and dealt out round-robin, which keeps shards balanced by file count and makes the partition deterministic across machines — `Directory.listSync` order is filesystem dependent, so without sorting two runners could disagree and either skip or duplicate tests. Tests tagged `skip_very_good_optimization` are sharded as well. They run as standalone files alongside the optimizer entrypoint, so leaving them unsharded would re-run all of them on every runner. Each runner generates its own `.test_optimizer.dart` containing only its slice, so no shard-specific filenames are needed. A shard with no test files succeeds instead of failing with "No tests were found", so an oversized matrix does not break the build. Sharding is rejected with a usage error when combined with `--min-coverage`, since each shard only exercises a subset of the codebase and its coverage is not representative of the whole suite, and when the optimizer is disabled, which sharding depends on. Closes VeryGoodOpenSource#1538 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Normalize path separators in getNotOptimizedTests so tagged tests in subdirectories are excluded from the optimized set on Windows. Without this the relative paths compare unequal to the forward-slash normalized paths built in run(), causing those tests to be both inlined into the optimizer entrypoint and run standalone. Parse the shard values inside validateSharding instead of passing both the raw and parsed forms, and shorten the --min-coverage error. Also cover the shard that only contains non optimized tests, and document how sharding interacts with --recursive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thanks for the contribution @peter-trost ! ❤️ You can undraft it, since I actually havent started much on the sharding tests and this actually on first glance does it how i would do it. I will give it a test and bigger review later today/tomorrow but so far it looks good to me |
|
@ryzizub nice, glad to hear! Let me know if I can support in any way :) |
|
the flame e2e failing seems unrelated 🤔 |
Correct, there seems to be issue unrelated to this PR |
Sharding the optimized and skip_very_good_optimization lists separately restarted the round-robin at shard 1 for each list, so the first shards received a file from both while later shards could stay empty. Deal out the single sorted list once and split it afterwards, which also drops one shardOf call. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
a505315 adds a fix to put optimized and |
The bundle also embeds the hook test file, which gained a case in the previous commit after the bundle had last been generated, so the verify_bundle check failed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ryzizub
left a comment
There was a problem hiding this comment.
Overall LGTM, great job! Just a small findings
Route the raw --shard-index and --total-shards values through the test options classes instead of reading argResults in run(), make shardOf private now that the sharding behaviour is covered through run(), and name the flag values (<index>, <count>) while stating the default in their help text. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks for the review @ryzizub! Latest commit addresses the feedback. |
# Conflicts: # lib/src/cli/dart_cli.dart # lib/src/cli/flutter_cli.dart # lib/src/cli/templates/test_optimizer_bundle.dart # lib/src/cli/test_cli_runner.dart # test/src/commands/dart/commands/dart_test_test.dart Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@ryzizub merge conflicts fixed. |
|
Adding @marcossevilla as additional reviewer for this PR |
Validate sharding against the effective optimizer setting, so --platform, --update-goldens and explicit test paths are rejected instead of silently running the whole suite on every shard. Reject only an explicit --min-coverage and ignore a threshold inherited from very_good.yaml while sharding, since a single shard cannot meet it. Write an empty lcov.info for an empty shard so the merge step still finds a report per shard. Guard the hook against out of range shard values reached through `mason make`, look non optimized tests up in a set, keep the emitted test paths native, and move validateSharding next to TestCLIRunner so the dart command no longer depends on the flutter one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks for the thorough review @marcossevilla. Addressed all comments. 🙂 |
# Conflicts: # lib/src/cli/dart_cli.dart # lib/src/cli/test_cli_runner.dart # lib/src/commands/dart/commands/dart_test_command.dart # lib/src/commands/test/test.dart # test/src/commands/dart/commands/dart_test_test.dart # test/src/commands/test/test_test.dart Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@marcossevilla resolved conflicts. Ready for your re-review :) |
|
@peter-trost holding a bit on reviewing this since we had large changes for how optimization is handled inside the CLI, we addressed the issue on preserving test annotations and that should be the last. sorry to cause so many changes on this PR, please lmk when the conflicts are solved and I can give a fresh review 😄 |
# Conflicts: # bricks/test_optimizer/hooks/lib/pre_gen.dart # lib/src/cli/templates/test_optimizer_bundle.dart Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@marcossevilla no worries! :D The changes are integrated, please have another look :) |
TestOptimization.testTargets joins the hook's POSIX paths natively, so on Windows the expected value is test\integration\login_test.dart rather than the mixed test\integration/login_test.dart these assertions built with p.join. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@marcossevilla attempted a fix for the failing workflow on windows. Should pass now. 🤞 |
Description
Adds
--shard-indexand--total-shardstovery_good testandvery_good dart test, so a test suite can be split across multiple CI runners with astrategy.matrix.Opening as a draft because @ryzizub is assigned to #1538 — happy to hand this over, close it, or adapt to a different design if work is already underway or a different approach is preferred.
Why this approach
The test optimizer already discovers every test file, so sharding is a partition of that list rather than new machinery.
Directory.listSyncorder is filesystem dependent, so without sorting two runners could disagree on the partition and either skip or duplicate tests. Sorting first makes the partition deterministic across machines; dealing out round-robin keeps shards balanced by file count.skip_very_good_optimizationrun as standalone files alongside the optimizer entrypoint. Leaving them unsharded would re-run all of them on every runner, partly defeating the purpose..test_optimizer_1_of_3.dart. That turned out to be unnecessary — each runner generates its own.test_optimizer.dartcontaining only its slice, and the file is cleaned up afterwards anyway. Same result, no filename plumbing.No tests were found, breaking builds on oversized matrices. It now reports success, matching the existing "no test folder" behaviour.Interaction with coverage
--min-coverageis rejected with a usage error when sharding. Each shard only exercises a subset of the codebase, so its coverage is not representative of the whole suite and would fail the build spuriously. The error explains the alternative: collect per-shard coverage with--coverage, merge the lcov reports, and enforce the threshold once in a separate job.Sharding also requires the optimizer, so it is rejected with
--no-optimizationand with--platform(which disables the optimizer). Both cases exit withExitCode.usageand an actionable message.This is the part I'd most like a maintainer opinion on — a documented merge-then-check workflow is the other reasonable option, and #804 may change what's possible here.
Testing
testanddart test.dart analyze --fatal-infos --fatal-warnings .is clean, and the touchedlib/files are at 100% line coverage.Note
The full suite passes with
-j 1(529 tests). At-j 8a handful of unrelated tests fail due to a pre-existing working-directory race between test files — I confirmed the same failures on a cleanmaincheckout, so it is not introduced here.Closes #1538
🤖 Generated with Claude Code