I have a solution with many .Net Framework 4.8 projects. I've been working on CI/CD automation, using Jenkins pipelines. I have a large suite of NUnit tests that I run, and am attempting to collect test and code coverage results, which I then pass to SonarQube for analysis/tracking, as well as Jenkins itself, using assorted plugins for visualization and such.
I had originally gone down the path of using OpenCover with the NUnit console application. I seemed to have gotten good results with this. However, it took a LONG time for the entire suite to complete (along with the coverage instrumentation)--nearly 3 hours for ~3700 tests.
It was then that I realized that we might be able to leverage NCrunch. Indeed, that seems to be what the NCrunch Console Tool is intended to do. So I introduced this into my pipeline, following all of the NCrunch documentation. I get an OpenCover.xml report and a TestResultsInNUnitFormat.xml report that I can send off for SonarQube, as well as visualize with my Jenkins plugins.
With what I'm seeing in all of these, I have open questions as to the generation of both reports.
First, I
think the NUnit report you're generating may be based on an old/outdated format. The reason I think this is that, ever since NUnit got rid of its GUI runner, I've been using the website located
here for opening up reports for visualization. On that site, I can't open the TestResultsInNUnitFormat.xml report. I searched around the web and found an application for viewing NUnit reports
here that can open your report, but which blows up when trying to open a newer one (created with the NUnit console runner). This viewer has a copyright date of 2012.
Interestingly, the Jenkins plugin looks like it can handle this report well, though there does seem to be a discrepancy between the counts (and also the "Package") for tests that use TestCaseAttribute, TestCaseSourceAttribute, and perhaps others.
However, when SonarQube tries to parse the report, it chokes on it.
With SonarQube debugging on, when it attempts to parse the report generated by the NUnit console runner, I get many messages of the following:
18:23:13 18:23:13.803 DEBUG: Added Test Method: WOTI.Xift.Arbiter.UnitTests.WOTI.Xift.Tests.Arbiter.ArbiterConfigurationSettingsTests.CanGetADServiceAccount to File: E:\Jenkins\workspace\Code_Quality_PR-35\Tests\Arbiter.UnitTests\ArbiterConfigurationSettingsTests.cs
However, when it attempts to parse the NCrunch TestResultsInNUnitFormat.xml, they all look like the following:
16:04:41 16:04:41.403 DEBUG: Test method null.WOTI.Xift.Tests.Arbiter.ArbiterConfigurationSettingsTests.CanGetADServiceAccount cannot be mapped to the test source file. The test will not be included.
Not a single test can be added.
Just from the above, it seems that SonarQube is looking for the assembly name to prepend to the namespace -> class -> method, and this may be the entire problem.
As to the problem with my OpenCover report: when SonarQube tries to parse it, I get many messages (6714) of the following type:
16:04:38 16:04:38.161 DEBUG: Coverage import: Line 69 is out of range in the file 'Libraries/Core/Services/Program/IFullCaseDetailsService.cs' (lines: 68)
For this particular file, I get the error for lines 69-84.
If I look for this file in the OpenCover.xml, I find:
<File uid="900" fullPath="E:\Jenkins\workspace\Code_Quality_PR-35\Libraries\Core\Services\Program\IFullCaseDetailsService.cs" />
If I search the file for "900", I find many things in the report like this:
<Class>
<Summary numSequencePoints="17" visitedSequencePoints="17" numBranchPoints="0" visitedBranchPoints="0" sequenceCoverage="100" branchCoverage="0" maxCyclomaticComplexity="0" minCyclomaticComplexity="0" maxCrapScore="0" minCrapScore="0" visitedClasses="1" numClasses="1" visitedMethods="1" numMethods="1" />
<FullName>Xift.Core.Security.PermissionService+<ProcessFieldPermissions>d__7</FullName>
<Methods>
<Method visited="true" cyclomaticComplexity="0" nPathComplexity="0" sequenceCoverage="0" branchCoverage="0" crapScore="0" isConstructor="false" isStatic="false" isGetter="false" isSetter="false">
<Summary numSequencePoints="17" visitedSequencePoints="17" numBranchPoints="0" visitedBranchPoints="0" sequenceCoverage="100" branchCoverage="0" maxCyclomaticComplexity="0" minCyclomaticComplexity="0" maxCrapScore="0" minCrapScore="0" visitedClasses="0" numClasses="0" visitedMethods="1" numMethods="1" />
<MetadataToken>100671976</MetadataToken>
<Name>System.Boolean Xift.Core.Security.PermissionService+<ProcessFieldPermissions>d__7::MoveNext()</Name>
<FileRef uid="123" />
<SequencePoints>
<SequencePoint vc="1" uspid="32932" ordinal="0" offset="50" sl="67" sc="137" el="67" ec="138" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32933" ordinal="1" offset="51" sl="68" sc="13" el="68" ec="67" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32934" ordinal="2" offset="78" sl="69" sc="13" el="69" ec="96" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32935" ordinal="3" offset="140" sl="70" sc="13" el="70" ec="20" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32936" ordinal="4" offset="141" sl="70" sc="38" el="70" ec="104" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32937" ordinal="5" offset="207" sl="70" sc="22" el="70" ec="34" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32938" ordinal="6" offset="224" sl="70" sc="106" el="70" ec="107" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32939" ordinal="7" offset="225" sl="71" sc="17" el="71" ec="24" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32940" ordinal="8" offset="226" sl="71" sc="52" el="71" ec="126" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32941" ordinal="9" offset="293" sl="71" sc="26" el="71" ec="48" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32942" ordinal="10" offset="310" sl="71" sc="128" el="71" ec="129" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32943" ordinal="11" offset="311" sl="72" sc="21" el="72" ec="53" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32944" ordinal="12" offset="342" sl="73" sc="17" el="73" ec="18" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32945" ordinal="13" offset="350" sl="71" sc="49" el="71" ec="51" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32946" ordinal="14" offset="377" sl="74" sc="13" el="74" ec="14" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32947" ordinal="15" offset="385" sl="70" sc="35" el="70" ec="37" bec="0" bev="0" fileid="900" />
<SequencePoint vc="1" uspid="32948" ordinal="16" offset="415" sl="75" sc="9" el="75" ec="10" bec="0" bev="0" fileid="900" />
</SequencePoints>
<BranchPoints />
<MethodPoint xsi:type="SequencePoint" vc="1" uspid="32932" ordinal="0" offset="50" sl="67" sc="137" el="67" ec="138" bec="0" bev="0" fileid="900" />
</Method>
</Methods>
</Class>
<Class>
I assume it's the fileid that's pointing back to that file, and the "sl" and "el" attributes that are pointing to line numbers that are out of range.
So, it seems to me that the OpenCover report is not being generated correctly, or is somehow getting corrupted.
I've also been able to use an AltCover.Visualizer tool to open this report. If I traverse the tree looking for the above class and methods, clicking on it shows me the source code for the file with fileid 900 (IFullCaseDetailsService).
I did read the support post
here, which seems rather similar, though not exactly. But there are a lot of moving parts to this part of my issue, so I want to be deliberate with a solution. So I have not yet tried anything as specified in that post, e.g., set DebugType to portable.
One of the issues is that I do use a Grid Node Server to distribute my tests during my CI build, and I wonder if that might be something that's screwing this up. (ChapGPT seems to think so.)
Any thoughts on the above are appreciated.