[fix] Fix CopyTraceDataCollectorArtifacts overwriting newer MTP CodeCoverage DLLs - #15794
[fix] Fix CopyTraceDataCollectorArtifacts overwriting newer MTP CodeCoverage DLLs#15794Jakub Jareš (nohwnd) wants to merge 8 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.
Jakub Jareš (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 🧠
This comment has been minimized.
This comment has been minimized.
… 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:
|
|
needs more careful analysis. |
This comment has been minimized.
This comment has been minimized.
…f MTP detection Replace the Microsoft.Testing.Extensions.CodeCoverage detection logic with Overwrite="false" on the Copy task. This is simpler and more robust: - Files already placed by MTP CodeCoverage (which runs before this target) are not overwritten with older versions from this package. - All TraceDataCollector-specific files are still copied for users without MTP. - No package detection logic needed (avoids Pkgxxx reliability concerns). The previous detection-based approach also had a gap: it skipped copying *all* files, including TraceDataCollector-specific ones that MTP doesn't provide, which could silently break /collect:"Code Coverage" for MSTest.Sdk users. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
This comment has been minimized.
This comment has been minimized.
Jakub Jareš (nohwnd)
left a comment
There was a problem hiding this comment.
🧠 Expert Review — PR #15794
The Overwrite="false" approach is a clean, robust fix. It works regardless of MSBuild target execution order between this package and Microsoft.Testing.Extensions.CodeCoverage (as long as MTP uses the default Overwrite=true), so the fix is more reliable than the previous PackageReference-detection approach.
No blocking issues found. One inline note on the code comment's accuracy and a minor observation about incremental publish behavior.
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
…' into nohwnd-repair-codecoverage-copy
Register legacy artifacts with ResolvedFileToPublish and exclude paths already supplied by the project publish. 🤖 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🧠 Expert Review — PR #15794
Activated dimensions (via src/package/ + eng/ routing): Dependency & Package Integrity, Build Script & Infrastructure Hygiene, Cross-TFM & Framework Resolution, Source Build & Cross-Platform Compliance
Core fix — Microsoft.CodeCoverage.targets ✅
The ResolvedFileToPublish-based deduplication is a correct and robust approach. Replacing the filesystem-level Copy with MSBuild item-level exclusion ensures:
- The exclusion operates before any files are written, eliminating the race between
AfterTargets="ComputeFilesToPublish"targets. MatchOnMetadataOptions="PathLike"correctly handles slash-direction differences (forward vs back) between the legacy package'sRelativePathand the paths set by MTP, making the comparison cross-platform safe.- Using
_TraceDataCollectorArtifacts(underscore prefix) avoids polluting the consumer's item namespace.
No issues found with the core fix.
[Description] — Minor inaccuracy
The PR description states: "The attempted Overwrite="false" fix was invalid because MSBuild's Copy task has no such parameter."
This is not correct — MSBuild's Copy task does have an [Overwrite parameter]((learn.microsoft.com/redacted) The reason the Overwrite="false" approach was abandoned (based on what the previous review flagged) was that the copy ordering between the legacy package and MTP was not guaranteed in the full dotnet publish pipeline. The description's stated rationale is misleading; the change is still the right direction, but the explanation should be corrected.
[Build Script & Infrastructure Hygiene] — Missing final newline
eng/AfterSolutionBuild.targets is missing the final newline (file ends with </Project> and no trailing \n). The file previously had one; it was dropped by the last edit. This is a minor SA1518/editorconfig hygiene issue.
Other dimensions: No issues found
- Cross-TFM: No TFM-specific behavior in the changed targets.
- Source Build & Cross-Platform Compliance: The
DotNetBuildskip gate added to_VerifyNuGetPackagesis correct — source-built assemblies ship different assembly versions and the binding-redirect check would false-positive. eng/PublishForPackaging.targets: Design is sound. See inline comment about one inaccurate anti-recursion explanation (documentation only; actual behavior is correct).
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
…ed files Explain why the target still exists, that the exclusion is keyed on RelativePath against ResolvedFileToPublish, and that it never inspects PackageReference, so a direct or transitive Microsoft.Testing.Extensions.CodeCoverage is handled the same way. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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
Core fix — CopyTraceDataCollectorArtifacts ✅
The ResolvedFileToPublish-based deduplication is correct. Replacing the direct Copy task with item-level exclusion ensures the project's own files (from Microsoft.Testing.Extensions.CodeCoverage) take precedence, regardless of whether that package arrives directly or transitively. MatchOnMetadataOptions="PathLike" correctly normalises slash direction for cross-platform correctness. The underscore-prefixed _TraceDataCollectorArtifacts item name avoids polluting the consumer's item namespace. No issues with the core fix.
Rebase required — MsCoverageReferencedPathMaps is behind main
The PR branch diverged from main before two changes were made to MsCoverageReferencedPathMaps:
and '$(DesignTimeBuild)' != 'true'in the target condition — prevents the target from running on every IDE design-time build, avoiding unnecessaryResolveProjectReferencescalls and IDE slowdowns.BuildInParallel="true"on the<MSBuild>task — enables parallel resolution for multi-project solutions.
Both are present in main; neither is in this branch's head. A squash-merge would drop them. See the inline comment for the exact location.
This PR needs a rebase onto current main before merging, so the only change in the resulting merge is the intended CopyTraceDataCollectorArtifacts fix.
Description alignment
The PR description is accurate for the CopyTraceDataCollectorArtifacts change. No issues.
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
Comments that could not be inline-anchored
src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.targets:51
[Build Script & Infrastructure Hygiene] Two features from main are absent from this target in the PR head
Current main has two additions to MsCoverageReferencedPathMaps that are not in this branch:
-
and '$(DesignTimeBuild)' != 'true'in the target condition — without this, the target runs on every IDE design-time build (IntelliSense, background analysis), which callsResolveProjectReferencesunnecessarily and can degrade IDE responsiveness. -
BuildInParallel="true"…
The pull request was diffing from a merge base of 2026-05-17, so the Files tab listed 654 files instead of the one file this change touches. Merging current main in moves the merge base forward so the diff is reviewable.
|
Merged current main in, which brings in the two The merge also fixed the Files tab. It was diffing from a merge base of 17 May and listing 654 files, now it shows the one file this change touches, 18 insertions and 5 deletions. I rechecked the publish behaviour after the merge, with two reproductions against the package layout, each run against the target before and after the change. With a newer 🤖 |
There was a problem hiding this comment.
🧠 Expert Review — PR #15794
Follow-up review (current head: 72078b98)
The previous REQUEST_CHANGES concern — that MsCoverageReferencedPathMaps was missing and '$(DesignTimeBuild)' != 'true' and BuildInParallel="true" from main — is fully resolved. Both are present in the current head and match main exactly.
The only remaining diff is the intended CopyTraceDataCollectorArtifacts fix, which was already reviewed and approved in prior rounds. The fix is correct:
_TraceDataCollectorArtifacts(underscore-prefixed) avoids polluting the consumer's item namespace.- The
RelativePathmetadata computation andMatchOnMetadataOptions="PathLike"correctly handle slash-direction differences on Windows vs. Linux. - Removing from
_TraceDataCollectorArtifactsbefore including inResolvedFileToPublishensures the project's own files (fromMicrosoft.Testing.Extensions.CodeCoverage) stay authoritative, whether that package is direct or transitive.
No blocking issues. This PR is ready for maintainer approval.
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
|
Worth recording how this actually shows up, because it depends on file timestamps and does not always reproduce. On main the target copies the package payload into the publish folder, and Checked against the package layout on SDK 10.0.302. With the package file given the later write time, main publishes the old file and this branch publishes the project's one. With no competing asset both publish the same 8 files, and The change removes the timestamp dependency, the package file is dropped from the item list when the project already supplies that relative path, so neither target order nor mtime decides it any more. 🤖 |
|
State of this PR, checked against head The The stale merge base is gone. One correction to the record. The 17 August review said the description was wrong to claim Youssef Fahmy (@Youssef1313) was correct in the earlier thread. Nothing in the description needs changing. The item logic does what it claims. I reproduced the target in a standalone project against a
So All checks pass on this head. 🤖 |
Dismissing: the concern in this review is resolved and its author has since retracted it.
The review asked for a rebase because MsCoverageReferencedPathMaps was missing '$(DesignTimeBuild)' != 'true' and BuildInParallel="true" from main. Both are present at the current head. git show origin/main:src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.targets and git show 72078b98:<same path> are identical in that target, and the three-dot compare against main is one file, +18/-5, touching only CopyTraceDataCollectorArtifacts.
The same reviewer confirmed this on 2026-08-20, but as a comment, which does not clear a changes-requested state.
🤖
|
I verified this against the actual failure in #15387 rather than by reading the diff. I published a project twice against the package layout, once with
So The mechanism is the incremental check on With no competing asset the published file set is identical between the two targets, six files either way, One correction to an earlier automated review on this thread, since it is still in the history: it said the description is wrong to claim I also dismissed the stale changes-requested review: it asked for a rebase because 🤖 |
|
Reproduced #15387 against the real packages and checked the published assembly version before and after this change. A 18.0.6.0 is the copy from On the mechanism, with the numbers from that run.
The assembly versions and the file timestamps disagree here, so the raw copy has to go rather than be reordered. It is also why build is fine and only publish breaks, the build output copy does not go through this path. 🤖 |
|
Thanks for the detailed reproduction — this confirms the Current status: PR is fully green (all 21 checks passing on the latest commit
|
Fixes microsoft/vstest#15387
CopyTraceDataCollectorArtifactscopied the package payload directly after publish item computation, which let an olderMicrosoft.CodeCoverage.Core.dllreplace the newer assembly supplied byMicrosoft.Testing.Extensions.CodeCoverage. The attemptedOverwrite="false"fix was invalid because MSBuild'sCopytask has no such parameter.The target now registers its artifacts with
ResolvedFileToPublishand first removes the ones whoseRelativePaththe project publish already supplies, so the SDK does the copying and the project's own files stay authoritative. It does not look atPackageReferenceat all, so a direct and a transitiveMicrosoft.Testing.Extensions.CodeCoveragebehave the same. Files that only this package carries, such asCodeCoverage\CodeCoverage.exe, are still published, which is what makes/collect:"Code Coverage"work withdotnet vsteston a machine without the NuGet cache.Verified with two publish reproductions against the package layout, each run against the target before and after the change. With a newer
Microsoft.CodeCoverage.Core.dllalready inResolvedFileToPublish, the published file comes from the package before the change and from the project after it. With no competing asset, the published file set is identical before and after, andCodeCoverage\CodeCoverage.exeis published in every case.🤖