Skip to content

Commit f322be6

Browse files
Speed up Code Coverage Jobs (#3907)
* PR pipeline runs will separate which tests they run: - Project runs will perform unit tests (i.e. MDS Unit and Functional suites). - Package runs will perform integration tests (i.e. MDS Manual suite). * Re-wrote the code coverage job to: - Use a temp dir to avoid cluttering up the pooled agents. - Combine all merging/converting into a few files as possible. - Use the modern features of the dotnet tools to avoid parallel jobs and loops. - Combined all MDS reports into a single upload, and same for AKV. * - Moved code coverage job to Linux and streamlined it even further. - Removed unnecessary parameters related to test and coverage publishing. * Replaced PowerShell with native script calls to ensure compatibility with the Linux agent. * Working around a dotnet CLI bug related to tool installation. * Trying a different workaround for the dotnet CLI bug. * Trying to fix artifact download pattern. * Removed unnecessary reportgenerator step. * Added Code Coverage configruation to only include driver code. We don't want coverage of test code. * Trying to get code coverage and symbols working for package builds. * Downloading NuGet and Symbols packages for test runs. * Updated pipelines to generate symbols with nuget. * Using DotNetCoreCli on Unix instead of MSBuild. * Fixed incorrect MDS NuGet package version in CI runs. * Abandoning running unit and integration tests in separate pipelines, since we can't get coverage details from Package reference builds. * Reverted changes to blame config. * Removed stale comments from PR pipelines. * Addressed Copilot feedback. --------- Co-authored-by: Malcolm Daigle <mdaigle@microsoft.com>
1 parent a68e00f commit f322be6

24 files changed

Lines changed: 187 additions & 281 deletions

build.proj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
<ProjectProperties>$(CommonProperties);AssemblyFileVersion=$(AssemblyFileVersion);TargetsWindows=$(TargetsWindows);TargetsUnix=$(TargetsUnix);</ProjectProperties>
3434
<TestProjectProperties>$(ProjectProperties);BuildForRelease=false;TargetNetCoreVersion=$(TargetNetCoreVersion);TargetNetFxVersion=$(TargetNetFxVersion)</TestProjectProperties>
3535
<ResultsDirectory Condition="$(ResultsDirectory) == ''">TestResults</ResultsDirectory>
36-
3736
<!--
3837
Path to a `dotnet` version like a x86 or any especific versions in addition to a default installed version.
3938
This property is empty by default to use the default path of the system's path variable.
@@ -54,7 +53,8 @@
5453

5554
<!-- Collect code coverage unless explicitly disabled -->
5655
<CollectCodeCoverage Condition="'$(CollectCodeCoverage)' == ''">true</CollectCodeCoverage>
57-
<CollectArgument Condition="'$(CollectCodeCoverage)' == 'true'">--collect "Code coverage"</CollectArgument>
56+
<CodeCoverageRunSettings>$(TestsPath)/tools/Microsoft.Data.SqlClient.TestUtilities/CodeCoverage.runsettings</CodeCoverageRunSettings>
57+
<CollectStatement Condition="'$(CollectCodeCoverage)' == 'true'">--collect "Code coverage" --settings "$(CodeCoverageRunSettings)"</CollectStatement>
5858

5959
<!-- Set up tests to fail after hangs and report via blame -->
6060
<BlameArgument>$(Blame)</BlameArgument>

eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ jobs:
112112
parameters:
113113
buildConfiguration: ${{ parameters.buildConfiguration }}
114114
displayName: 'Create MDS NuGet Package'
115-
generateSymbolsPackage: false
115+
generateSymbolsPackage: true
116116
nuspecPath: 'tools/specs/Microsoft.Data.SqlClient.nuspec'
117117
outputDirectory: $(packagePath)
118118
packageVersion: ${{ parameters.mdsPackageVersion }}
@@ -132,7 +132,7 @@ jobs:
132132
parameters:
133133
buildConfiguration: ${{ parameters.buildConfiguration }}
134134
displayName: 'Create AKV NuGet Package'
135-
generateSymbolsPackage: false
135+
generateSymbolsPackage: true
136136
installNuget: false
137137
nuspecPath: 'tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec'
138138
outputDirectory: $(packagePath)

eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml

Lines changed: 95 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,43 @@
44
# See the LICENSE file in the project root for more information. #
55
#################################################################################
66

7-
# This job processes code coverage reports generated during test runs,
8-
# merges them, generates code coverage reports, publishes them to the
9-
# pipeline, and uploads them to CodeCov.
7+
# This job processes code coverage reports generated during test runs, merges
8+
# them, generates code coverage reports, publishes them to the pipeline, and
9+
# uploads them to CodeCov.
1010

1111
parameters:
12-
12+
1313
# True to include debug steps.
1414
- name: debug
1515
type: boolean
1616
default: false
1717

18-
# The pool image to use.
19-
- name: image
20-
type: string
21-
22-
# The agent pool name.
23-
- name: pool
24-
type: string
25-
26-
# Array of target frameworks to process code coverage for:
27-
#
28-
# e.g. [net462, net8.0, net9.0]
29-
#
30-
- name: targetFrameworks
31-
type: object
32-
3318
# True to upload code coverage results to CodeCov.
3419
- name: upload
3520
type: boolean
3621

3722
jobs:
38-
- job: CodeCoverage
23+
- job: publish_code_coverage
3924
displayName: Publish Code Coverage
4025

4126
pool:
42-
name: ${{ parameters.pool }}
43-
${{ if eq(parameters.pool, 'Azure Pipelines') }}:
44-
vmImage: ${{ parameters.image }}
45-
${{ else }}:
46-
demands:
47-
- imageOverride -equals ${{ parameters.image }}
27+
name: Azure Pipelines
28+
vmImage: ubuntu-latest
4829

4930
variables:
50-
netFxDir: $(Build.SourcesDirectory)\coverageNetFx
51-
netCoreDir: $(Build.SourcesDirectory)\coverageNetCore
31+
# Use a temp directory that is cleaned up after each job runs. This helps
32+
# avoid disk space issues on pooled agents that may run many jobs before
33+
# being retired.
34+
- name: workingDir
35+
value: $(Agent.TempDirectory)/coverage
5236

5337
steps:
5438

5539
- ${{if eq(parameters.debug, true)}}:
56-
- pwsh: Get-Volume
40+
- script: df -h
5741
displayName: '[Debug] Show Disk Usage'
5842

59-
- pwsh: 'Get-ChildItem env: | Sort-Object Name'
43+
- script: env | sort
6044
displayName: '[Debug] List Environment Variables'
6145

6246
# Install the .NET SDK.
@@ -65,150 +49,103 @@ jobs:
6549
debug: ${{ parameters.debug }}
6650

6751
# Install additional dotnet tools.
68-
- pwsh: |
69-
dotnet tool install --global dotnet-coverage
70-
dotnet tool install --global dotnet-reportgenerator-globaltool
71-
displayName: Install dotnet tools
72-
73-
- pwsh: |
74-
Write-Host "Removing leftover coverage files from previous runs..."
75-
Remove-Item $(netFxDir) -Recurse -Force -ErrorAction SilentlyContinue
76-
Remove-Item $(netCoreDir) -Recurse -Force -ErrorAction SilentlyContinue
77-
78-
Write-Host "Removing leftover merged XML files from previous runs..."
79-
Remove-Item coverageNetFxXml -Recurse -Force -ErrorAction SilentlyContinue
80-
Remove-Item coverageNetCoreXml -Recurse -Force -ErrorAction SilentlyContinue
81-
82-
Write-Host "Removing leftover reports from previous runs..."
83-
Remove-Item coveragereportNetFx -Recurse -Force -ErrorAction SilentlyContinue
84-
Remove-Item coveragereportNetCore -Recurse -Force -ErrorAction SilentlyContinue
85-
Remove-Item coveragereportAddOns -Recurse -Force -ErrorAction SilentlyContinue
86-
87-
- ${{ each targetFramework in parameters.targetFrameworks }}:
88-
- task: DownloadPipelineArtifact@2
89-
displayName: 'Download Coverage Reports [${{ targetFramework }}]'
90-
inputs:
91-
itemPattern: '**\${{ targetFramework }}*'
92-
${{ if startsWith(targetFramework, 'net4') }}:
93-
targetPath: $(netFxDir)
94-
${{ else }}:
95-
targetPath: $(netCoreDir)
52+
#
53+
# We must work around a bug in the dotnet CLI that prevents tool installs
54+
# when multiple project/solution files are present in the working
55+
# directory:
56+
#
57+
# https://github.com/dotnet/sdk/issues/9623
58+
#
59+
# Our repo root (the default working directory for tasks) contains more
60+
# than one project file. However, we must also obey the NuGet.config in
61+
# the repo root to ensure that tools are installed from the correct feeds.
62+
# We accomplish this by copying the NuGet.config to a temp dir and then
63+
# instructing the dotnet CLI tasks to run from that directory. They will
64+
# still install the tools globally for subsequent tasks to use.
65+
#
66+
- script: cp "$(Build.SourcesDirectory)/NuGet.config" "$(Agent.TempDirectory)/"
67+
displayName: Copy NuGet.config for tool installs
68+
69+
- task: DotNetCoreCLI@2
70+
displayName: Install dotnet-coverage tool
71+
inputs:
72+
command: custom
73+
custom: tool
74+
workingDirectory: $(Agent.TempDirectory)
75+
arguments: install --global dotnet-coverage --version 18.3.2
9676

9777
- ${{if eq(parameters.debug, true)}}:
98-
- pwsh: Get-Volume
78+
- script: df -h
9979
displayName: '[Debug] Show Disk Usage'
10080

101-
- pwsh: Get-ChildItem $(netFxDir) -Recurse -File -Filter *.coverage
102-
displayName: '[Debug] List coverageNetFx files'
103-
104-
- pwsh: Get-ChildItem $(netCoreDir) -Recurse -File -Filter *.coverage
105-
displayName: '[Debug] List coverageNetCore files'
106-
107-
- pwsh: |
108-
function MergeFiles {
109-
param(
110-
[string]$InputDirectoryPath,
111-
[string]$OutputDirectoryName
112-
)
113-
114-
$files = Get-ChildItem $InputDirectoryPath -Recurse -File -Filter *.coverage
115-
116-
# echo $files
117-
mkdir $OutputDirectoryName
118-
$counter=0
119-
120-
$toProcess = @()
121-
122-
foreach ($file in $files) {
123-
$toProcess += @{
124-
File = $file.FullName
125-
OutputFile = "$OutputDirectoryName\$counter.coveragexml"
126-
}
127-
128-
$counter++
129-
}
130-
131-
$jobs = @()
132-
foreach ($file in $toProcess){
133-
$jobs += Start-ThreadJob -ScriptBlock {
134-
$params = $using:file
135-
& dotnet-coverage merge $($params.File) --output $($params.OutputFile) --output-format xml
136-
}
137-
}
138-
139-
Write-Host "Merging started..."
140-
Wait-Job -Job $jobs
141-
142-
foreach ($job in $jobs) {
143-
Receive-Job -Job $job -Wait -AutoRemoveJob
144-
}
145-
}
146-
147-
MergeFiles -InputDirectoryPath "$(netFxDir)" -OutputDirectoryName "coverageNetFxXml"
148-
MergeFiles -InputDirectoryPath "$(netCoreDir)" -OutputDirectoryName "coverageNetCoreXml"
149-
150-
Write-Host "Removing original coverage files..."
151-
Remove-Item $(netFxDir) -Recurse -Force -ErrorAction SilentlyContinue
152-
Remove-Item $(netCoreDir) -Recurse -Force -ErrorAction SilentlyContinue
153-
displayName: Convert coverage files to xml
81+
# Download all of the coverage reports from the test jobs.
82+
#
83+
# These artifacts contain the .coverage files generated during test runs,
84+
# along with a bunch of other files that we ignore. We only download the
85+
# .coverage files.
86+
#
87+
- task: DownloadPipelineArtifact@2
88+
displayName: Download Coverage Reports
89+
inputs:
90+
# All of our coverage report artifact names start with 'net'.
91+
itemPattern: '**/net*/**/*.coverage'
92+
targetPath: $(workingDir)/originals
15493

15594
- ${{if eq(parameters.debug, true)}}:
156-
- pwsh: Get-Volume
95+
- script: df -h
15796
displayName: '[Debug] Show Disk Usage'
15897

159-
- pwsh: |
160-
dir coverageNetFxXml\
161-
dir coverageNetCoreXml\
162-
displayName: '[Debug] List converted files'
163-
164-
- pwsh: |
165-
$jobs = @()
166-
$jobs += Start-ThreadJob -ScriptBlock {
167-
& reportgenerator "-reports:coverageNetFxXml\*.coveragexml" "-targetdir:coveragereportNetFx" "-reporttypes:Cobertura;" "-assemblyfilters:+microsoft.data.sqlclient.dll" "-sourcedirs:$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\netfx\src;$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\src" "-classfilters:+Microsoft.Data.*"
168-
}
98+
- script: ls -alFR "$(workingDir)/originals"
99+
displayName: '[Debug] List coverage files'
169100

170-
$jobs += Start-ThreadJob -ScriptBlock {
171-
& reportgenerator "-reports:coverageNetCoreXml\*.coveragexml" "-targetdir:coveragereportNetCore" "-reporttypes:Cobertura;" "-assemblyfilters:+microsoft.data.sqlclient.dll" "-sourcedirs:$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\netcore\src;$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\src" "-classfilters:+Microsoft.Data.*"
172-
}
173-
174-
$jobs += Start-ThreadJob -ScriptBlock {
175-
& reportgenerator "-reports:coverageNetCoreXml\*.coveragexml" "-targetdir:coveragereportAddOns" "-reporttypes:Cobertura;" "-assemblyfilters:+microsoft.data.sqlclient.alwaysencrypted.azurekeyvaultprovider.dll" "-sourcedirs:$(Build.SourcesDirectory)\src\Microsoft.Data.SqlClient\add-ons\AzureKeyVaultProvider" "-classfilters:+Microsoft.Data.*"
176-
}
177-
178-
Write-Host "Running ReportGenerator..."
179-
Wait-Job -Job $jobs
180-
181-
foreach ($job in $jobs) {
182-
Receive-Job -Job $job -Wait -AutoRemoveJob
183-
}
184-
185-
Write-Host "Removing merged XML files..."
186-
Remove-Item coverageNetFxXml -Recurse -Force -ErrorAction SilentlyContinue
187-
Remove-Item coverageNetCoreXml -Recurse -Force -ErrorAction SilentlyContinue
188-
displayName: Run ReportGenerator
101+
# Merge them all into a single Cobertura XML file.
102+
- script: >-
103+
dotnet-coverage merge "$(workingDir)/originals/**/*.coverage"
104+
--output "$(workingDir)/merge/Cobertura.xml"
105+
--output-format cobertura
106+
--log-file "$(workingDir)/merge/merge.log"
107+
--log-level Verbose
108+
displayName: Merge coverage files to Cobertura XML
189109
190110
- ${{if eq(parameters.debug, true)}}:
191-
- pwsh: Get-Volume
111+
- script: df -h
192112
displayName: '[Debug] Show Disk Usage'
193113

114+
# Publish the Cobertura XML coverage file as a pipeline artifact for
115+
# debugging purposes.
116+
- task: PublishPipelineArtifact@1
117+
displayName: Publish Cobertura XML Artifact
118+
inputs:
119+
targetPath: $(workingDir)/merge
120+
artifact: Cobertura Merge Results
121+
122+
# Publish the Cobertura reports to the pipeline to be viewed in the Azure
123+
# DevOps pipeline run UI.
194124
- task: PublishCodeCoverageResults@2
195125
displayName: Publish code coverage results
196126
inputs:
197-
summaryFileLocation: '*\Cobertura.xml'
127+
summaryFileLocation: $(workingDir)/merge/Cobertura.xml
198128

129+
# Publish the Cobertura reports to CodeCov, if desired.
199130
- ${{if eq(parameters.upload, true)}}:
200-
- pwsh: |
201-
#download Codecov CLI
202-
$ProgressPreference = 'SilentlyContinue'
203-
Invoke-WebRequest -Uri https://cli.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe
204-
205-
./codecov --verbose upload-process --fail-on-error -t $(CODECOV_TOKEN) -f "coveragereportNetFx\Cobertura.xml" -F netfx
206-
./codecov --verbose upload-process --fail-on-error -t $(CODECOV_TOKEN) -f "coveragereportNetCore\Cobertura.xml" -F netcore
207-
./codecov --verbose upload-process --fail-on-error -t $(CODECOV_TOKEN) -f "coveragereportAddOns\Cobertura.xml" -F addons
131+
# Download the CodeCov CLI
132+
- script: |
133+
curl -o "$(workingDir)/codecov" https://cli.codecov.io/latest/linux/codecov
134+
chmod +x "$(workingDir)/codecov"
135+
displayName: Download CodeCov CLI
136+
137+
# Upload the report.
138+
#
139+
# We use the pipeline name as the "flag" to help distinguish reports
140+
# uploaded from different pipelines.
141+
#
142+
- script: >-
143+
$(workingDir)/codecov
144+
--verbose
145+
upload-process
146+
--fail-on-error
147+
-t $(CODECOV_TOKEN)
148+
--disable-search
149+
-f "$(workingDir)/merge/Cobertura.xml"
150+
-F $(Build.DefinitionName)
208151
displayName: Upload to CodeCov
209-
210-
- pwsh: |
211-
Write-Host "Removing reports..."
212-
Remove-Item coveragereportNetFx -Recurse -Force -ErrorAction SilentlyContinue
213-
Remove-Item coveragereportNetCore -Recurse -Force -ErrorAction SilentlyContinue
214-
Remove-Item coveragereportAddOns -Recurse -Force -ErrorAction SilentlyContinue

eng/pipelines/common/templates/jobs/ci-run-tests-job.yml

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,6 @@ parameters:
8686
type: stepList
8787
default: []
8888

89-
- name: publishTestResults
90-
type: boolean
91-
default: false
92-
9389
- name: referenceType
9490
type: string
9591
values:
@@ -327,10 +323,9 @@ jobs:
327323
abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }}
328324
mdsPackageVersion: ${{ parameters.mdsPackageVersion }}
329325

