SonarQube won't import dotnet-coverage file

Must-share information (formatted with Markdown):

  • which versions are you using (SonarQube Server / Community Build, Scanner, Plugin, and any relevant extension)

    • Latest installed by running:
          dotnet tool install --global dotnet-coverage
          dotnet tool install --global dotnet-sonarscanner
          dotnet tool install --global dotnet-reportgenerator-globaltool
      
  • how is SonarQube deployed: zip, Docker, Helm

    • SonarCloud is being used
  • what are you trying to achieve

    • Getting code coverage working in SonarQube using the SonarQube docs
  • what have you tried so far to achieve this

    • All 3 different options for dotnet-coverage output formats (.coverage, .xml and .cobertura.xml). None work

On the SonarSource documentation there’s some information on how you should create a coverage file for dotnet with dotnet-coverage. This however, does not work and results in a failure during import.

Here’s the guidance on the docs:

dotnet sonarscanner begin /k:"<sonar-project-key>"
    /d:sonar.token="<sonar-token>"
    /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml
dotnet build --no-incremental
dotnet-coverage collect "dotnet test" -f xml -o "coverage.xml"
dotnet sonarscanner end /d:sonar.token="<sonar-token>"

When I run this a file is generated which starts as follows:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<results>
  <modules>
    <module id="030075AC1D6B784E8D4EF7926A88B8ED4B405EC2" name="DeveDiskSpaceInfo.dll" path="DeveDiskSpaceInfo.dll" block_coverage="64.75" line_coverage="73.96" blocks_covered="891" blocks_not_covered="485" lines_covered="659" lines_partially_covered="1" lines_not_covered="231">
      <functions>
   ...

When SonarQube picks this up in the scanner it shows the following error:

2025-08-04T16:05:37.6203622Z INFO: Parsing the OpenCover report /home/runner/work/DeveDiskSpaceInfo/DeveDiskSpaceInfo/././DeveDiskSpaceInfo.Tests/bin/Release/net9.0/TestResults/f4939be0-c657-4425-8a6c-1dd96060820a.xml
2025-08-04T16:05:37.6492824Z WARN: Could not import coverage report '././DeveDiskSpaceInfo.Tests/bin/Release/net9.0/TestResults/f4939be0-c657-4425-8a6c-1dd96060820a.xml' because 'Missing root element <CoverageSession> in /home/runner/work/DeveDiskSpaceInfo/DeveDiskSpaceInfo/././DeveDiskSpaceInfo.Tests/bin/Release/net9.0/TestResults/f4939be0-c657-4425-8a6c-1dd96060820a.xml at line 2'. Troubleshooting guide: https://community.sonarsource.com/t/37151

The scanner logs indicate that SonarQube is attempting to parse a file at /home/runner/work/DeveDiskSpaceInfo/DeveDiskSpaceInfo/././DeveDiskSpaceInfo.Tests/bin/Release/net9.0/TestResults/f4939be0-c657-4425-8a6c-1dd96060820a.xml as an OpenCover report, not the ‘coverage.xml’ you mentioned generating (which should be parsed as ‘vscoveragexml’ or ‘sonar.cs.vscoveragexml.reportsPaths’ setting).

I would suggest that you start by checking to make sure sonar.cs.opencover.reportsPaths isn’t being set anywhere in your build, or in your .csproj files.

Here’s the relevant build script code:

    - name: SonarQube begin
      if: github.actor != 'dependabot[bot]'
      run: dotnet-sonarscanner begin /k:"DeveDiskSpaceInfo" /o:"devedse-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login=${{secrets.SONARQUBETOKEN}} /d:sonar.cs.opencover.reportsPaths="./DeveDiskSpaceInfo.Tests/bin/Release/net9.0/TestResults/*.xml" /d:sonar.coverage.exclusions="DeveDiskSpaceInfo.Tests/**/*.cs"
    - name: dotnet build
      run: dotnet build DeveDiskSpaceInfo.slnx -c Release --no-restore /p:Version=1.0.${{needs.generate_version_number.outputs.build_number}}
    - name: dotnet test
      #run: dotnet run --coverage -c Release --no-build --coverage-output-format xml --project DeveDiskSpaceInfo.Tests
      run: dotnet-coverage collect 'dotnet test DeveDiskSpaceInfo.Tests' -f xml -o './DeveDiskSpaceInfo.Tests/bin/Release/net9.0/TestResults/coverage.xml'
    - name: SonarQube end
      if: github.actor != 'dependabot[bot]'
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: dotnet-sonarscanner end /d:sonar.login=${{secrets.SONARQUBETOKEN}}

I do in fact set the path to the directory where the test results are generated. I can’t imagine how it could find them if I didn’t.

A couple of things to point out here.

If you’re running dotnet-coverage collect "dotnet test" -f xml, make sure you use sonar.cs.vscoveragexml.reportsPaths instead of sonar.cs.opencover.reportsPaths.

The docs explain that this is because the dotnet-coverage tool’s output matches the Visual Studio Code Coverage format, so that’s the parameter SonarQube expects.

Note that we specify the path to the reports using sonar.cs.vscoveragexml.reportsPaths because this tool’s output format is the same as the Visual Studio Code Coverage tool (see the Test coverage parameters section for information about this parameter). We use the -f xml parameter to specify that the output format is in XML.

Also, keep in mind that you’re using a wildcard like ./DeveDiskSpaceInfo.Tests/bin/Release/net9.0/TestResults/*.xml to specify the report paths. That folder often holds both test execution results and coverage reports, all saved as .xml files. So, it’s better to specify just your coverage.xml file here to avoid any mix-ups.

Thanks, this fixed my issue:

1 Like