coverlet-coverage / coverlet

Cross platform code coverage for .NET
MIT License
2.97k stars 386 forks source link

[coverlet] Unable to instrument module - NET 5 - Microsoft.Extensions.Logging.Abstractions #1231

Closed 304NotModified closed 1 year ago

304NotModified commented 2 years ago

For some test projects, but not all, we get

/home/AzDevOps/.nuget/packages/coverlet.msbuild/3.1.0/build/coverlet.msbuild.targets(39,5): warning : [coverlet] Unable to instrument module: MyApi.dll because : AssemblyResolutionException for 'Microsoft.Extensions.Logging.Abstractions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. Try to add to test projects or pass '/p:CopyLocalLockFileAssemblies=' option to the 'dotnet test' command-line

Tried:

Related:

PS I hope this helps to diagnose the issue https://github.com/coverlet-coverage/coverlet/issues/1196. I have run more than 10 builds (with 10 tries), but all have the same issue :(

304NotModified commented 2 years ago

I fixed this on Azure Devops (Linux build) with a copy of the dll as follows:

This fixes two warnings:

/home/AzDevOps/.nuget/packages/coverlet.msbuild/3.1.0/build/coverlet.msbuild.targets(39,5):
 warning : [coverlet] Unable to instrument module: 
/agent/_work/1/s/Backend/MyApp.Domain.Tests/bin/Release/net5.0/MyApp.Infra.dll 
because : AssemblyResolutionException for 'Microsoft.Extensions.Logging.Abstractions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. 
Try to add <PreserveCompilationContext>***</PreserveCompilationContext> to test projects </PropertyGroup> or pass '/p:CopyLocalLockFileAssemblies=***' option to the 'dotnet test' command-line 
[/agent/_work/1/s/Backend/MyApp.Domain.Tests/MyApp.Domain.Tests.csproj]

/home/AzDevOps/.nuget/packages/coverlet.msbuild/3.1.0/build/coverlet.msbuild.targets(39,5): 
warning : [coverlet] Unable to instrument module: 
/agent/_work/1/s/Backend/MyApp.Infra.Tests/bin/Release/net5.0/MyApp.Infra.dll 
because: AssemblyResolutionException for 'Microsoft.Extensions.Logging.Abstractions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. 
Try to add <PreserveCompilationContext>***</PreserveCompilationContext> to test projects </PropertyGroup> or pass '/p:CopyLocalLockFileAssemblies=***' option to the 'dotnet test' command-line 
[/agent/_work/1/s/Backend/MyApp.Infra.Tests/MyApp.Infra.Tests.csproj]

Temp fix:

  - task: PowerShell@2
    displayName: 'fix - [coverlet] Unable to instrument module - Microsoft.Extensions.Logging.Abstractions'
    inputs:
      pwsh: true
      targetType: 'inline'
      script: |
        $dllToFix = "Microsoft.Extensions.Logging.Abstractions.dll"
        $from = "Backend/MyApp.Infra.Tests/bin/Release/net5.0/refs/$dllToFix"
        Copy-Item $from -Destination "Backend/MyApp.Domain.Tests/bin/Release/net5.0"
        Copy-Item $from -Destination "Backend/MyApp.Infra.Tests/bin/Release/net5.0"

it seems I have to copy the dll also one level up? (from .../net5.0/refs to .../net5.0) - does that makes any sense?

update, tested, and I really need to copy from net5.0/refs to the net5.0 folder

MarcoRossignoli commented 2 years ago

Hi @304NotModified thanks for reporting this. We'll take a look asap.

MarcoRossignoli commented 2 years ago

@304NotModified are you able to provide a repro?I have an idea of the possible problem but I need a repro to validate it. Is it a desktop app?

304NotModified commented 2 years ago

It's a ASP.NET Core 5 application with multiple projects.

I'm afraid it will take some time to create a repro.

hedasilv commented 2 years ago

I am having the same issue:

/xxx/xxx/.nuget/packages/coverlet.msbuild/3.1.0/build/coverlet.msbuild.targets(39,5): warning:
[coverlet] Unable to instrument module: xxx/xxx/xxx/xxx/xxx.dll because: 
AssemblyResolutionException for 'Microsoft.Extensions.Logging.Abstractions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. 
Try to add <PreserveCompilationContext>true</PreserveCompilationContext> to test projects </PropertyGroup> or pass '/p:CopyLocalLockFileAssemblies=true' option to the 'dotnet test' command-line

As @304NotModified pointed out it seems that coverlet msbuild cannot find the DLL under bin/Debug/.net5.0. Even though the DLL is available in bin/Debug/.net5.0/refs, things only work when I manually copy the DLL file to the parent folder,bin/Debug/.net5.0. Is it the case that coverlet needs to scan the /refs folder as well to prevent this type of issue?

