Skip to content

[BUG] Shared-framework assemblies are unresolvable during instrumentation because they are absent from deps.json #2026

Description

@stevep001

Describe the bug

We were upgrading our project from .net 8 to .net 10 and ran into the problem described in https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/KnownIssues.md#failed-to-resolve-assembly-during-instrumentation.

In our case the issue was: An optional parameter whose default value is a constant defined in a shared-framework assembly makes coverlet drop the entire containing module from coverage. Tests still pass and a coverage file is still written — it just silently omits the assembly. We noticed this when our pr gate, which validates coverage levels, failed after the upgrade.

I think this conclusion is no longer correct, and rather than spamming you with a patch, would like your approval of this issue first.

Both suggestions in the exception message and in KnownIssues.md were tested on SDK 10.0.401:

  • CopyLocalLockFileAssemblies=true — no effect. Framework provision wins regardless. (KnownIssues.md already anticipates this for the shared framework.)
  • PreserveCompilationContext=true — populates refs/ with the assembly, but Cecil does not search refs/, so instrumentation still fails.

That leaves the manual-copy workaround from #560.

Why did this suddenly appear when we upgraded the framework? Whether the assembly is copied to the output folder depends on SDK conflict resolution comparing file versions between the package and the targeting pack's PlatformManifest.txt. Same source, same package version, two SDK feature bands:

SDK targeting pack manifest file version package file version Microsoft.Extensions.* copied result
10.0.204 10.0.8 — 10.0.826.23019 10.0.11 — 10.0.1126.37416 6 assemblies Lib present, line-rate="1"
10.0.401 10.0.12 — 10.0.1226.42308 10.0.11 — 10.0.1126.37416 none empty report

Bumping the package to match does not help: ties go to the platform (verified with 10.0.12 against pack 10.0.12).

To Reproduce

Two projects. Lib takes a transitive FrameworkReference to Microsoft.AspNetCore.App via OpenTelemetry.Instrumentation.AspNetCore, and exposes a method whose optional parameter defaults to an enum constant from Microsoft.Extensions.Logging.Abstractions.

Lib/Lib.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.11" />
    <!-- Brings a transitive FrameworkReference to Microsoft.AspNetCore.App. -->
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
  </ItemGroup>
</Project>

Lib/Thing.cs

using Microsoft.Extensions.Logging;

namespace Lib;

public static class Thing
{
    public static int Go(LogLevel level = LogLevel.Error)
    {
        return level == LogLevel.Error ? 1 : 0;
    }
}

Tests/Tests.csprojnet10.0, ProjectReference to Lib, plus Microsoft.NET.Test.Sdk 18.0.1, xunit 2.9.2, xunit.runner.visualstudio 2.8.2, coverlet.collector 6.0.4. One [Fact] calling Lib.Thing.Go().

dotnet test Tests\Tests.csproj --collect "XPlat Code Coverage"

Expected behavior

Coverage file written with data.

Actual behavior
The test passes. coverage.cobertura.xml is written and contains no packages at all. The data-collector diagnostic log shows:

[coverlet]Unable to instrument module: ...\Tests\bin\Debug\net10.0\Lib.dll
Coverlet.Core.Exceptions.CecilAssemblyResolutionException: AssemblyResolutionException for
  'Microsoft.Extensions.Logging.Abstractions, Version=10.0.0.0, Culture=neutral,
   PublicKeyToken=adb9793829ddae60'
 ---> Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly
   at Coverlet.Core.Instrumentation.NetstandardAwareAssemblyResolver.TryWithCustomResolverOnDotNetCore
   at Mono.Cecil.MetadataBuilder.GetConstantType(TypeReference constant_type, Object constant)
   at Mono.Cecil.MetadataBuilder.AddConstant(IConstantProvider owner, TypeReference type)
   at Mono.Cecil.MetadataBuilder.AddParameter(...)

Removing = LogLevel.Error from the signature makes the assembly instrument normally. The Cecil stack is narrow: the assembly is needed only to compute the underlying type of the enum constant used as the default value.
Configuration (please complete the following information):
.net 10.0.401
coverlet 6.0.4

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions