[fix] Fix CopyTraceDataCollectorArtifacts overwriting newer MTP CodeCoverage DLLs#15794
[fix] Fix CopyTraceDataCollectorArtifacts overwriting newer MTP CodeCoverage DLLs#15794nohwnd wants to merge 2 commits into
Conversation
…e DLLs When Microsoft.Testing.Extensions.CodeCoverage is referenced alongside Microsoft.NET.Test.Sdk, the CopyTraceDataCollectorArtifacts target blindly copies older Microsoft.CodeCoverage artifacts into the publish directory, overwriting the newer DLLs from the MTP package. This causes a FileNotFoundException at runtime because the newer version is expected but the older one is present. Fix: Skip the blind copy when Microsoft.Testing.Extensions.CodeCoverage is directly referenced, as that package already provides the correct version of all its artifacts. Fixes #15387 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the Microsoft.CodeCoverage.targets packaging logic to avoid overwriting newer code coverage binaries during dotnet publish when newer coverage tooling is present, addressing the publish-time assembly version conflict described in #15387.
Changes:
- Adds MSBuild logic to detect
Microsoft.Testing.Extensions.CodeCoverageand conditionally skipCopyTraceDataCollectorArtifacts. - Updates the target’s inline documentation to explain why the copy is skipped in that scenario.
| <!-- Detect whether Microsoft.Testing.Extensions.CodeCoverage is directly referenced. --> | ||
| <ItemGroup> | ||
| <_MtpCodeCoverageRef Include="@(PackageReference)" Condition="'%(PackageReference.Identity)' == 'Microsoft.Testing.Extensions.CodeCoverage'" /> |
|
|
||
| <!-- This target required to enable /collect:"Code Coverage" in "dotnet publish" scenario with "dotnet vstest". | ||
| E.g: Release pipelines where user/project nuget cache not available on current machine. --> | ||
| E.g: Release pipelines where user/project nuget cache not available on current machine. |
nohwnd
left a comment
There was a problem hiding this comment.
🧠 Expert Review — PR #15794
Activated dimensions (via src/package/ routing): Dependency & Package Integrity, Build Script & Infrastructure Hygiene, Cross-TFM & Framework Resolution
Critical: Detection logic may miss transitive references
The core fix uses @(PackageReference) to detect whether Microsoft.Testing.Extensions.CodeCoverage is referenced. This only catches direct references in the project file. However, the PR's own motivation describes the scenario where the package arrives transitively via MSTest.Sdk 4.0.2 — which would not be visible in @(PackageReference).
In that scenario, _MtpCodeCoverageRef will be empty, the condition evaluates to ''== '' (true = copy proceeds), and the newer DLLs are still overwritten. The fix would have no effect for the described root cause.
See the inline comment for a concrete suggestion using $(Pkgmicrosoft_Testing_Extensions_CodeCoverage) which NuGet sets for all resolved packages including transitive ones.
Other dimensions: No issues found
- Build Script & Infrastructure Hygiene: MSBuild syntax is valid; target ordering (
AfterTargets="ComputeFilesToPublish") is appropriate. - Cross-TFM:
.targetsfile is TFM-agnostic; no concerns. - Description alignment: The PR title and description accurately describe the intent. The description is well-written and matches the diff.
🧠 Reviewed by Expert Code Reviewer
🧠 Reviewed by Expert Code Reviewer 🧠
|
|
||
| <!-- Detect whether Microsoft.Testing.Extensions.CodeCoverage is directly referenced. --> | ||
| <ItemGroup> | ||
| <_MtpCodeCoverageRef Include="@(PackageReference)" Condition="'%(PackageReference.Identity)' == 'Microsoft.Testing.Extensions.CodeCoverage'" /> |
There was a problem hiding this comment.
[Dependency & Package Integrity] Transitive references not detected — fix may not work in the described scenario
@(PackageReference) only contains directly declared package references from the project file. Transitive dependencies (packages brought in by other packages) do not appear here.
The PR description motivates the fix with this exact scenario:
When
Microsoft.Testing.Extensions.CodeCoverage18.1.0 is also referenced (e.g. viaMSTest.Sdk 4.0.2)...
If a user has MSTest.Sdk as their SDK (or has it as a PackageReference) and Microsoft.Testing.Extensions.CodeCoverage arrives transitively, this detection will see an empty _MtpCodeCoverageRef and still perform the overwrite — exactly the bug being fixed.
A more robust approach would be to check @(ResolvedPackageReference) (populated by RunResolvePackageDependencies) or use the NuGet-generated package path property:
<!-- Available after NuGet resolves packages; includes transitive references -->
<_MtpCodeCoverageRef Include="@(PackageReference)" Condition="'%(Identity)' == 'Microsoft.Testing.Extensions.CodeCoverage'" />
<!-- Also check via the NuGet package property (set for direct and some transitive scenarios) -->
<PropertyGroup>
<_MtpCodeCoveragePresent Condition="'$(Pkgmicrosoft_Testing_Extensions_CodeCoverage)' != ''">true</_MtpCodeCoveragePresent>
</PropertyGroup>Or, alternatively, check whether a file from the newer package is already present in the publish output (file-based version comparison), though that is more complex.
At minimum, the condition should also cover '$(Pkgmicrosoft_Testing_Extensions_CodeCoverage)' != '', as NuGet sets this property for all resolved packages (direct and transitive).
There was a problem hiding this comment.
Good catch. This was addressed in the follow-up commit f0d0102 — the condition now also checks $(Pkgmicrosoft_Testing_Extensions_CodeCoverage), which NuGet sets for all resolved packages including transitive ones:
<_MtpCodeCoveragePresent Condition="'@(_MtpCodeCoverageRef)' != '' or '$(Pkgmicrosoft_Testing_Extensions_CodeCoverage)' != ''">true</_MtpCodeCoveragePresent>This covers the MSTest.Sdk transitive scenario described in the PR.
🔧 Iterated by PR Iteration Agent 🔧
🔧 Iterated by PR Iteration Agent 🔧
|
PR registered for tracking by the PR Iteration Agent. The fix looks correct — the This PR was created as a draft. Please undraft it to trigger CI.
|
… reference Use $(Pkgmicrosoft_Testing_Extensions_CodeCoverage) in addition to @(PackageReference) to detect when Microsoft.Testing.Extensions.CodeCoverage is present transitively (e.g. via MSTest.Sdk). NuGet sets the Pkg* property for all resolved packages, including transitive ones. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
| </ItemGroup> | ||
|
|
||
| <PropertyGroup> | ||
| <_MtpCodeCoveragePresent Condition="'@(_MtpCodeCoverageRef)' != '' or '$(Pkgmicrosoft_Testing_Extensions_CodeCoverage)' != ''">true</_MtpCodeCoveragePresent> |
There was a problem hiding this comment.
It sounds weird to have property conditioned on an item.
Also sounds weird to have two ways for checking the package reference.
I'm also not sure if the overall fix is even correct. This won't handle if CC is brought transitively.
| </ItemGroup> | ||
|
|
||
| <Copy SourceFiles="@(TraceDataCollectorArtifacts)" DestinationFolder="$(PublishDir)%(RecursiveDir)" /> | ||
| <Copy SourceFiles="@(TraceDataCollectorArtifacts)" DestinationFolder="$(PublishDir)%(RecursiveDir)" |
There was a problem hiding this comment.
I would say this copy invocation should skip any existing files in destination (regardless of whether or not they are different).
I'm also not even sure why the whole target is needed. Maybe if you track the original problem it tries to solve, you can find a whole better solution.
Fixes #15387
Root Cause
In
Microsoft.CodeCoverage.targets, theCopyTraceDataCollectorArtifactstarget blindly copies the entireMicrosoft.CodeCoveragepackage payload into the publish directory afterComputeFilesToPublish. WhenMicrosoft.Testing.Extensions.CodeCoverage18.1.0 is also referenced (e.g. viaMSTest.Sdk 4.0.2), it placesMicrosoft.CodeCoverage.Corev18.1.0.0 into publish output, but this target then overwrites it with the older v18.0.6.0 fromMicrosoft.CodeCoverage18.0.1.At runtime,
Microsoft.Testing.Extensions.CodeCoverageexpects v18.1.0.0 but finds v18.0.6.0, causing:Fix
Skip the
CopyTraceDataCollectorArtifactscopy whenMicrosoft.Testing.Extensions.CodeCoverageis directly referenced. That package already provides the correct version of all its artifacts, so the blind copy is both unnecessary and harmful in that scenario.Change
src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.targets— detectMicrosoft.Testing.Extensions.CodeCoverageviaPackageReferenceitems and skip the copy when present.