304NotModified commented 2 years ago

If I understand it correctly, <PreserveCompilationContext>true</PreserveCompilationContext> is creating the refs folder (or ref?), see https://docs.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#preservecompilationcontext

MarcoRossignoli commented 2 years ago

PreserveCompilationContext is used to emit information on deps files and help coverlet to try to resolve libs needed by Cecil.

https://github.com/MarcoRossignoli/coverlet/blob/master/src/coverlet.core/Instrumentation/CecilAssemblyResolver.cs#L173

If you're able to provide a repro I can try to improve this one.

andrey-chernykh commented 2 years ago

As workaround I'm using such test pipeline:

dotnet publish
cp -f ./bin/Debug/net6.0/refs/* ./bin/Debug/net6.0/
dotnet test --collect:\"XPlat Code Coverage\" -l:\"junit;LogFileName=results.xml\" -r /reports --settings coverlet.runsettings --diag /reports/test.log --no-restore

You can simplify the last string to dotnet test --collect:\"XPlat Code Coverage\" --no-restore

304NotModified commented 2 years ago

@MarcoRossignoli

I'm afraid that creating reproduction is hard.

I propose the following: coverlet tries to load from the refs folder, create a prerelease of coverlet, and I will be happy to test it :)

MarcoRossignoli commented 2 years ago

Despite the fact that use publish+ref works I feel it too much as an "hack", I prefer something more useful in every scenario. One idea I had also in past would be add a configuration that can be used by instrumentor for probing additional default directories. Something like typesresolutionpaths where a dev can in case of issues specify a list of path where to search if default paths don't work.

adampaquette commented 1 year ago

As additional information, I reproduce the problem in my library. Check the action associated with: https://github.com/adampaquette/Typely/pull/28.

I have the following structure: Typely.EfCore depends on Typely.Core. The project Typely.EfCore.Tests needs to instrument Typely.Core to fill the missing coverage on common functions.

MarcoRossignoli commented 1 year ago

fixed in https://github.com/coverlet-coverage/coverlet/pull/1449

dovic95 commented 8 months ago

I've referenced the latest nightly build version in my dotnet 8 solution and I still get the error for two projects:

Coverlet.Core.Exceptions.CecilAssemblyResolutionException: AssemblyResolutionException for 'Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. Try to add true to test projects or pass '/p:CopyLocalLockFileAssemblies=true' option to the 'dotnet test' command-line ---> Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: 'Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' --- End of inner exception stack trace --- at Coverlet.Core.Instrumentation.NetstandardAwareAssemblyResolver.TryWithCustomResolverOnDotNetCore(AssemblyNameReference name) in /_/src/coverlet.core/Instrumentation/CecilAssemblyResolver.cs:line 217

I have tried several workarounds, but the only one that works is copying the Microsoft.Extensions.Logging.Abstractions from the refs folder to the parent one, as suggested by @304NotModified.

Do you guys still have the issue? @MarcoRossignoli when you say that this issue is fixed in #1449, is there any specific thing to do to get rid of this issue?

MarcoRossignoli commented 8 months ago

@lg2de any idea of the @dovic95 issue? I would expect that we try to scan all the shared framework installed and that lib should be there for asp.net core

"C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.1\Microsoft.Extensions.Logging.Abstractions.dll"

cc: @daveMueller @bert2

lg2de commented 8 months ago

I'm currently not that motivated to investigate that issue again, because I was waiting half a year for a release of my fix. Now, I switched (back) to MS coverage in my project... I do not have an idea except that Microsoft has changed some detail with .NET 8. IF I start investigation I would update the existing tests from net6 to net8.

MarcoRossignoli commented 8 months ago

Got it thanks @lg2de for the quick response and contributions to coverlet!

mikejr83 commented 7 months ago

I just noticed that I wasn't getting code coverage for an assembly. I use Fine Code Coverage in Visual Studio and was taking a look at its logs. I found this exception:

Unable to instrument module: C:\projects\refunds-service\Firstech.Refunds.Tests\bin\Debug\net8.0\fine-code-coverage\build-output\Firstech.Refunds.API.Web.dll
Coverlet.Core.Exceptions.CecilAssemblyResolutionException: AssemblyResolutionException for 'Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. Try to add <PreserveCompilationContext>true</PreserveCompilationContext> to test projects </PropertyGroup> or pass '/p:CopyLocalLockFileAssemblies=true' option to the 'dotnet test' command-line
 ---> Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: 'Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'
   --- End of inner exception stack trace ---
   at Coverlet.Core.Instrumentation.NetstandardAwareAssemblyResolver.TryWithCustomResolverOnDotNetCore(AssemblyNameReference name) in /_/src/coverlet.core/Instrumentation/CecilAssemblyResolver.cs:line 215
   at Coverlet.Core.Instrumentation.NetstandardAwareAssemblyResolver.Resolve(AssemblyNameReference name) in /_/src/coverlet.core/Instrumentation/CecilAssemblyResolver.cs:line 127
   at Mono.Cecil.MetadataResolver.Resolve(TypeReference type)
   at Mono.Cecil.ModuleDefinition.Resolve(TypeReference type)
   at Mono.Cecil.TypeReference.Resolve()
   at Mono.Cecil.Mixin.CheckedResolve(TypeReference self)
   at Mono.Cecil.MetadataBuilder.GetConstantType(TypeReference constant_type, Object constant)
   at Mono.Cecil.MetadataBuilder.AddConstant(IConstantProvider owner, TypeReference type)
   at Mono.Cecil.MetadataBuilder.AddParameter(UInt16 sequence, ParameterDefinition parameter, ParamTable table)
   at Mono.Cecil.MetadataBuilder.AddParameters(MethodDefinition method)
   at Mono.Cecil.MetadataBuilder.AddMethod(MethodDefinition method)
   at Mono.Cecil.MetadataBuilder.AddMethods(TypeDefinition type)
   at Mono.Cecil.MetadataBuilder.AddType(TypeDefinition type)
   at Mono.Cecil.MetadataBuilder.AddTypes()
   at Mono.Cecil.MetadataBuilder.BuildTypes()
   at Mono.Cecil.MetadataBuilder.BuildModule()
   at Mono.Cecil.MetadataBuilder.BuildMetadata()
   at Mono.Cecil.ModuleWriter.<>c.<BuildMetadata>b__2_0(MetadataBuilder builder, MetadataReader _)
   at Mono.Cecil.ModuleDefinition.Read[TItem,TRet](TItem item, Func`3 read)
   at Mono.Cecil.ModuleWriter.BuildMetadata(ModuleDefinition module, MetadataBuilder metadata)
   at Mono.Cecil.ModuleWriter.Write(ModuleDefinition module, Disposable`1 stream, WriterParameters parameters)
   at Mono.Cecil.ModuleWriter.WriteModule(ModuleDefinition module, Disposable`1 stream, WriterParameters parameters)
   at Mono.Cecil.ModuleDefinition.Write(Stream stream, WriterParameters parameters)
   at Coverlet.Core.Instrumentation.Instrumenter.InstrumentModule() in /_/src/coverlet.core/Instrumentation/Instrumenter.cs:line 337
   at Coverlet.Core.Instrumentation.Instrumenter.Instrument() in /_/src/coverlet.core/Instrumentation/Instrumenter.cs:line 153
   at Coverlet.Core.Coverage.PrepareModules() in /_/src/coverlet.core/Coverage.cs:line 135

I traced back in my commit history to where it started happening. The best I can tell is that I took a service that was for audit logging and changed its signature to take LogLevel:

        public Task IncomingRequestReceived(int clientId,
            AuditLoggingReqeuestType requestType,
            AuditLoggingRequestPurpose requestPurpose,
            string? message = null,
            string? queryString = null,
            string? requestBody = null,
            LogLevel logLevel = LogLevel.Debug)
        {
            this.logger.Log(logLevel,
                "Incoming request recieved: Request type {RequestType} - Request purpose {RequestPurpose} - Message {Message} - Query string: {QueryString} - Request Body {RequestBody}",
                requestType,
                requestPurpose,
                message,
                queryString,
                requestBody);

I'm using the Moq library and have some verifications setup:

            this.mockAuditLoggerService
                .Verify(s => s.IncomingRequestReceived(It.Is<int>(id => id == CLIENT_ID),
                    It.Is<AuditLoggingReqeuestType>(t => t == AuditLoggingReqeuestType.Refund),
                    It.Is<AuditLoggingRequestPurpose>(t => t == AuditLoggingRequestPurpose.Create),
                    It.IsAny<string?>(),
                    It.IsAny<string?>(),
                    It.IsAny<string?>(),
                    It.IsAny<LogLevel>()),
                    Times.Once());

Now, I don't know why this would make any difference. First I removed all the code in my test assembly that would reference the Microsoft.Extensions.Logging.LogLevel, Microsoft.Extensions.Logging.Abstractions enum. Running tests and code coverage gave me the same error. When I went through and reverted the service's interface and implementation to no longer use the enum and replace this.logger.Log(logLevel... with this.logger.LogDebug(... I was able to avoid the error and get results.

This is very interesting. I have ILogger throughout my test assembly and in every class. I thought that came from the abstractions assembly. Why using either the enum or ILogger.Log would cause this is beyond me. I'm pretty sure that Log method is an extension method just as LogDebug.

Hopefully this is helpful in some way.