Batch libraries test Helix work items by compatibility metadata - #129632
Batch libraries test Helix work items by compatibility metadata#129632mmitche wants to merge 3 commits into
Conversation
Compose desktop batch commands from structured runner parts and group library Helix work items by compatibility metadata. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Emit non-batch-compatible desktop library work items directly so stress scenarios keep the original runner and timeout instead of flowing through ComputeBatchTimeout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Tagging subscribers to this area: @akoeplinger, @matouskozak, @simonrozsival |
There was a problem hiding this comment.
Pull request overview
This PR changes the libraries Helix submission flow to batch multiple compatible test payload ZIPs into fewer Helix work items, and adds a desktop batch runner that executes each suite inside a single work item while keeping per-suite results separated.
Changes:
- Extend the
GroupWorkItemsMSBuild task to partition candidates by a compatibility-metadata tuple (and optionally force “solo” batches). - Update
sendtohelixhelp.projto emit either classic per-assembly work items or new batched work items (with per-batch payload creation and timeouts). - Add
DesktopBatchRunnerscripts and a newHelixTestTasks.Testsproject with unit tests for the grouping logic.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/tasks/HelixTestTasks/GroupWorkItems.cs | Adds compatibility partitioning + “solo” batching logic and assigns globally-unique batch IDs across partitions. |
| src/tasks/HelixTestTasks.Tests/HelixTestTasks.Tests.csproj | Introduces a new xUnit test project for HelixTestTasks. |
| src/tasks/HelixTestTasks.Tests/GroupWorkItemsTests.cs | Adds unit tests validating partitioning and solo-batch behaviors. |
| src/libraries/sendtohelixhelp.proj | Adds batching opt-in default + emits batched work items, payload staging, and batch-runner command composition. |
| src/libraries/pretest.proj | Ensures HelixTestTasks is built for desktop library Helix batching scenarios (not just browser). |
| eng/testing/DesktopBatchRunner.sh | Adds Unix batch runner that unpacks suite ZIPs, sets per-suite upload roots, runs each suite, and summarizes outcomes. |
| eng/testing/DesktopBatchRunner.cmd | Adds Windows batch runner with similar behavior to the Unix runner. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| string batchCompatible = item.GetMetadata(batchCompatibleMetadataKey); | ||
| if (!string.IsNullOrEmpty(batchCompatible) && | ||
| !string.Equals(batchCompatible, "true", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| Log.LogMessage( | ||
| MessageImportance.Low, | ||
| "Keeping '{0}' in a solo batch because metadata '{1}' is '{2}'.", | ||
| item.ItemSpec, | ||
| batchCompatibleMetadataKey, | ||
| batchCompatible); | ||
| return true; | ||
| } | ||
| } |
| // Greedy bin-packing for small items | ||
| if (smallItems.Count > 0) | ||
| foreach (List<(ITaskItem item, long size)> smallItems in partitionedSmallItems.Values) | ||
| { | ||
| int numBatches = Math.Min(BatchSize, smallItems.Count); | ||
| var batchSizes = new long[numBatches]; |
| [Fact] | ||
| public void BatchCompatibleFalseItemsBecomeSoloBatches() | ||
| { | ||
| ITaskItem[] result = Execute( | ||
| batchSize: 2, | ||
| largeThreshold: 1_000, | ||
| compatibilityMetadataKeys: "RunnerType", | ||
| batchCompatibleMetadataKey: "BatchCompatible", | ||
| Item("one.zip", 100, ("RunnerType", "Desktop"), ("BatchCompatible", "true")), | ||
| Item("two.zip", 90, ("RunnerType", "Desktop"), ("BatchCompatible", "true")), | ||
| Item("stress.zip", 80, ("RunnerType", "Desktop"), ("BatchCompatible", "false"))); | ||
|
|
||
| Assert.NotEqual(BatchId(result, "stress.zip"), BatchId(result, "one.zip")); | ||
| Assert.NotEqual(BatchId(result, "stress.zip"), BatchId(result, "two.zip")); | ||
| Assert.Single(result.Where(item => item.GetMetadata("BatchId") == BatchId(result, "stress.zip"))); | ||
| } | ||
|
|
||
| [Fact] |
|
Workflow state for the Holistic Review Orchestrator. {
"version": 5,
"last_dispatched_commit": "3a38379a4d2810c8132ed00c4a88f2cbf2d55312",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "81df0579e5cd5560cbc3bfc2505a790e0e8d6511",
"last_reviewed_commit": "3a38379a4d2810c8132ed00c4a88f2cbf2d55312",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "81df0579e5cd5560cbc3bfc2505a790e0e8d6511",
"last_recorded_worker_run_id": "29677691788",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "3a38379a4d2810c8132ed00c4a88f2cbf2d55312",
"review_id": 4730523095
}
]
} |
There was a problem hiding this comment.
Holistic Review
Motivation: Reducing the thousands of per-assembly libraries-test Helix work items to cut fixed per-work-item overhead (queueing, provisioning, payload download) is a well-justified, real infrastructure cost problem, and the ~90% work-item reduction reported from a manual pipeline run is compelling evidence.
Approach: Partitioning assemblies by a compatibility-key tuple before greedy bin-packing, then running batched suites through a structured batch runner with isolated upload roots, is a reasonable and appropriately conservative design that keeps non-batch-compatible (e.g. stress) items on their original path. The opt-out flag (BatchLibraryHelixWorkItems=false) and unit tests are good hygiene.
Summary: MaxTestsPerWorkItem (it actually bounds the number of batches per partition, not tests-per-work-item) could surprise operators tuning it and interacts with timeout computation. This is test-infrastructure only (no product/API change) and gated behind a flag, so risk is contained; a human familiar with the Helix libraries pipeline should confirm the intended batching contract and that per-suite result upload/reporting still surfaces correctly under batching.
Detailed Findings
⚠️ Correctness / Naming — MaxTestsPerWorkItem bounds batch count, not tests per work item
See the inline comment on src/libraries/sendtohelixhelp.proj line 372. BatchSize is consumed as the number of bins per compatibility partition (numBatches = Math.Min(BatchSize, smallItems.Count)), so the property name overstates the guarantee — large partitions produce work items with more than 20 suites. This also feeds ComputeBatchTimeout's count * 20 min calculation. Recommend renaming or reworking to a true per-item cap.
✅ Compatibility partitioning and solo-batch handling
The compatibility-key tuple (RunnerType;PlatformFamily;RuntimeFlavor;TargetOS;TargetArchitecture;Scenario;TimeoutClass;PayloadShape) is applied before packing, and items missing required metadata or marked BatchCompatible!=true are correctly kept in solo batches. Non-batch items are emitted through the original HelixCommand/timeout path, preserving stress-scenario behavior. Negative solo batch IDs for large items are preserved. Behavior is well covered by the added unit tests.
✅ Batch runner isolation
DesktopBatchRunner.sh/.cmd give each suite its own HELIX_WORKITEM_UPLOAD_ROOT subdirectory to avoid result collisions, restore the original upload root afterward, aggregate per-suite exit codes, and fail the work item if any suite fails. Exit-code aggregation and the no-zip guard look correct.
💡 Minor observations
DesktopBatchRunner.shdoes notset -euo pipefail; it relies on${VAR:-}guards and explicit exit-code checks, which is acceptable but slightly more error-prone than an explicit strict mode. Not blocking.ComputeBatchTimeout's doc comment references WASM/Cryptography rationale ("~17 min", "WASM startup overhead") while now being used for desktop libraries batching; consider updating the comment so the 20-min/30-min-minimum rationale reflects the desktop path.- The
chmod +x DesktopBatchRunner.shin the batch runner entry point pluschmod +x RunTests.shinside the runner is reasonable for Helix payloads; worth a human confirming permission bits survive the zip round-trip on all queues.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 133.7 AIC · ⌖ 11 AIC · ⊞ 10K
|
|
||
| <GroupWorkItems Condition="'$(BatchLibraryHelixWorkItems)' == 'true' and '$(EnableDefaultBuildHelixWorkItems)' == 'true' and '@(_BatchCandidateWorkItems)' != ''" | ||
| Items="@(_BatchCandidateWorkItems)" | ||
| BatchSize="$(MaxTestsPerWorkItem)" |
There was a problem hiding this comment.
The property is named MaxTestsPerWorkItem (defaulting to 20), but it is passed as GroupWorkItems.BatchSize, which the task interprets as the number of bins per compatibility partition (numBatches = Math.Min(BatchSize, smallItems.Count)), not a per-work-item cap on the number of test suites. As a partition grows, each of the (up to) 20 bins receives count/20 suites, so a partition with, say, 400 compatible assemblies yields 20 work items of ~20 suites each, and a larger partition would produce work items with more than 20 suites. In other words this value bounds the number of work items, and there is no actual cap on tests-per-work-item. Consider renaming the property (e.g. MaxLibraryHelixBatches/LibraryHelixBatchCount) to match the semantics, or reworking the task to bin by a true per-item test/size cap if a hard per-work-item limit is intended. This also interacts with ComputeBatchTimeout, which computes timeout as count * 20 min per batch — an unexpectedly large count would inflate timeouts. Worth confirming the intended contract.
|
I think we're likely to take a different approach here. |
|
Closing — not proceeding with this approach (batching libraries test Helix work items by compatibility metadata). |
Summary
Reduces the number of libraries-test Helix work items by batching multiple test assemblies into a single work item, cutting per-work-item overhead (queueing, machine provisioning, payload/runtime download) while keeping total test execution time roughly flat.
Today each libraries test assembly is sent to Helix as its own work item, so the fixed per-work-item overhead is paid thousands of times. This change groups compatible desktop libraries work items together and runs them through a structured batch runner.
How it works
GroupWorkItemsMSBuild task (src/tasks/HelixTestTasks/GroupWorkItems.cs): greedy bin-packs assemblies into batches by file size. Items above a size threshold get their own solo batch. Assemblies are first partitioned by a compatibility-key tuple so only co-runnable assemblies share a work item:RunnerType;PlatformFamily;RuntimeFlavor;TargetOS;TargetArchitecture;Scenario;TimeoutClass;PayloadShapeDesktopBatchRunner.sh/.cmd(eng/testing/): inside a single work item, iterates the batched suites, gives each its own upload root so results don't collide, runs each suite's existingRunTestsscript, and aggregates per-suite exit codes/durations.sendtohelixhelp.proj: composes the desktop batch command from structured runner parts and emits batched work items. Non-batch-compatible (e.g. stressScenario) items are emitted directly so they keep their original runner and timeout instead of flowing throughComputeBatchTimeout.HelixTestTasks.Tests/GroupWorkItemsTests.cs.Batching can be disabled via
BatchLibraryHelixWorkItems=false.Validation
A manual pipeline run of this branch showed:
mainrun, with total test-execution time roughly flat.Opening as draft to get full CI coverage and review on the approach.
Note
This pull request description was drafted with the assistance of GitHub Copilot (AI-generated).