SonarQube for Azure DevOps doesn't find my code coverage

Must-share information (formatted with Markdown):

  • which versions are you using
    SonarScanner for MSBuild 5.15 (Azure DevOps plugin)

  • how is SonarQube deployed
    SonarQube server 9.9.4.87374

  • what are you trying to achieve
    Make SonarQube identify and upload my code coverage

  • what have you tried so far to achieve this
    I’ve read available documentation, applied suggestions from other similar post but so far, no success.

Description
My project is a .NET 8 project written in C#.
I’m using a local hosted build agent.
My test project as the coverlet.msbuild nugget added.
My pipeline runs my unit tests like this:

      - task: DotNetCoreCLI@2
        displayName: "Run unit tests"
        inputs:
          command: test
          projects: '**/MyUnitTests.csproj' 
          publishTestResults: true
          arguments: '--runtime win-x64 /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura'

I publish my code coverage this way:

      - task: PublishCodeCoverageResults@1
        displayName: 'Publish code coverage results'
        condition: succeeded()
        inputs:
          codeCoverageTool: 'Cobertura'
          summaryFileLocation: '**/*coverage.cobertura.xml'

With this my unit tests are executed and the code coverage collected and published in my Azure DevOps build page. However the build server is on-premise and runs an agent builder.

I can see in my Azure build log mentions to code coverage in my task ‘SonarQubeAnalyze’ but nothing is found or updated to my SonarQube server. See these messages:

C:\agent\_work\_tasks\SonarQubePrepare_XXXXXXX-b62f-4a2a-a403-89b77a063157\5.19.1\classic-sonar-scanner-msbuild\SonarScanner.MSBuild.exe end
SonarScanner for MSBuild 5.15
Using the .NET Framework version of the Scanner for MSBuild
Post-processing started.
Calling the TFS Processor executable...
Attempting to locate the CodeCoverage.exe tool...
Attempting to locate the CodeCoverage.exe tool using setup configuration...
Code coverage command line tool: E:\Program Files\Microsoft Visual Studio\2022\Enterprise\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe
Fetching code coverage report information from TFS...
Attempting to locate a test results (.trx) file...
Looking for TRX files in: C:\agent\_work\4\TestResults
No test results files found
Did not find any binary coverage files in the expected location.
Falling back on locating coverage files in the agent temp directory.
Searching for coverage files in C:\agent\_work\_temp
No coverage files found in the agent temp directory.
Coverage report conversion completed successfully.

Also found this:

INFO: Sensor JaCoCo XML Report Importer [jacoco]
INFO: 'sonar.coverage.jacoco.xmlReportPaths' is not defined. Using default locations: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml,build/reports/jacoco/test/jacocoTestReport.xml
INFO: No report imported, no coverage information will be imported by JaCoCo XML Report Importer
INFO: Sensor JaCoCo XML Report Importer [jacoco] (done) | time=3ms

And at the end:

INFO: Sensor C# File Caching Sensor [csharp]
INFO: Sensor C# File Caching Sensor [csharp] (done) | time=192ms
INFO: Sensor Zero Coverage Sensor
INFO: Sensor Zero Coverage Sensor (done) | time=190ms
INFO: CPD Executor 45 files had no CPD blocks

Looking in my test task log I see:

C:\agent\_work\_tool\dotnet\dotnet.exe test C:\agent\_work\2\s\myproject\MyProject.UnitTests\MyProject.UnitTests.csproj --logger trx --results-directory C:\agent\_work\_temp --runtime win-x64 /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura
  Determining projects to restore...
 // >>> List of projects restored here... <<<
  MyProject.DataStore -> C:\agent\_work\2\s\MyProject.DataStore\MyProject.DataStore\bin\Debug\net8.0\win-x64\MyProject.DataStore.dll
  Sonar: (MyProject.DataStore.csproj) Project processed successfully
  MyProject.DataStore.UnitTests -> C:\agent\_work\2\s\MyProject.DataStore\Myproject.DataStore.UnitTests\bin\Debug\net8.0\win-x64\MyProject.DataStore.UnitTests.dll
  Sonar: (MyProject.DataStore.UnitTests.csproj) Project processed successfully
  [coverlet] _mapping file name: 'CoverletSourceRootsMapping_MyProject.DataStore.UnitTests'
