Attach retry artifacts to Azure DevOps sub-results - #10723
Conversation
Use server-assigned sub-result IDs for retry attachment uploads while preserving parent-level uploads for ordinary results. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
There was a problem hiding this comment.
Note
🤖 Automated review by GitHub Copilot. Generated by the Expert Code Review workflow. To request a follow-up action, reply by tagging @copilot directly.
Expert Review Summary — PR #10723
Attach retry artifacts to Azure DevOps sub-results
This PR correctly switches from renaming attachments per-attempt to uploading them against the AzDO sub-result that produced them, using server-assigned sub-result IDs. The design is clean: response parsing is extracted into a shared ParsePublishedResults helper, the WithFileName/RenameForAttempt workaround is removed, and failure to resolve sub-result IDs is handled gracefully (count attachments as failed, log warning, continue).
| # | Dimension | Verdict |
|---|---|---|
| 1 | Algorithmic Correctness | ✅ LGTM — validation logic in ParsePublishedResults handles mismatched counts, bad IDs, duplicate sequence IDs |
| 2 | Threading & Concurrency | ✅ LGTM — Interlocked.Add on _failedAttachmentCount is correct |
| 3 | Security & IPC Contract Safety | ✅ N/A |
| 4 | Public API & Binary Compatibility | ✅ LGTM — all types are internal, changes tracked in InternalAPI.Unshipped.txt |
| 5 | Performance & Allocations | ✅ LGTM — HttpCompletionOption.ResponseHeadersRead is a nice improvement; Dictionary<int,int> per result is proportional to sub-result count |
| 6 | Cross-TFM Compatibility | ✅ N/A — no TFM-specific APIs introduced |
| 7 | Resource & IDisposable Management | ✅ LGTM — CancellationTokenSource properly using-scoped in new code paths |
| 8 | Defensive Coding at Boundaries | ✅ LGTM — response parsing failures return null without retrying the accepted write; OperationCanceledException, JsonException, HttpRequestException, IOException all caught |
| 9 | Localization & Resources | ✅ LGTM — warning message uses existing resource string |
| 10 | Test Isolation | ✅ LGTM |
| 11 | Assertion Quality | ✅ LGTM — assertions verify the right things (sub-result IDs, URI query params, attachment names) |
| 12 | Flakiness Patterns | ✅ LGTM |
| 13 | Test Completeness & Coverage | ✅ LGTM — 6 new/updated tests covering create path, update path, missing sub-results, response read failure, attachment URI with/without sub-result |
| 14 | Data-Driven Test Coverage | ✅ N/A |
| 15 | Code Structure & Simplification | 📝 NIT — one inline comment posted (collapsible double _resultIdStore null check) |
| 16 | Naming & Conventions | ✅ LGTM |
| 17 | Documentation Accuracy | ✅ LGTM — obsolete <remarks> on WithFileName and UpdateTestResultsAsync properly removed |
| 18 | Analyzer & Code Fix Quality | ✅ N/A |
| 19 | IPC Wire Compatibility | ✅ N/A — this is HTTP REST, not IPC wire format |
| 20 | Build Infrastructure & Dependencies | ✅ N/A |
| 21 | Scope & PR Discipline | ✅ LGTM — single-concern PR, references #10558 |
| 22 | PowerShell Scripting Hygiene | ✅ N/A |
Overall: Clean PR. One NIT-level style suggestion posted inline. No blocking or major issues found.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 105.7 AIC · ⌖ 2.97 AIC · ⊞ 16.9K · ◷
There was a problem hiding this comment.
Pull request overview
Routes retry artifacts to their Azure DevOps sub-results using server-assigned IDs while preserving parent attachment behavior.
Changes:
- Captures sub-result IDs from create/update responses.
- Uploads retry attachments with
testSubResultId. - Adds API tracking and tests for attachment routing and response failures.
Show a summary per file
| File | Description |
|---|---|
AzureDevOpsLivePublishingTests.cs |
Tests sub-result attachment routing and response handling. |
InternalAPI.Unshipped.txt |
Tracks internal API changes. |
IAzureDevOpsTestResultsClient.cs |
Extends result and attachment contracts. |
AzureDevOpsTestResultsPublisher.Flush.cs |
Routes attachments to resolved sub-results. |
AzureDevOpsTestResultsClient.cs |
Parses IDs and builds attachment requests. |
AzureDevOpsResultIdStore.cs |
Exposes first-attempt construction. |
AzureDevOpsLivePublishingModels.cs |
Adds the published-result ID model. |
Review details
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 7/7 changed files
- Comments generated: 3
- Review effort level: Balanced
Map returned sub-results by sequence ID, keep legacy response reads cancellable, clarify fresh rerun payloads, and tighten test resource ownership. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Review details
Suppressed comments (1)
src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsTestResultsClient.cs:166
- The PATCH path has the same accepted-response gap: an invalid/unsupported response charset throws
InvalidOperationExceptionorArgumentExceptionoutside this filter.TryUpdateResultsAsyncthen forgets the accepted mapping and requeues the attempt, which can turn the next pass into a duplicate create. Returnnullfor these decoding failures as well.
catch (Exception ex) when (ex is JsonException or HttpRequestException or IOException)
- Files reviewed: 7/7 changed files
- Comments generated: 2
- Review effort level: Balanced
This comment has been minimized.
This comment has been minimized.
Treat response charset failures as accepted-write parse failures and dispose non-success responses on every exceptional path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
This comment has been minimized.
This comment has been minimized.
Replace manual array cleanup with using declarations so setup failures cannot leak mock responses. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
This comment has been minimized.
This comment has been minimized.
🧪 Expert test review — PR #10723No new or modified test methods were identified in the changed regions of this PR. Nothing to review. Re-run with
|
Cancel only after the response body read starts instead of relying on a wall-clock timer. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64fbc2fa-0f15-4789-b4c9-6c74e6b4c925
There was a problem hiding this comment.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsTestResultsClient.cs:296
- PATCH responses are still paired with submitted updates by array position. Each update already has a stable parent
Id, so if Azure DevOps returns the same results in a different order, this comparison rejects the otherwise valid response;TryUpdateResultsAsyncthen cannot resolve any sub-result IDs and drops the batch's retry attachments. Map PATCH response entries by parent ID instead (while retaining the existing create validation), and cover a batch whose parent results are returned in reverse order.
PublishedTestResult published = parsed.Value[i];
AzureDevOpsTestCaseResult submitted = submittedResults[i];
if (published.Id <= 0
|| (validateAutomatedTestName
? !string.Equals(published.AutomatedTestName, submitted.AutomatedTestName, StringComparison.Ordinal)
: submitted.Id != published.Id))
- Files reviewed: 7/7 changed files
- Comments generated: 0 new
- Review effort level: Balanced
🧵 Parallel-safety audit — PR #10723Parallelization — audited assembly for this PR:
Nothing audited here touches process-global state, shared filesystem paths, or What was reviewed: the PR only changes
No under-declared global-state mutations, no shared/relative-path collisions, no Advisory only — heuristic, non-blocking. Re-run with
|
Summary
Fixes #10558
Testing
Microsoft.Testing.Extensions.UnitTestsacross all configured target frameworks.AzureDevOpsLivePublishingTestsonnet8.0.