Skip to content

Forward Azure DevOps logging commands over the dotnet test pipe (multi-assembly)#9463

Merged
Evangelink merged 3 commits into
mainfrom
dev/amauryleve/azdo-log-forwarding-dotnet-test-pipe
Jun 29, 2026
Merged

Forward Azure DevOps logging commands over the dotnet test pipe (multi-assembly)#9463
Evangelink merged 3 commits into
mainfrom
dev/amauryleve/azdo-log-forwarding-dotnet-test-pipe

Conversation

@Evangelink

Copy link
Copy Markdown
Member

What

Makes the Microsoft.Testing.Extensions.AzureDevOpsReport extension's logging commands (##[group], ##[endgroup], ##vso[...]) work in multi-assembly dotnet test runs, where they were previously swallowed.

Why

Under the dotnet test pipe protocol (--server dotnettestcli), the host installs a no-op output device (OutputDeviceManager.BuildAsync) because the SDK's TerminalTestReporter owns all user-facing output (see #7161 / dotnet/sdk#51615). Every AzDO reporter emits through IOutputDevice, so in multi-assembly runs all of its output — log groups, ##vso[task.logissue] failure annotations, slow-test lines, artifact-upload/build-tag commands, summary commands — was dropped on the floor. Single-assembly runs (real terminal) were unaffected.

How

Selective passthrough over the pipe protocol, gated so old SDKs are never affected:

  • Protocol 1.2.0 — new AzureDevOpsLogMessage (serializer id 11, fields ExecutionId/InstanceId/LogText) added to the dotnet-test wire contract; ProtocolConstants.SupportedVersions bumped to 1.0.0;1.1.0;1.2.0. ObjectFieldIds.cs/Constants.cs are the source-shared contract (kept aligned with dotnet/sdk).
  • Version gatingDotnetTestConnection.IsLogForwardingSupported is true only when the negotiated version is ≥ 1.2.0, so the host never sends id 11 to a 1.0.0/1.1.0 SDK.
  • Marker type — new internal AzureDevOpsCommandOutputDeviceData : TextOutputDeviceData. Because it derives from TextOutputDeviceData (not FormattedTextOutputDeviceData), single-assembly rendering routes through the existing case TextOutputDeviceData and is byte-for-byte unchanged.
  • DotnetTestPassthroughOutputDevice — under the pipe protocol on an Azure DevOps agent, this replaces the no-op device: it forwards only marked lines via the protocol and swallows everything else (preserving the deliberate suppression). It gates on TF_BUILD only (new AzureDevOpsLogIssueFormatter.IsAzureDevOpsAgent), not the TESTINGPLATFORM_AZDO_OUTPUT opt-out — that opt-out is scoped to the platform's automatic ##vso[task.logissue] emission, and single-assembly extension output is gated on TF_BUILD alone, so multi-assembly must match.
  • The 5 AzDO emitters now emit the marker.

Zero new public API (all new types are internal; the extension already has InternalsVisibleTo).

Tests

  • Unit: AzureDevOpsLogMessage serializer round-trip (incl. null fields); DotnetTestPassthroughOutputDevice swallow behavior; IsAzureDevOpsAgent opt-out independence; the SerializerIds_AreStable / ProtocolVersion_IsStable wire tripwires (and their mirror in the standalone contract test project) updated for id 11 and 1.2.0.
  • Acceptance (DotnetTestPipe/DotnetTestPipeAzureDevOpsForwardingTests): black-box over the wire via the fake-SDK harness — forwards ##[group]/##[endgroup] (with matching InstanceId) at 1.2.0 + AzDO; does not forward at 1.1.0; does not forward off an AzDO agent. Existing pipe baseline tests updated for the version bump.

All green locally: 9/9 DotnetTestPipe acceptance, platform unit + contract tripwires, 158 AzDO extension unit tests.

Out of scope / follow-ups

  • SDK consumption (the rendering half). The dotnet test SDK must add the aligned id-11 deserializer + reply VoidResponse, then write LogText to its TerminalTestReporter — and that must land before any SDK advertises 1.2.0. This PR is safe in the meantime (forwarding is version-gated).
  • Parallel group interleaving is an SDK-side rendering concern; the forwarded InstanceId lets the SDK scope ##[group]/##[endgroup] per assembly.
  • Non-command WarningMessageOutputDeviceData emits remain swallowed in multi-asm (unchanged behavior; not targeted here).

…i-assembly)