Test run for C:\agent\_work\2\s\MyProject.DataStore\MyProject.DataStore.UnitTests\bin\Debug\net8.0\win-x64\MyProject.DataStore.UnitTests.dll (.NETCoreApp,Version=v8.0)
Microsoft (R) Test Execution Command Line Tool Version 17.9.0 (x64)
Copyright (c) Microsoft Corporation.  All rights reserved.

What am I missing?

Best regards

Hey there.

Can you share your complete Azure Pipelines YML?

Hi, here it is:

trigger:
- none

name: 'Set dynamically below in a task'

jobs:
- job: ReadVariables
  pool:
    name: 'BuildAgentPool'  

  variables:
  - template: app-version.yml
  steps:
  - powershell: echo "##vso[task.setvariable variable=versionMajorMinorPatch;isOutput=true]${{variables.versionMajorMinorPatch}}"
    name: setvarStep
  - script: echo $(setvarStep.versionMajorMinorPatch)
    name: echovar

- job: BuildAndPublish
  dependsOn: ReadVariables

  pool: 
    name: 'BuildAgentPoolTeo'

  variables:
    buildConfiguration: 'Release'
    buildPlatform: 'x64'
    solution: '**/*.sln'
    versionMajorMinorPatch : $[dependencies.ReadVariables.outputs['setvarStep.versionMajorMinorPatch']]
    versionRevision : $[counter(variables['versionMajorMinorPatch'], 0)]

  steps:
      - task: PowerShell@2
        inputs:
          targetType: 'inline'
          script: |
              $shortHash = $env:BUILD_SOURCEVERSION.subString(0, 8)
              Write-Host "##vso[task.setvariable variable=shortHash]$shortHash"

      - task: PowerShell@2
        displayName: Set the name of the build
        inputs:
          targetType: 'inline'
          script: |
            [string] $buildName = "$(versionMajorMinorPatch).$(versionRevision)+$(shortHash)"
            Write-Host "Setting the name of the build to '$buildName'."
            Write-Host "##vso[build.updatebuildnumber]$buildName"
          
      - task: UseDotNet@2
        displayName: Use .NET 8.0
        inputs:
          packageType: 'sdk'
          version: '8.0.x'

      - task: DotNetCoreCLI@2
        displayName: Restore packages
        inputs:
          command: 'restore'
          feedsToUse: 'select'
          projects: '**/*.csproj'
          vstsFeed: 1fe5e5aa-5ca3-4c94-bd43-4d63ade46876/c48da80d-abbc-4117-af39-89ff8eeadb82
          includeNuGetOrg: true
      
      - task: SonarQubePrepare@5
        inputs:
          SonarQube: 'MyProject_SonarQube'
          scannerMode: 'MSBuild'
          projectKey: 'MyProject.DataStore_AY4S0w0aP1cSfuVE0T-L'
          projectVersion: '$(versionMajorMinorPatch).$(versionRevision)'

      - task: DotNetCoreCLI@2
        displayName: "Build DTO"
        inputs:
          command: build
          projects: '**/MyProject.DataStore.Dto.csproj' 
          arguments: '--runtime win-x64 --configuration $(buildConfiguration) --self-contained -p:Version=$(versionMajorMinorPatch).$(versionRevision);InformationalVersion=$(versionMajorMinorPatch).(versionRevision)+$(shortHash)'

      - task: DotNetCoreCLI@2
        displayName: "Build application"
        inputs:
          command: build
          projects: '**/MyProject.DataStore.csproj' 
          arguments: '--runtime win-x64 --configuration $(buildConfiguration) --self-contained -p:Version=$(versionMajorMinorPatch).$(versionRevision);InformationalVersion=$(versionMajorMinorPatch).(versionRevision)+$(shortHash)'

      - task: DotNetCoreCLI@2
        displayName: "Run unit tests"
        inputs:
          command: test
          projects: '**/MyProject.DataStore.UnitTests.csproj' 
          publishTestResults: true
          arguments: '--runtime win-x64 /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura'
          
      - task: SonarQubeAnalyze@5
        displayName: 'SonarQube analysis'
        inputs:
          jdkversion: 'JAVA_HOME_11_X64'
          
      - task: PublishCodeCoverageResults@1
        displayName: 'Publish code coverage results'
        condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
        inputs:
          codeCoverageTool: 'Cobertura'
          summaryFileLocation: '**/*coverage.cobertura.xml'        

      - task: SonarQubePublish@5
        condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
        displayName: 'Publish SonarQube report'
        inputs:
          pollingTimeoutSec: '300'

      - task: DeleteFiles@1
        displayName: "Emptying publish folder"
        inputs:
          SourceFolder: '$(Pipeline.Workspace)/publish'
          Contents: '**/*'

      - task: DotNetCoreCLI@2
        displayName: "Publish self-contained web api"
        condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
        inputs:
          command: publish
          publishWebProjects: false
          projects: '**/MyProject.DataStore.csproj' 
          arguments: '--runtime win-x64 --self-contained --configuration $(buildConfiguration) --output $(Pipeline.Workspace)/publish -p:Version=$(versionMajorMinorPatch).$(versionRevision) -p:InformationalVersion=$(versionMajorMinorPatch).$(versionRevision)+$(shortHash) -p:PublishReadyToRun=true'
          zipAfterPublish: false

      - task: CopyFiles@2
        displayName: "Copy report templates"
        condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
        inputs:
          Contents: '**/template.frx'
          TargetFolder: '$(Pipeline.Workspace)/publish'
          OverWrite: true

      - task: PublishPipelineArtifact@1
        displayName: "Drop published web api"
        condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
        inputs:
          targetPath: $(Pipeline.Workspace)/publish
          artifactName: 'webapi_application'
          artifactType: 'pipeline'
          
      - task: DotNetCoreCLI@2
        displayName: Publish MyProject.DataStore.Dto nuget package
        condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
        inputs:
          command: 'push'
          packagesToPush: '**/MyProject.DataStore.Dto/bin/Release/*.nupkg'
          nuGetFeedType: 'internal'
          arguments: '--skip-duplicate'
          publishVstsFeed: '1th5e5aa-5ca3-4c94-bd43-4d63ade46876/c48da80d-affc-4117-af39-89ff8eeadb82'   

      # install & prepare snyk
      - script: |
          npm install -g snyk snyk-to-html
          # This OPTIONAL step will configure the Snyk CLI to connect to the EU instance of Snyk.
          # snyk config set use-base64-encoding=true
          # snyk config set endpoint='https://app.eu.snyk.io/api'
          snyk auth $(SNYK_TOKEN)
          # explicitly allow scripts to continue if errors occur
          set +e
        displayName: 'snyk install & auth'

      # snyk code
      - script: |
          snyk code test --json | snyk-to-html -o $(Build.ArtifactStagingDirectory)\results-code.html
        continueOnError: true
        displayName: 'snyk code'        

      # snyk open source
      - script: |
          snyk test --all-projects --json | snyk-to-html -o $(Build.ArtifactStagingDirectory)\results-open-source.html
        continueOnError: true
        displayName: 'snyk open source'      

      - task: PublishHtmlReport@1
        condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
        inputs:
          reportDir: $(Build.ArtifactStagingDirectory)\results-code.html
          tabName: 'Snyk Code'

      - task: PublishHtmlReport@1
        condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
        inputs:
          reportDir: $(Build.ArtifactStagingDirectory)\results-open-source.html
          tabName: 'Snyk Open Source'

      - task: PublishBuildArtifacts@1
        condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
        inputs:
          pathToPublish: '$(Build.ArtifactStagingDirectory)'
          artifactName: Snyk Reports