Issues parsing xUnit Test Coverage data into SonarQube

The Issue

I have been chasing what it turned out to be a non-existent fault when trying to ingest test code coverage data into our SonarQube instance.

I saw my ‘problem’ in a .NET 8.0 solution with XUnit v3 based unit tests, this solution was being built using this Azure DevOps Pipelines YAML

 - task: SonarQubePrepare@7
    inputs:
      SonarQube: "SonarQube"
      scannerMode: "dotnet"
      jdkversion: "JAVA_HOME_17_X64"
      projectKey: "${{ parameters.sonarQubeProjectKey }}"
      projectName: "${{ parameters.sonarQubeProjectName }}"
      projectVersion: "$(GitVersion_Major).$(GitVersion_Minor)"
      extraProperties: |
        # Additional properties that will be passed to the scanner,
        # Put one key=value per line, example:
        sonar.cpd.exclusions=**/AssemblyInfo.cs,**/*.g.cs
        # Ingest the test results and coverage data
        sonar.cs.vscoveragexml.reportsPaths=$(Agent.TempDirectory)/**/*.coveragexml
        sonar.cs.vstest.reportsPaths=$(Agent.TempDirectory)/**/*.trx

  - task: DotNetCoreCLI@2
    displayName: ".NET Build"
    inputs:
      command: "build"
      arguments: >
        --configuration ${{ parameters.buildConfiguration }}
        --no-restore
      projects: "$(Build.SourcesDirectory)/src/MySolution.sln"

  - task: DotNetCoreCLI@2
    displayName: ".NET Test"
    inputs:
      command: "test"
      projects: "$(Build.SourcesDirectory)/src/MySolution.sln"
      arguments: >
        --configuration ${{ parameters.buildConfiguration }}
        --collect "Code coverage"
        --no-restore
        --no-build

  - task: SonarQubeAnalyze@7
    displayName: 'Complete the SonarQube analysis'
    inputs:
      jdkversion: "JAVA_HOME_17_X64"

  - task: SonarQubePublish@7
    displayName: 'Publish Quality Gate Result'
    inputs:
      pollingTimeoutSec: "300"

At the start of the SonarQubeAnalyze@7 task log I could see that the .coverage file was found and converted into a .coveragexml file. However, there were multiple ‘The device is not ready’ errors when parsing this file later in the process.

Falling back on locating coverage files in the agent temp directory.
Searching for coverage files in E:\Agent\_work\_temp
All matching files: count=2
	E:\Agent\_work\_temp\a3b45684-38dd-4511-a371-1326896d8e57\BMAGENT2025_2026-01-20.16_40_35.coverage
	E:\Agent\_work\_temp\BMAGENT2025_2026-01-20_16_40_32\In\BMAGENT2025\BMAGENT2025_2026-01-20.16_40_35.coverage
Unique coverage files: count=1
	E:\Agent\_work\_temp\a3b45684-38dd-4511-a371-1326896d8e57\BMAGENT2025_2026-01-20.16_40_35.coverage
Converting coverage file 'E:\Agent\_work\_temp\a3b45684-38dd-4511-a371-1326896d8e57\BMAGENT2025_2026-01-20.16_40_35.coverage' to 'E:\Agent\_work\_temp\a3b45684-38dd-4511-a371-1326896d8e57\BMAGENT2025_2026-01-20.16_40_35.coveragexml'.
Coverage report conversion completed successfully.

... other analysis ...

INFO: Sensor C# Tests Coverage Report Import [csharpenterprise] INFO: Parsing the Visual Studio coverage XML report E:\Agent_work_temp\a3b45684-38dd-4511-a371-1326896d8e57\BMAGENT2025_2026-01-20.16_40_35.coveragexml WARN: Skipping the import of Visual Studio XML code coverage for the invalid file path: D:\a_work\1\s\src\DotNetWorker.Core\StartupHook.cs at line 3085 java.io.IOException: The device is not ready

Initially my project contained no ‘real’ tests, just some placeholders as I was just getting the CI/CD process in place. These placeholder tests generated no significant test coverage, so I expected the coverage in SonarQube to be near 0%. However, I never bothered to actually check for any coverage value in SonarQube UI, as I assumed these errors in the log meant that coverage data was not being processed at all.

I now know this was not the case, but it took me a while to realise this.

The Exploration

On seeing the errors I started to try to isolate the issue.

  • I created a small test project that had a .NET 8.0 class library project with some MSTests. Using the same YAML pipeline settings as above, the test coverage was imported without error.
  • I next swapped from MSTest for xUnit, I could still import the coverage without errors

So the issue looked to be due to my codebase, not the basic tools in use.

An Internet search threw up that other people had seen similar issues and an answer was to gather the code coverage data using Coverlet as opposed to the built in dotnet test coverage tooling.

So I did that, and my errors disappeared the test coverage was imported. However, the downside of using Coverlet is that you loose the Coverage tab in the Azure DevOps pipeline summary view. This needs the binary .coverage format file.

As I did not want to loose the built-in coverage tab in the Azure DevOps pipeline summary view, I was back where I started, or so I thought.

It was then I realised that though I was seeing error in the log, I was also seeing a code coverage details in SonarQube. This was easier to seen now as while I had been fiddling tests had got added to the codebase, so I now had significant code coverage.

A Possible Explanation

I am not 100% certain of the problem, there is certainly an issue with SonarQube parsing .coveragexml files, the question is does it matter for my purposes?

I suspect the issue is my .coveragexml file contains coverage data for both my project assemblies and externally referenced ones. I can see this in the Azure DevOps pipeline summary code coverage view.

Azure DevOps Coverage View

I think it is external assemblies that are causing the parsing issue, but these errors are not actually important to me as they concern files outside my projects codebase under analysis in SonarQube.

So, I would have saved myself a lot of time if I had noticed the import issues were warnings and not errors, and read the line after the warning block in the SonarQube log that told me what had actually been imported successfully.

WARN: Skipping the import of Visual Studio XML code coverage for the invalid file path: D:\a\_work\1\s\src\DotNetWorker.Core\StartupHook.cs at line 3085
java.io.IOException: The device is not ready

INFO: Coverage Report Statistics: 88 files, 58 main files, 58 main files with coverage, 30 test files, 0 project excluded files, 0 other language files.

The moral of the story is to read the whole of the log before jumping to conclusions, like you were told to do for exam questions at school!

For the original version of this post see Richard Fennell's personal blog at Issues parsing xUnit Test Coverage data into SonarQube