Under the dotnet test pipe protocol the host installs a no-op output device (the SDK's TerminalTestReporter owns user-facing output), so the AzureDevOpsReport extension's logging commands (##[group], ##vso[...]) were swallowed in multi-assembly runs. This adds protocol version 1.2.0 with a new AzureDevOpsLogMessage (serializer id 11): on an Azure DevOps agent with a negotiated version >= 1.2.0, those marked lines are forwarded to the SDK instead of dropped, while all other host output stays suppressed. Single-assembly behavior is unchanged. The SDK-side consumption (deserialize -> TerminalTestReporter.WriteMessage) is a separate follow-up.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 26, 2026 16:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Microsoft.Testing.Platform’s dotnet test pipe protocol to selectively forward Azure DevOps logging commands (e.g., ##[group], ##[endgroup], ##vso[...]) during multi-assembly dotnet test runs, where the host currently installs a no-op output device and AzDO extension output is otherwise swallowed.

Changes:

  • Introduces protocol 1.2.0 and a new wire message AzureDevOpsLogMessage (serializer id 11) with version-gated sending (IsLogForwardingSupported).
  • Adds DotnetTestPassthroughOutputDevice + AzureDevOpsCommandOutputDeviceData marker so only explicitly-marked AzDO command lines are forwarded under the pipe protocol (and only on AzDO agents).
  • Updates unit + acceptance tests and contract “tripwire” tests to lock down the new serializer id and protocol-version stability.
Show a summary per file
File Description
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/DotnetTestPassthroughOutputDeviceTests.cs Adds unit coverage for swallow/forward gating behavior in the passthrough device.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/AzureDevOpsLogIssueFormatterTests.cs Adds tests to ensure TF_BUILD gating is independent from the platform opt-out.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/IPC/ProtocolTests.cs Adds AzureDevOpsLogMessage serializer round-trip tests; updates protocol/version stability assertions.
test/UnitTests/Microsoft.Testing.Platform.DotnetTestProtocolContract.UnitTests/DotnetTestProtocolContractTests.cs Mirrors serializer-id and protocol-version stability checks in the shared contract test project.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/AzureDevOpsSummaryReporterTests.cs Updates expectations to treat AzDO command output as TextOutputDeviceData (marker-compatible).
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/AzureDevOpsLogGroupReporterTests.cs Same as above for group open/close output.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/AzureDevOpsArtifactUploaderTests.cs Same as above for artifact/tag command emission.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DotnetTestPipe/DotnetTestPipeProtocol.cs Extends the fake-SDK protocol decoder with serializer id 11 and message-body decoding.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DotnetTestPipe/DotnetTestPipeBaselineTests.cs Updates baseline expectations for host-advertised protocol versions (now includes 1.2.0).
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DotnetTestPipe/DotnetTestPipeAzureDevOpsForwardingTests.cs New acceptance tests validating on-wire forwarding behavior under version + AzDO gating.
src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/AzureDevOpsLogMessageSerializer.cs Adds serializer implementation for the new AzureDevOpsLogMessage contract.
src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/ObjectFieldIds.cs Adds AzureDevOpsLogMessageFieldsId with serializer id 11 and field ids.
src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Models/AzureDevOpsLogMessage.cs Adds the new IPC model representing forwarded AzDO log command lines.
src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Constants.cs Bumps supported protocol versions to 1.0.0;1.1.0;1.2.0 and documents the forwarding behavior.
src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/DotnetTestConnection.cs Adds IsLogForwardingSupported and sends AzureDevOpsLogMessage frames when appropriate.
src/Platform/Microsoft.Testing.Platform/OutputDevice/OutputDeviceManager.cs Installs DotnetTestPassthroughOutputDevice under pipe protocol on AzDO agents; keeps no-op otherwise.
src/Platform/Microsoft.Testing.Platform/OutputDevice/DotnetTestPassthroughOutputDevice.cs Implements the selective forwarding output device (marker-based forwarding + version gate).
src/Platform/Microsoft.Testing.Platform/OutputDevice/AzureDevOpsLogIssueFormatter.cs Introduces IsAzureDevOpsAgent (TF_BUILD-only) and refactors IsAzureDevOpsEnvironment to use it.
src/Platform/Microsoft.Testing.Platform/OutputDevice/AzureDevOpsCommandOutputDeviceData.cs Adds a marker output type used to identify AzDO command lines for forwarding.
src/Platform/Microsoft.Testing.Platform/IPC/Serializers/RegisterSerializers.cs Registers the new serializer so it participates in the named-pipe protocol.
src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsSummaryReporter.cs Switches command emission to the marker data type so it can be forwarded in multi-assembly pipe runs.
src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsSlowTestReporter.cs Same marker switch for slow-test logging lines.
src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsReporter.cs Same marker switch for log-issue/build-tag commands emitted by the reporter.
src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsLogGroupReporter.cs Same marker switch for ##[group]/##[endgroup] emission.
src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsArtifactUploader.cs Same marker switch for artifact-upload/build-tag commands.

Review details

Comments suppressed due to low confidence (1)

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DotnetTestPipe/DotnetTestPipeBaselineTests.cs:36

  • The test name still references advertising protocol versions 1.0.0 and 1.1.0, but this file now asserts the host advertises up to 1.2.0. Renaming the test to match the current contract would avoid confusion when reading failures or test output.
    [TestMethod]
    public async Task DotnetTestPipe_TestAppAdvertises100And110_NegotiatesDownToV100WithOldSdk()
    {
  • Files reviewed: 25/25 changed files
  • Comments generated: 0
  • Review effort level: Low

- DotnetTestPipe_WhenNotRunningInAzureDevOps test set TF_BUILD=false explicitly: CI runs on an Azure DevOps agent (TF_BUILD=true inherited), which made the 'not on an agent' scenario impossible and the forwarding assertion fail.
- DotnetTestConnection.SendMessageAsync: guard the pipe client with a runtime null check instead of a debug-only assert (CodeQL).
- DotnetTestConnection.IsCompatibleProtocolAsync: simplify the nullable-bool check to 'is true' (CodeQL).
- Rename baseline test to reflect that the host now advertises 1.2.0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink
Evangelink marked this pull request as ready for review June 29, 2026 13:49
Copilot AI review requested due to automatic review settings June 29, 2026 13:49
@Evangelink
Evangelink enabled auto-merge (squash) June 29, 2026 13:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 4
  • Review effort level: Low

@Evangelink

This comment has been minimized.

- AzureDevOpsCommandOutputDeviceData: the doc claimed it only carries Azure DevOps logging commands, but it also carries other AzureDevOpsReport output (e.g. the slow-test progress lines). Clarified the summary/remarks.
- Renamed the AzDO unit-test helper GetFormattedLines -> GetCommandLines and made it filter precisely on AzureDevOpsCommandOutputDeviceData (instead of any TextOutputDeviceData), so the name matches what it returns.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink

Copy link
Copy Markdown
Member Author

🧪 Test quality grade — PR #9463

36 test methods graded (14 new, 22 modified across 9 files). All 36 earn A (90–100). The new acceptance and unit tests are well-structured, use meaningful assertions, and are free of anti-patterns. One low-severity observation: the existing DotnetTestPipe_TestAppNegotiatesV110WithUpToDateSdk method name is slightly stale now that 1.2.0 is the highest supported protocol.

ΔTestGradeBandNotes
mod AzureDevOpsArtifactUploaderTests.
AllMode_
EmitsBuildTagsAndArtifactLines
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
ArtifactUploadPaths_
AreEscaped
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
ConsumedArtifacts_
DetectCrashAndHangDumps
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
EmptyTestResultsDirectory_
DoesNotEmitArtifactLines
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
FilesMode_
EmitsOnlyArtifactLines
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
FilesMode_
SkipsArtifactsOutsideResultsDirectory
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
HasTestFailuresTag_
IsNotEmittedWithoutFailures
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
IncludeAndExcludeGlobs_
AreAppliedToArtifactUploads
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
MissingTfBuild_
EmitsWarningAndSkipsOutput
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
NonDumpProducer_
DoesNotEmitDumpTags
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
OffMode_
IsDisabledAndNoOps
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
TagsOnlyMode_
EmitsOnlyBuildTags
A90–100 No issues found.
mod AzureDevOpsArtifactUploaderTests.
WhitespaceTestResultsDirectory_
DoesNotEmitArtifactLines
A90–100 No issues found.
mod AzureDevOpsLogGroupReporterTests.
ConsumeAsync_
IsNoOp_
AndEmitsNothingAsync
A90–100 No issues found.
mod AzureDevOpsLogGroupReporterTests.
SessionFinishing_
DoesNothing_
WhenGroupWasNeverOpenedAsync
A90–100 No issues found.
mod AzureDevOpsLogGroupReporterTests.
SessionFinishing_
EmitsEndGroup_
WhenGroupWasOpenedAsync
A90–100 No issues found.
mod AzureDevOpsLogGroupReporterTests.
SessionStarting_
EmitsGroupHeaderWithAssemblyAndTfmAsync
A90–100 No issues found.
new AzureDevOpsLogIssueFormatterTests.
IsAzureDevOpsAgent_
IgnoresOptOutAndReturnsTrueWhenTfBuildIsTrue
A90–100 Data-driven (3 opt-out values); correctly distinguishes IsAzureDevOpsAgent from IsAzureDevOpsEnvironment.
new AzureDevOpsLogIssueFormatterTests.
IsAzureDevOpsAgent_
ReturnsFalseWhenTfBuildIsAbsent
A90–100 No issues found.
new AzureDevOpsLogIssueFormatterTests.
IsAzureDevOpsAgent_
ReturnsFalseWhenTfBuildIsFalse
A90–100 No issues found.
mod AzureDevOpsSummaryReporterTests.
SessionFinishing_
DoesNothingAndEmitsWarning_
WhenTfBuildNotSetAsync
A90–100 No issues found.
mod AzureDevOpsSummaryReporterTests.
SessionFinishing_
WritesSummaryFileAndEmitsUploadSummaryCommandAsync
A90–100 Comprehensive: file content and the emitted ##vso command are independently asserted.
new DotnetTestPassthroughOutputDeviceTests.
DisplayAsync_
WithMarkerButNoConnection_
IsSwallowedWithoutThrowing
A90–100 No issues found.
new DotnetTestPassthroughOutputDeviceTests.
DisplayAsync_
WithNonMarkerData_
IsSwallowedWithoutResolvingTheConnection
A90–100 Strict-mock pattern precisely verifies the early-exit isolation invariant.
new DotnetTestPassthroughOutputDeviceTests.
IsEnabledAsync_
ReturnsFalse_
LikeNopDevice
A90–100 No issues found.
new DotnetTestPipeAzureDevOpsForwardingTests.
DotnetTestPipe_
WhenNotRunningInAzureDevOps_
DoesNotForwardLogMessages
A90–100 No issues found.
new DotnetTestPipeAzureDevOpsForwardingTests.
DotnetTestPipe_
WhenSdkOnlySupports110_
DoesNotForwardLogMessages
A90–100 No issues found.
new DotnetTestPipeAzureDevOpsForwardingTests.
DotnetTestPipe_
WhenSdkSupports120AndRunningInAzureDevOps_
ForwardsLogGroupCommands
A90–100 Rich: version negotiation, forwarded content, and InstanceId correlation all verified.
mod DotnetTestPipeBaselineTests.
DotnetTestPipe_
TestAppAdvertisesAllSupportedVersions_
NegotiatesDownToV100WithOldSdk
A90–100 No issues found.
mod DotnetTestPipeBaselineTests.
DotnetTestPipe_
TestAppNegotiatesV110WithUpToDateSdk
A90–100 Low: "UpToDateSdk" suffix is stale now that 1.2.0 is the highest supported protocol.
mod DotnetTestProtocolContractTests.
ProtocolVersion_
IsStable
A90–100 No issues found.
mod DotnetTestProtocolContractTests.
SerializerIds_
AreStable
A90–100 No issues found.
new ProtocolTests.
AzureDevOpsLogMessageSerializeDeserialize
A90–100 No issues found.
new ProtocolTests.
AzureDevOpsLogMessageSerializeDeserialize_
WithNullOptionalFields
A90–100 Good complement; explicitly pins round-trip behavior for all-null optional fields.
mod ProtocolTests.
ProtocolVersion_
IsStable
A90–100 No issues found.
mod ProtocolTests.
SerializerIds_
AreStable
A90–100 Mildly long (~35 lines) but appropriate for a contract-stability pinning test.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Grade Tests on PR (on open / sync) workflow. · 515.4 AIC · ⌖ 14.9 AIC · ⊞ 43.8K · [◷]( · )

@Evangelink Evangelink added the state/needs-review Awaiting review from the team. label Jun 29, 2026
@Evangelink
Evangelink merged commit 86d6648 into main Jun 29, 2026
60 checks passed
@Evangelink
Evangelink deleted the dev/amauryleve/azdo-log-forwarding-dotnet-test-pipe branch June 29, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-review Awaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants