Integrating Azure Pipelines with SonarCloud
Today's post is about the integration of SonarCloud with Azure Pipelines.
SonarCloud is the SaaS version of the well-known SonarQube, a tool considered the standard for static code analysis.
This post's purpose is to show how SonarCloud can be integrated into a pull request validation pipeline, maximizing our ability to detect problems or bugs in the code.
Let's start with a standard pull request validation pipeline, assuming an Azure service connection to SonarCloud has already been created.
1trigger: none
2
3pool:
4 vmImage: "ubuntu-latest"
5
6variables:
7 buildConfiguration: "Release"
8 buildSolution: "src/discografia.sln"
9 testProjects: "src/tests/**/*.csproj"
10
11steps:
12 - task: DotNetCoreCLI@2
13 name: "Restore"
14 inputs:
15 projects: $(buildSolution)
16 command: "restore"
17
18 - task: DotNetCoreCLI@2
19 name: "Build"
20 displayName: "dot net build $(buildConfiguration)"
21 inputs:
22 command: "build"
23 projects: $(buildSolution)
24 arguments: "--configuration $(buildConfiguration)"
25
26 - task: DotNetCoreCLI@2
27 name: "RunTests"
28 displayName: "dot net test $(buildConfiguration)"
29 inputs:
30 command: "test"
31 projects: $(testProjects)
32 arguments: "--configuration $(buildConfiguration) --logger trx"
33 publishTestResults: true
With this configuration, we will restore all solution dependencies, execute the build, and run the tests. At this moment, there is no integration with Sonar.
When we run the pipeline, everything seems normal except for the test coverage, which appears as not configured.
To generate the test coverage report, three steps are necessary:
- Install the coverlet.msbuild package in all test projects.
- Modify the test step to collect the coverage data.
- Add a task to publish the coverage report.
1 - task: DotNetCoreCLI@2
2 name: "RunTests"
3 displayName: "dot net test $(buildConfiguration)"
4 inputs:
5 command: "test"
6 projects: $(testProjects)
7 arguments: "--configuration $(buildConfiguration) /p:CollectCoverage=true /p:CoverletOutputFormat=Cobertura /p:CoverletOutput=$(Build.SourcesDirectory)/TestResults/Coverage/ --logger trx"
8 publishTestResults: true
9
10 - task: PublishCodeCoverageResults@1
11 displayName: "Publish code coverage report"
12 inputs:
13 codeCoverageTool: "Cobertura"
14 summaryFileLocation: "$(Build.SourcesDirectory)/**/*.cobertura.xml"
Now it's possible to see the coverage report in the pipeline
To integrate with SonarCloud, we need to add 3 new steps to our pipeline and fix the coverage report generation:
- Prepare the code analysis for SonarCloud immediately before the build step.
1 - task: SonarCloudPrepare@1
2 inputs:
3 SonarCloud: $(sonar.connection)
4 organization: $(sonar.organization)
5 scannerMode: "MSBuild"
6 projectKey: $(sonar.projectKey)
7 projectName: $(sonar.projectName)
8 extraProperties: |
9 sonar.exclusions=**/obj/**,**/*.dll
10 sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/**/coverage.opencover.xml
11 sonar.cs.vstest.reportsPaths=$(Build.SourcesDirectory)/**/*.trx
2. Modify the coverage report generation
To have the code coverage report in Azure Pipelines and SonarCloud, we need to solve a problem: Azure Pipelines doesn't accept the OpenCover format, and SonarCloud doesn't take the Cobertura format!
As a solution, we generate the report in the OpenCover format and add a script to convert it to Cobertura.
1 - task: DotNetCoreCLI@2
2 name: "RunTests"
3 displayName: "dot net test $(buildConfiguration)"
4 inputs:
5 command: "test"
6 projects: $(testProjects)
7 arguments: "--configuration $(buildConfiguration) /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput=$(Build.SourcesDirectory)/TestResults/Coverage/ --logger trx"
8 publishTestResults: true
9
10 - script: |
11 dotnet tool install dotnet-reportgenerator-globaltool --tool-path .
12 ./reportgenerator "-reports:$(Build.SourcesDirectory)/TestResults/Coverage/coverage.opencover.xml" "-targetdir:coverage/Cobertura" "-reporttypes:Cobertura;HTMLInline;HTMLChart"
13 condition: eq( variables['Agent.OS'], 'Linux' )
14 displayName: Run Reportgenerator on Linux
15
16 - script: |
17 dotnet tool install dotnet-reportgenerator-globaltool --tool-path .
18 .\reportgenerator.exe "-reports:$(Build.SourcesDirectory)/TestResults/Coverage/coverage.opencover.xml" "-targetdir:coverage/Cobertura" "-reporttypes:Cobertura;HTMLInline;HTMLChart"
19 condition: eq( variables['Agent.OS'], 'Windows_NT' )
20 displayName: Run Reportgenerator on Windows
3. Finally, we run the Code Analysis and publish the results
1 - task: SonarCloudAnalyze@1
2 - task: PublishCodeCoverageResults@1
3 displayName: "Publish code coverage report"
4 inputs:
5 codeCoverageTool: "Cobertura"
6 summaryFileLocation: "$(Build.SourcesDirectory)/**/Cobertura.xml"
7 - task: SonarCloudPublish@1
8 inputs:
9 pollingTimeoutSec: "300"
Complete Pipeline:
1trigger: none
2pool:
3 vmImage: "ubuntu-latest"
4
5variables:
6 buildConfiguration: "Release"
7 buildSolution: "src/discografia.sln"
8 testProjects: "src/tests/**/*.csproj"
9 sonar.connection: "SonarCloud"
10 sonar.organization: "highonsoftware"
11 sonar.projectKey: "rcm-discografia"
12 sonar.projectName: "RCM Backend"
13
14steps:
15 - task: DotNetCoreCLI@2
16 name: "Restore"
17 inputs:
18 projects: $(buildSolution)
19 command: "restore"
20
21 - task: SonarCloudPrepare@1
22 inputs:
23 SonarCloud: $(sonar.connection)
24 organization: $(sonar.organization)
25 scannerMode: "MSBuild"
26 projectKey: $(sonar.projectKey)
27 projectName: $(sonar.projectName)
28 extraProperties: |
29 sonar.exclusions=**/obj/**,**/*.dll
30 sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/**/coverage.opencover.xml
31 sonar.cs.vstest.reportsPaths=$(Build.SourcesDirectory)/**/*.trx
32
33 - task: DotNetCoreCLI@2
34 name: "Build"
35 displayName: "dot net build $(buildConfiguration)"
36 inputs:
37 command: "build"
38 projects: $(buildSolution)
39 arguments: "--configuration $(buildConfiguration)"
40
41 - task: DotNetCoreCLI@2
42 name: "RunTests"
43 displayName: "dot net test $(buildConfiguration)"
44 inputs:
45 command: "test"
46 projects: $(testProjects)
47 arguments: "--configuration $(buildConfiguration) /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput=$(Build.SourcesDirectory)/TestResults/Coverage/ --logger trx"
48 publishTestResults: true
49
50 - script: |
51 dotnet tool install dotnet-reportgenerator-globaltool --tool-path .
52 ./reportgenerator "-reports:$(Build.SourcesDirectory)/TestResults/Coverage/coverage.opencover.xml" "-targetdir:coverage/Cobertura" "-reporttypes:Cobertura;HTMLInline;HTMLChart"
53 condition: eq( variables['Agent.OS'], 'Linux' )
54 displayName: Run Reportgenerator on Linux
55
56 - script: |
57 dotnet tool install dotnet-reportgenerator-globaltool --tool-path .
58 .\reportgenerator.exe "-reports:$(Build.SourcesDirectory)/TestResults/Coverage/coverage.opencover.xml" "-targetdir:coverage/Cobertura" "-reporttypes:Cobertura;HTMLInline;HTMLChart"
59 condition: eq( variables['Agent.OS'], 'Windows_NT' )
60 displayName: Run Reportgenerator on Windows
61
62 - task: SonarCloudAnalyze@1
63
64 - task: PublishCodeCoverageResults@1
65 displayName: "Publish code coverage report"
66 inputs:
67 codeCoverageTool: "Cobertura"
68 summaryFileLocation: "$(Build.SourcesDirectory)/**/Cobertura.xml"
69
70 - task: SonarCloudPublish@1
71 inputs:
72 pollingTimeoutSec: "300"
We have a pull request validation pipeline that is appropriately integrated with SonarCloud.
In a future post, we will see how to directly integrate the code analysis results into our PR.