ReportGenerator icon indicating copy to clipboard operation
ReportGenerator copied to clipboard

MS Coverage output isn't cleaning compiler generated methods/types

Open erichiller opened this issue 10 months ago • 3 comments

Describe the bug When using Microsoft CodeCoverage (aka dotnet-coverage) which can now export in Cobertura format, the Cobertura is not cleaned up for compiler generated types and methods where the names are mangled.

A real example, for a single class, the following classes are present in the CodeCoverage output Cobertura file:

<class line-rate="0.7832167832167832" branch-rate="0.75" complexity="58" name="RequestState.RequestStateManager&lt;TRequestMessage, TResponseMessage, TChannelOutput&gt;" filename="RequestState/RequestStateManager.cs">
<class line-rate="1" branch-rate="1" complexity="4" name="RequestState.RequestStateManager.&lt;&lt;GetChannelForRequestAsync&gt;g__addDbStorageAsync|14_0&gt;d&lt;TRequestMessage, TResponseMessage, TChannelOutput&gt;" filename="RequestState/RequestStateManager.cs">
<class line-rate="1" branch-rate="1" complexity="2" name="RequestState.RequestStateManager.&lt;&gt;c&lt;TRequestMessage, TResponseMessage, TChannelOutput&gt;" filename="RequestState/RequestStateManager.cs">
<class line-rate="1" branch-rate="1" complexity="1" name="RequestState.RequestStateManager.&lt;&gt;c__DisplayClass19_0&lt;TRequestMessage, TResponseMessage, TChannelOutput&gt;" filename="RequestState/RequestStateManager.cs">
<class line-rate="0.6153846153846154" branch-rate="0.5" complexity="4" name="RequestState.RequestStateManager.&lt;ClearRequestStateAsync&gt;d__17&lt;TRequestMessage, TResponseMessage, TChannelOutput&gt;" filename="RequestState/RequestStateManager.cs">
<class line-rate="0.84" branch-rate="0.8" complexity="10" name="RequestState.RequestStateManager.&lt;CompleteRequestAsync&gt;d__19&lt;TRequestMessage, TResponseMessage, TChannelOutput&gt;" filename="RequestState/RequestStateManager.cs">
<class line-rate="1" branch-rate="1" complexity="1" name="RequestState.RequestStateManager.&lt;DisposeAsync&gt;d__33&lt;TRequestMessage, TResponseMessage, TChannelOutput&gt;" filename="RequestState/RequestStateManager.cs">
<class line-rate="0.8888888888888888" branch-rate="0.9166666666666666" complexity="12" name="RequestState.RequestStateManager.&lt;DisposeAsyncCore&gt;d__34&lt;TRequestMessage, TResponseMessage, TChannelOutput&gt;" filename="RequestState/RequestStateManager.cs">
<class line-rate="1" branch-rate="1" complexity="4" name="RequestState.RequestStateManager.&lt;mkmrk-DataSource-Ibkr-RequestState-IMessageStateManager-MarkSentAsync&gt;d__30&lt;TRequestMessage, TResponseMessage, TChannelOutput&gt;" filename="RequestState/RequestStateManager.cs">

Which results in the following classes being reported:

Name Line Branch Method
RequestState.RequestStateManager.<>c<TRequestMessage,
TResponseMessage, TChannelOutput>
100%
RequestState.RequestStateManager<TRequestMessage, TRe
sponseMessage, TChannelOutput>
77.3% 75% 85.7%
RequestState.RequestStateManager<TRequestMessage, TRe
sponseMessage, TChannelOutput>
87.3% 85.2% 100%
RequestState.RequestStateManager<TRequestMessage, TRe
sponseMessage>
65.3% 66.6% 100%

To Reproduce Any use of Microsoft CodeCoverage on any codebase with constructs such as async methods, local functions, etc. causes these same issues.

erichiller avatar Apr 12 '24 11:04 erichiller

Probably Microsoft CodeCoverage uses a slightly different formatting that other tools (e.g. coverlet). I will look into this within the next days and try to handle this format as well.

danielpalme avatar Apr 12 '24 17:04 danielpalme

My assumption is correct. The format is different:

dotnet-test Test.ClassWithLocalFunctions.MyNestedClass<T1, T2> Test.ClassWithLocalFunctions.MyNestedClass.<MyAsyncMethod>d__4<T1, T2, T3> Test.ClassWithLocalFunctions.MyNestedClass.<>c__DisplayClass4_0.<<MyAsyncMethod>g__MyAsyncLocalFunction|0>d<T1, T2, T3, T4>

coverlet Test.ClassWithLocalFunctions1/MyNestedClass1 Test.ClassWithLocalFunctions1/MyNestedClass1/<MyAsyncMethod>d__41 Test.ClassWithLocalFunctions1/MyNestedClass1/&lt;&gt;c__DisplayClass4_01/<<MyAsyncMethod>g__MyAsyncLocalFunction|0>d`1

Will have to adjust in ReportGenerator

danielpalme avatar Apr 17 '24 18:04 danielpalme

I invested several hours, but I have not yet found a way filter out those classes and at the same time don't miss any relevant coverage results.

danielpalme avatar Apr 23 '24 20:04 danielpalme

@erichiller Do you know if the PublishCodeCoverageResults@2 Azure DevOps pipeline task handles this correctly?

@danielpalme Maybe I'm missing something, but couldn't you just exclude/merge all types that have an invalid name? The compiler generates those invalid names to make sure they will never clash with the actually written code. In the given examples they all start with <.

cremor avatar May 15 '24 06:05 cremor

@cremor A < is not enough to exclude a class. E.g. generic class names also contain <.

danielpalme avatar May 15 '24 20:05 danielpalme

I meant type.Name.StartsWith('<').

cremor avatar May 16 '24 04:05 cremor

I will have a look again as soon as possible. I'm pretty busy at the moment so it may take some time.

danielpalme avatar May 16 '24 18:05 danielpalme

I just made some change to improve the handling of Cobertura files generated by "Microsoft Coverage".

In your example the coverage file contains the following class names:

RequestState.RequestStateManager<TRequestMessage, TResponseMessage, TChannelOutput> RequestState.RequestStateManager.<<GetChannelForRequestAsync>g__addDbStorageAsync|14_0>d<TRequestMessage, TResponseMessage, TChannelOutput> RequestState.RequestStateManager.<>c<TRequestMessage, TResponseMessage, TChannelOutput> RequestState.RequestStateManager.<>c__DisplayClass19_0<TRequestMessage, TResponseMessage, TChannelOutput> RequestState.RequestStateManager.<ClearRequestStateAsync>d__17<TRequestMessage, TResponseMessage, TChannelOutput> RequestState.RequestStateManager.<CompleteRequestAsync>d__19<TRequestMessage, TResponseMessage, TChannelOutput> RequestState.RequestStateManager.<DisposeAsync>d__33<TRequestMessage, TResponseMessage, TChannelOutput> RequestState.RequestStateManager.<DisposeAsyncCore>d__34<TRequestMessage, TResponseMessage, TChannelOutput> RequestState.RequestStateManager.<mkmrk-DataSource-Ibkr-RequestState-IMessageStateManager-MarkSentAsync>d__30<TRequestMessage, TResponseMessage, TChannelOutput>

With my change, only the following element will appear in the report:

RequestState.RequestStateManager<TRequestMessage, TResponseMessage, TChannelOutput>

I will do some more testing and probably publish a new release within the next days.

danielpalme avatar May 28 '24 19:05 danielpalme

I just released version 5.3.6 with some improvements regarding Microsoft CodeCoverage. Please let me know if this works (better) for you.

danielpalme avatar Jun 01 '24 09:06 danielpalme

@danielpalme in some cases (async + generics + static) classes are still reported multiple times in 5.3.6 Name properties under class tags

Repro.Class1.&lt;Method3&gt;d__2&lt;T, TU&gt;
Repro.Class1.&lt;Method2&gt;d__1&lt;T&gt;
Repro.Class1.&lt;Method1&gt;d__0

Complete xml file

More details in microsoft/codecoverage issue

standsed avatar Jun 14 '24 06:06 standsed

I will have a look in the next days.

danielpalme avatar Jun 14 '24 06:06 danielpalme

@danielpalme for nested classes also getting duplicated statistics in report https://github.com/standsed/CodeCoverageRepro/blob/main/Repro/Class2.cs Class2 includes statistics also for Class3 image Coverage report via Coverlet collector image

standsed avatar Jun 21 '24 08:06 standsed

@standsed I answered here on the problem: https://github.com/microsoft/codecoverage/issues/124#issuecomment-2171511622

TLDR: I can't fix this without causing other issues. Coverlet uses / to separate namespace and nested class names. Microsoft CodeCoverage uses . and therefore there is no way to distinct between namespaces and class name.

danielpalme avatar Jun 22 '24 11:06 danielpalme