330-
- ${{ if eq(parameters.publishTestResults, true) }}:
331-
- template: /eng/pipelines/common/templates/steps/publish-test-results-step.yml@self
332-
parameters:
333-
debug: ${{ parameters.debug }}
334-
targetFramework: ${{ parameters.targetFramework }}
335-
operatingSystem: ${{ parameters.operatingSystem }}
336-
buildConfiguration: ${{ parameters.buildConfiguration }}
326+
- template: /eng/pipelines/common/templates/steps/publish-test-results-step.yml@self
327+
parameters:
328+
debug: ${{ parameters.debug }}
329+
targetFramework: ${{ parameters.targetFramework }}
330+
operatingSystem: ${{ parameters.operatingSystem }}
331+
buildConfiguration: ${{ parameters.buildConfiguration }}

eng/pipelines/common/templates/stages/ci-run-tests-stage.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,6 @@ stages:
9393
targetFramework: ${{ targetFramework }}
9494
netcoreVersionTestUtils: ${{config.value.netcoreVersionTestUtils }}
9595
testSet: ${{ testSet }}
96-
${{ each codeCoveTF in config.value.codeCovTargetFrameworks }}:
97-
${{ if eq(codeCoveTF, targetFramework) }}:
98-
publishTestResults: true
9996
configSqlFor: ${{ config.value.configSqlFor }}
10097
operatingSystem: ${{ config.value.operatingSystem }}
10198
isArm64: ${{ eq(config.value.isArm64, 'true') }}
@@ -130,9 +127,6 @@ stages:
130127
targetFramework: ${{ targetFramework }}
131128
netcoreVersionTestUtils: ${{config.value.netcoreVersionTestUtils }}
132129
testSet: ${{ testSet }}
133-
${{ each codeCoveTF in config.value.codeCovTargetFrameworks }}:
134-
${{ if eq(codeCoveTF, targetFramework) }}:
135-
publishTestResults: true
136130
configSqlFor: ${{ config.value.configSqlFor }}
137131
operatingSystem: ${{ config.value.operatingSystem }}
138132
isArm64: ${{ eq(config.value.isArm64, 'true') }}

0 commit comments

Comments
 (0)