You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Everything is OK—the changes have been pushed to branch efficiency/azuredevops-single-pass-min-max-7b36f35ab3d68d7c. Please review the changes, including any protected files, before creating the pull request.
AzureDevOpsResultIdStore.GetEarliestStartedDate/GetLatestCompletedDate (both the public IReadOnlyList<AzureDevOpsTestCaseResult> overload and the internal IReadOnlyList<AzureDevOpsTestSubResult> overload used for attempt history) used attempts.Where(a => a.X is not null).Min/Max(a => a.X). These two methods are always called together from the Azure DevOps live-publishing flush path (AzureDevOpsTestResultsPublisher.ResultPublishing.cs, AzureDevOpsTestResultsPublisher.AttemptPublishing.cs, AzureDevOpsResultIdStore.cs/.Persistence.cs) — i.e. once per batch of published test results/attempts, not a one-time cold path. Each call allocated a Where iterator and enumerated the list twice (once for Min, once for Max).
Focus area
Code-Level Efficiency (redundant enumeration / avoidable LINQ overhead in a per-batch-flush hot path).
Approach
Replaced both LINQ chains with manual single-pass foreach loops that track the running min/max directly — mirroring the existing hand-rolled SumDurations/AddDurations pattern already used right next to these methods in the same file. No behavioral change: null-date entries are still skipped, and an all-null list still returns null.
Energy efficiency evidence
Proxy metrics used: CPU time (Stopwatch) and allocated bytes (GC.GetAllocatedBytesForCurrentThread()) — both map directly to CPU/DRAM energy draw per Green Software Foundation guidance on hardware efficiency.
Standalone (not committed) console benchmark, .NET 8 Release, 2,000,000 call-pairs (GetEarliestStartedDate + GetLatestCompletedDate) over a 5-element list mimicking a typical attempt/sub-result batch:
Time
Allocated
OLD (Where().Min()/Max())
1073.37 ms
192,000,040 B
NEW (single-pass loop)
511.95 ms
128,000,040 B
~2.1x faster, ~33% less allocation for this call-pair shape. The remaining allocation in both versions comes from DateTimeOffset? boxing in the benchmark harness itself, not from the methods under test.
Green Software Foundation context
Hardware Efficiency: fewer CPU cycles per flush batch and less GC pressure from eliminated iterator allocations.
SCI: reduces the Energy term of the SCI equation for the "publish one batch of Azure DevOps test results" functional unit, aggregated across every CI run that uses --report-azdo.
Trade-offs
The loop form is a few lines longer than the one-line LINQ expression, but it directly mirrors the existing SumDurations helper's style already present in the same file, so it doesn't introduce a new pattern to the codebase.
Reproducibility
Benchmark harness (not committed) computed old vs. new implementations side-by-side with warmup iterations before each measured loop; happy to reproduce on request.
dotnet format whitespace --verify-no-changes on both changed files: clean.
No public API signature changes — both public methods keep their exact signatures, only the internal implementation changed.
Note
GitHub Actions is not permitted to create or approve pull requests in this repository.
The changes have been pushed to branch efficiency/azuredevops-single-pass-min-max-7b36f35ab3d68d7c and are ready to review.
To fix the permissions issue, go to Settings → Actions → General and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ
Show patch preview (30 of 103 lines)
From d0a24adf94ca306775e1977d3f4b938017c5b42c Mon Sep 17 00:00:00 2001
X-GH-AW-Base-Commit: 00b4e2a445a42b8768fdd706e7de778e582fb034
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Fri, 18 Sep 2026 21:53:47 +0000
Subject: [PATCH] Single-pass min/max for Azure DevOps result date aggregation
Replace Where().Min()/Where().Max() LINQ chains with manual single-pass
loops in AzureDevOpsResultIdStore's GetEarliestStartedDate/GetLatestCompletedDate
(both the public IReadOnlyList<AzureDevOpsTestCaseResult> overload and the
internal IReadOnlyList<AzureDevOpsTestSubResult> overload). These are called
in pairs from the result-flush hot path (once per batch of published test
results/attempts), so the double enumeration + Where iterator allocation was
avoided in favor of the same manual-accumulation pattern already used by the
adjacent SumDurations helper.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
...AzureDevOpsResultIdStore.AttemptHistory.cs | 26 +++++++++++++++++--
.../AzureDevOpsResultIdStore.cs | 26 +++++++++++++++++--
2 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsResultIdStore.AttemptHistory.cs b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsResultIdStore.AttemptHistory.cs
index 3ae5eda..a03f7e7 100644
--- a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsResultIdStore.AttemptHistory.cs+++ b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsResultIdStore.AttemptHistory.cs@@ -59,10 +59,32 @@ or AzureDevOpsLivePublishingConstants.NotExecutedTestOutcome
: right.Value > long.MaxValue - left.Value ? long.MaxValue : left.Value + right.Value;
private static DateTimeOffset? GetEarliestStartedDate(IReadOnlyList<AzureDevOpsTestSubResult> attempts)
- => attempts.Where(attempt => attempt.StartedDate is not null).Min(a
... (truncated)
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
southcentralus0.in.applicationinsights.azure.com
To allow these domains, add them to the network.allowed list in your workflow frontmatter:
Tip
Your pull request is ready to create! 🎉 ✅
Everything is OK—the changes have been pushed to branch
efficiency/azuredevops-single-pass-min-max-7b36f35ab3d68d7c. Please review the changes, including any protected files, before creating the pull request.Create the pull request
The original pull request description is below.
Goal and rationale
AzureDevOpsResultIdStore.GetEarliestStartedDate/GetLatestCompletedDate(both the publicIReadOnlyList<AzureDevOpsTestCaseResult>overload and the internalIReadOnlyList<AzureDevOpsTestSubResult>overload used for attempt history) usedattempts.Where(a => a.X is not null).Min/Max(a => a.X). These two methods are always called together from the Azure DevOps live-publishing flush path (AzureDevOpsTestResultsPublisher.ResultPublishing.cs,AzureDevOpsTestResultsPublisher.AttemptPublishing.cs,AzureDevOpsResultIdStore.cs/.Persistence.cs) — i.e. once per batch of published test results/attempts, not a one-time cold path. Each call allocated aWhereiterator and enumerated the list twice (once forMin, once forMax).Focus area
Code-Level Efficiency (redundant enumeration / avoidable LINQ overhead in a per-batch-flush hot path).
Approach
Replaced both LINQ chains with manual single-pass
foreachloops that track the running min/max directly — mirroring the existing hand-rolledSumDurations/AddDurationspattern already used right next to these methods in the same file. No behavioral change: null-date entries are still skipped, and an all-null list still returnsnull.Energy efficiency evidence
Proxy metrics used: CPU time (
Stopwatch) and allocated bytes (GC.GetAllocatedBytesForCurrentThread()) — both map directly to CPU/DRAM energy draw per Green Software Foundation guidance on hardware efficiency.Standalone (not committed) console benchmark, .NET 8 Release, 2,000,000 call-pairs (
GetEarliestStartedDate+GetLatestCompletedDate) over a 5-element list mimicking a typical attempt/sub-result batch:Where().Min()/Max())~2.1x faster, ~33% less allocation for this call-pair shape. The remaining allocation in both versions comes from
DateTimeOffset?boxing in the benchmark harness itself, not from the methods under test.Green Software Foundation context
--report-azdo.Trade-offs
The loop form is a few lines longer than the one-line LINQ expression, but it directly mirrors the existing
SumDurationshelper's style already present in the same file, so it doesn't introduce a new pattern to the codebase.Reproducibility
Benchmark harness (not committed) computed old vs. new implementations side-by-side with warmup iterations before each measured loop; happy to reproduce on request.
Test Status
./build.sh(full repo): 0 warnings/errors.dotnet build test/UnitTests/Microsoft.Testing.Extensions.UnitTests -f net8.0: 0 warnings/errors.Microsoft.Testing.Extensions.UnitTestssuite (net8.0, run directly): 1839/1839 passed (37 pre-existing skips).AzureDevOpsLivePublishingTests-filtered run: 154/154 passed.dotnet format whitespace --verify-no-changeson both changed files: clean.No public API signature changes — both public methods keep their exact signatures, only the internal implementation changed.
Note
GitHub Actions is not permitted to create or approve pull requests in this repository.
To fix the permissions issue, go to Settings → Actions → General and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ
Show patch preview (30 of 103 lines)
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
southcentralus0.in.applicationinsights.azure.comTo allow these domains, add them to the
network.allowedlist in your workflow frontmatter:See Network Configuration for more information.
Add this agentic workflow to your repo
To install this agentic workflow, run