Deduplicate ExecutionId/InstanceId envelope across DotnetTest IPC serializers#9820
Conversation
…ializers Introduce shared ReadExecutionScopedFields/WriteExecutionScopedFields helpers on BaseSerializer and refactor DiscoveredTestMessagesSerializer, TestResultMessagesSerializer, TestInProgressMessagesSerializer and FileArtifactMessagesSerializer to use them. The top-level ExecutionId/InstanceId (+ message list) serialize/deserialize scaffold was duplicated verbatim across all four; the wire format is unchanged. Fixes #9812 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Deduplicates execution-scoped envelope handling across four dotnet test IPC serializers while preserving wire compatibility.
Changes:
- Adds shared read/write envelope helpers.
- Refactors four serializers to use them.
- Updates tracked internal API declarations.
Show a summary per file
| File | Description |
|---|---|
BaseSerializer.cs |
Adds shared envelope helpers. |
DiscoveredTestMessagesSerializer.cs |
Uses shared envelope handling. |
TestResultMessagesSerializer.cs |
Uses shared envelope handling. |
TestInProgressMessagesSerializer.cs |
Uses shared envelope handling. |
FileArtifactMessagesSerializer.cs |
Uses shared envelope handling. |
Microsoft.Testing.Platform/InternalAPI/net/InternalAPI.Unshipped.txt |
Tracks new helper APIs. |
Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt |
Tracks new helper APIs. |
Microsoft.Testing.Extensions.TrxReport/InternalAPI/InternalAPI.Unshipped.txt |
Tracks shared-source APIs. |
Microsoft.Testing.Extensions.Retry/InternalAPI/InternalAPI.Unshipped.txt |
Tracks shared-source APIs. |
Microsoft.Testing.Extensions.MSBuild/InternalAPI.Unshipped.txt |
Tracks shared-source APIs. |
Microsoft.Testing.Extensions.HangDump/InternalAPI/InternalAPI.Unshipped.txt |
Tracks shared-source APIs. |
Review details
- Files reviewed: 11/11 changed files
- Comments generated: 1
- Review effort level: Medium
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.
Review Summary
Clean dedup refactor — the wire format is preserved, the helpers are stateless, and the field-id constants match all four serializers. No issues found.
| # | Dimension | Verdict |
|---|---|---|
| 1 | Correctness | ✅ Pass — field ids 1/2 match all four *FieldsId classes; read/write logic is equivalent to the inlined original. |
| 2 | Bug risk | ✅ Pass — no behavioral change. |
| 3 | Edge cases | ✅ Pass — null ExecutionId/InstanceId handled identically (conditional field count + WriteField no-ops on null). |
| 4 | Security | ✅ N/A — no auth, no user input parsing changes. |
| 5 | Performance | ✅ Pass — one extra delegate allocation per call, but serializers are not hot-path; acceptable. |
| 6 | Concurrency | ✅ Pass — helpers are stateless static; no shared mutable state. |
| 7 | Error handling | ✅ Pass — unchanged from original. |
| 8 | API design | ✅ Pass — protected static scope is appropriate; tuple return is idiomatic. |
| 9 | Naming | ✅ Pass — ReadExecutionScopedFields/WriteExecutionScopedFields clearly describe purpose. |
| 10 | Documentation | ✅ Pass — XML docs on both helpers; inline comment explains wire-id contract. |
| 11 | Style / formatting | ✅ Pass — consistent with codebase conventions. |
| 12 | DRY / duplication | ✅ Pass — this is the point of the PR; duplication eliminated. |
| 13 | Testability | ✅ Pass — existing serializer round-trip tests cover the refactored paths. |
| 14 | Backward compat | ✅ Pass — wire ids and layout unchanged; vendored-into-SDK contract preserved. |
| 15 | Public API surface | ✅ Pass — InternalAPI.Unshipped.txt updated in all 6 locations. |
| 16 | Localization | ✅ N/A |
| 17 | Logging / diagnostics | ✅ N/A |
| 18 | Disposal / resources | ✅ N/A |
| 19 | Configuration | ✅ N/A |
| 20 | Dependencies | ✅ Pass — no new dependencies. |
| 21 | TODOs | ✅ Pass — none introduced. |
| 22 | Test coverage | ✅ Pass — covered by existing IPC serializer unit tests. |
Overall: No issues. Straightforward and safe dedup refactor.
Minor observation (non-blocking): DisplayMessageSerializer and AzureDevOpsLogMessageSerializer share the same ExecutionId=1/InstanceId=2 envelope pattern but weren't included in this refactor. Could be a follow-up if desired.
…ssages Address PR review: AzureDevOpsLogMessage/DisplayMessage also place ExecutionId/InstanceId at ids 1/2 but carry scalar payloads and don't use these helpers, so narrow the wording to the four collection envelopes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address PR review: the callback-based ReadExecutionScopedFields/WriteExecutionScopedFields added a closure/delegate allocation per serialized/deserialized envelope on the per-test IPC path. Replace them with closure-free helpers: - TryReadExecutionScopedField(ref executionId, ref instanceId): a ref-based field matcher the caller invokes from its own single ReadFields lambda (no second closure; same one closure as before the dedup). - WriteExecutionScopedHeader(...): writes the field count + ExecutionId/InstanceId header, after which the caller writes its payload directly (no callback). Wire format unchanged; contract + protocol round-trip tests pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Fixes #9812.
Summary
Four
dotnet testIPC serializers —DiscoveredTestMessagesSerializer,TestResultMessagesSerializer,TestInProgressMessagesSerializer, andFileArtifactMessagesSerializer— shared an identical top-levelExecutionId/InstanceIdenvelope in theirDeserializeCore/SerializeCoremethods (the sameExecutionId/InstanceIdswitch cases + field-count/write scaffolding, ~18 lines per serializer). This PR extracts that shared scaffold.Changes
BaseSerializer(IPC/Serializers/BaseSerializer.cs):ReadExecutionScopedFields(...)— reads the leadingExecutionId(id 1) /InstanceId(id 2) string fields and delegates every other field id to a caller-supplied payload reader, returning the two envelope values.WriteExecutionScopedFields(...)— writes the field-count prefix, theExecutionId/InstanceIdfields, then invokes a payload writer.GetFieldCount(<Messages>)methods (the per-message-itemGetFieldCountoverloads are unchanged).InternalAPI.Unshipped.txtfor the platform and the extension projects that embed these shared-source serializers.The helpers are stateless (the serializers are registered as singletons), so no shared mutable state is introduced.
Wire compatibility
The field ids (
ExecutionId=1,InstanceId=2, message lists), their order and layout are unchanged — this is a pure code-dedup refactor. These files are also vendored intodotnet/sdk, so the round-trip contract is preserved.Verification
Microsoft.Testing.Platform.DotnetTestProtocolContract.UnitTests(standalone shared-source round-trip contract): 21/21 passed.Microsoft.Testing.Platform.UnitTestsProtocolTests+ProtocolEdgeCaseTests: 33/33 passed.Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com