PR Pipeline - Code Coverage and Triggers#4360
Conversation
# Conflicts: # eng/pipelines/pr/jobs/test-buildproj-job.yml # eng/pipelines/pr/stages/test-stages.yml
… all tests are first.
There was a problem hiding this comment.
Pull request overview
This PR operationalizes the dedicated Azure DevOps PR validation pipeline by adding GitHub PR triggers, publishing per-job test results as pipeline artifacts, and introducing a post-test stage to merge and publish code coverage (including upload to CodeCov).
Changes:
- Add GitHub PR triggers + path filters to the PR pipeline YAML.
- Publish TRX results (and artifacts containing coverage outputs) from each test job to a consistent pipeline artifact namespace.
- Add a coverage-merge stage that downloads
.coveragefiles, merges to Cobertura, publishes coverage to ADO, and uploads to CodeCov.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| eng/pipelines/pr/variables/pr-variables.yml | Adds variables for CodeCov token and the shared test-results artifact name. |
| eng/pipelines/pr/steps/publish-test-results-step.yml | New reusable step to publish TRX results and upload the job’s test-results folder as an artifact. |
| eng/pipelines/pr/stages/test-stages.yml | Threads testResultsArtifactName through all PR test jobs. |
| eng/pipelines/pr/stages/collect-coverage-stage.yml | New stage to download coverage artifacts, merge to Cobertura, publish to ADO, and upload to CodeCov. |
| eng/pipelines/pr/pr-pipeline.yml | Adds PR triggers/path filters, centralizes platform matrix as a parameter, and wires in the coverage-collection stage. |
| eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml | Wires test-results publishing into SqlClient manual test jobs. |
| eng/pipelines/pr/jobs/test-buildproj-job.yml | Wires test-results folder routing + publishing into build.proj-driven test jobs. |
| - task: PublishTestResults@2 | ||
| displayName: 'Publish Test Results' | ||
| inputs: | ||
| buildConfiguration: ${{ parameters.buildConfiguration }} | ||
| testResultsFiles: | | ||
| ${{ parameters.testResultsPath }}/*.trx | ||
| ${{ parameters.testResultsPath }}/**/*.coverage | ||
| testRunTitle: "${{ parameters.platformDisplayName }}_${{ parameters.testDisplayName }}" | ||
| condition: succeededOrFailed() |
| -t:Test${{ parameters.packageShortName }}${{ parameters.testProject }} | ||
| -p:Configuration=${{ parameters.buildConfiguration }} | ||
| -p:BuildNumber='$(Build.BuildNumber)' | ||
| -p:BuildSuffix='${{ parameters.buildSuffix }}' | ||
| -p:TestFramework=${{ parameters.platformDotnet }} | ||
| -p:TestResultsFolderPath=${{ variables.testResultsPath }} | ||
|
|
There was a problem hiding this comment.
Indeed - I suspect $(REPO_ROOT) is the culprit here, unless it is populated by compile-time values/variables too (i.e. can you use ${{ variables.REPO_ROOT }} above?).
There was a problem hiding this comment.
🤔 I don't actually see the problem here. $() formatted variables are expanded at runtime, while ${{ }} formatted ones are evaluated at compile time.
So, at compile time, testResultsPath gets evaluated as eg, $(REPO_ROOT)/test_results/windows_net462_abstractions. Then at step runtime, it's further expanded to D:\a\_work\1\s/test_results/windows_net462_abstractions.
You can see it behaving correctly in any of the test runs: eg https://sqlclientdrivers.visualstudio.com/public/_build/results?buildId=156095&view=logs&j=9aaebe84-0bb8-5049-010a-f5d7cc3b7829&t=671e3b01-ce1f-5040-8f31-c4248ac036e2&l=12
| - template: /eng/pipelines/pr/steps/publish-test-results-step.yml@self | ||
| parameters: | ||
| buildConfiguration: ${{ parameters.buildConfiguration }} | ||
| platformDisplayName: ${{ parameters.platformDisplayName }} | ||
| testDisplayName: ${{ parameters.testDisplayName }} | ||
| testResultsArtifactName: ${{ parameters.testResultsArtifactName }} | ||
| testResultsPath: ${{ variables.testResultsPath }} |
There was a problem hiding this comment.
This actually isn't an issue, if you look at the test logs https://dev.azure.com/SqlClientDrivers/ADO.Net/_build/results?buildId=155942&view=logs&j=9aaebe84-0bb8-5049-010a-f5d7cc3b7829&t=6f7d4681-2ac1-5fe1-83e3-59d9d33c8750
I'm not sure there's a concept of "runtime variables" as is being asserted here. There are values that are only available at runtime, but as long as those are referenced by $() they will remain $() until they are used at runtime, where they are further expanded to their value.
There was a problem hiding this comment.
It's worth noting that if this wasn't the case, we'd never be able to have $(Password) in our connection strings and expect it to be expanded when we write it to the config file.
| -p:TestFramework=${{ parameters.platformDotnet }} | ||
| -p:TestSet=${{ parameters.testSet }} | ||
|
|
||
| # Publish test results | ||
| - template: /eng/pipelines/pr/steps/publish-test-results-step.yml@self | ||
| parameters: | ||
| buildConfiguration: ${{ parameters.buildConfiguration }} | ||
| platformDisplayName: ${{ parameters.platformDisplayName }} | ||
| testDisplayName: "sqlclient_manual_${{ parameters.configDisplayName}}_${{ parameters.testSet }}" | ||
| testResultsArtifactName: ${{ parameters.testResultsArtifactName }} | ||
| testResultsPath: ${{ variables.testResultsPath }} |
There was a problem hiding this comment.
That and the test result folder path isn't being passed into the build.proj step
| - task: CopyFiles@2 | ||
| displayName: Copy nuget.config for tool installs | ||
| inputs: | ||
| contents: "$(REPO_ROOT)/nuget.config" | ||
| targetFolder: $(Agent.TempDirectory) |
There was a problem hiding this comment.
Ah yes because copying 0 files is success 😑
There was a problem hiding this comment.
I wanted to avoid using a direct script here because we have tasks for these things ... but ok whatever.
| - task: PublishCodeCoverageResults@2 | ||
| displayName: Publish coverage results | ||
| inputs: | ||
| summaryFileLocation: "${{ variables.workingDir }}/merge/cobertura.xml" |
There was a problem hiding this comment.
Still not an issue....
| - script: | | ||
| curl -o "${{ variables.workingDir }}/codecov" https://cli.codecov.io/latest/linux/codecov | ||
| chmod +x "${{ variables.workingDir }}/codecov" | ||
| displayName: Download CodeCov CLI |
There was a problem hiding this comment.
It works just fine
| - script: >- | ||
| "${{ variables.workingDir }}/codecov" | ||
| --verbose | ||
| upload-process | ||
| --fail-on-error | ||
| --git-service github | ||
| --slug dotnet/SqlClient | ||
| -f "${{ variables.workingDir }}/merge/cobertura.xml" | ||
| -F $(Build.DefinitionName) | ||
| displayName: Upload coverage to CodeCov | ||
| env: | ||
| CODECOV_TOKEN: ${{ parameters.codeCovApiToken }} |
There was a problem hiding this comment.
I can't believe I'm misunderstanding how this works that badly
paulmedynski
left a comment
There was a problem hiding this comment.
Looking good! A few comments/suggestions.. I thumbs-upped the bot comments I agree with, or replied to them. I also closed a few that I disagreed with.
| contents: "$(REPO_ROOT)/nuget.config" | ||
| targetFolder: $(Agent.TempDirectory) | ||
|
|
||
| - task: DotNetCoreCLI@2 |
There was a problem hiding this comment.
This tool is controlled by our global tools config, so I think this and the above task can be replaced with dotnet tool install. I suspect you copied most of this file from the old PR/CI pipeline, and it's likely just stale.
There was a problem hiding this comment.
You're right that I copy/pasted it (with revisions) from the old pipeline, but I also confirmed that DotNetCLI task doesn't support the tool command for whatever reason.
The above might be unrelated to your actual comment here.
I think as long as we're doing everything from the temp directory, we need to have dotnet-coverage installed globally so we can call it as a command outside the repository.
I guess alternatively, we could do everything in a folder inside the repository, but I don't know if the repository directory is cleaned up after a job runs.
| - task: PublishTestResults@2 | ||
| displayName: 'Publish Test Results' | ||
| inputs: | ||
| buildConfiguration: ${{ parameters.buildConfiguration }} | ||
| testResultsFiles: | | ||
| ${{ parameters.testResultsPath }}/*.trx | ||
| ${{ parameters.testResultsPath }}/**/*.coverage | ||
| testRunTitle: "${{ parameters.platformDisplayName }}_${{ parameters.testDisplayName }}" | ||
| condition: succeededOrFailed() |
| # | ||
| # AzureKeyVaultTenantId | ||
| # AzureKeyVaultUrl | ||
| # CodeCovApiToken |
There was a problem hiding this comment.
Is this a secret we shouldn't be exposing to forked PRs? If yes, then maybe we should do everything except upload to CodeCov in this pipeline, and do CodeCov stuff in the CI pipeline.
Do any of these other variables contain secrets?
There was a problem hiding this comment.
:/ I'm not sure how secrety it is... I think API keys are usually considered secrets, so I'd agree with your suggestion to remove uploading to CodeCov. But I also don't know how critical uploading results to codecov are for our PR validation, so I'd be hesitant to break it as long as it's not officially classified as a secret.
None of the other variables are considered secrets, they either rely on managed identity or have the generated secret injected into them at runtime.
| # | ||
| # It maps to the "sqlclient-pr" pipeline in the Public ADO project: | ||
| # https://sqlclientdrivers.visualstudio.com/public/_build?definitionId=2281 | ||
| # And the "pr-pipeline" pipeline in the internal ADO project: |
There was a problem hiding this comment.
I think we want the pipelines to have the same name in both projects. sqlclient-pr seems correct. I would also name this file sqlclient-pr-pipeline.yml to match the pipeline name. I've been doing that for the other new pipelines.
There was a problem hiding this comment.
My thought was it didn't make sense to name it sqlclient-* when you're in the sqlclient project. But in the public project we're sharing space with other products, so we'd want to differentiate.
But, ok fine, I don't care too much about the scheme as long as its systematic and reasonable.
| - .azuredevops/* | ||
| - .config/* | ||
| - doc/* | ||
| - eng/pipelines/* |
There was a problem hiding this comment.
Hmm.. is there a narrower include path we could use, and then avoid the exclusions below?
- eng/pipelines/common/*
- eng/pipelines/pr/*
There was a problem hiding this comment.
I guess we could just include common and pr 🤔
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4360 +/- ##
==========================================
+ Coverage 65.69% 66.33% +0.64%
==========================================
Files 285 285
Lines 43311 66481 +23170
==========================================
+ Hits 28453 44101 +15648
- Misses 14858 22380 +7522
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@paulmedynski I disagree with most of the copilot comments you agreed with, so ... one of us is misunderstanding something 😅 Regarding the CodeCov stuff, I think I need an adult to tell me how they'd like me to address it. |
| steps: | ||
| # Publish the test results (trx files) to the pipeline | ||
| - task: PublishTestResults@2 | ||
| displayName: 'Publish Test Results' | ||
| inputs: | ||
| buildConfiguration: ${{ parameters.buildConfiguration }} | ||
| mergeTestResults: true | ||
| testResultsFiles: | | ||
| ${{ parameters.testResultsPath }}/*.trx | ||
| ${{ parameters.testResultsPath }}/**/*.coverage | ||
| testResultsFormat: VSTest | ||
| testRunTitle: "${{ parameters.platformDisplayName }}_${{ parameters.testDisplayName }}" | ||
| condition: succeededOrFailed() | ||
|
|
||
| # Publish the test results as artifacts for the pipeline | ||
| - task: PublishPipelineArtifact@1 | ||
| displayName: 'Publish Test Artifacts' | ||
| inputs: | ||
| artifact: ${{ parameters.testResultsArtifactName }}_$(System.JobId) | ||
| targetPath: ${{ parameters.testResultsPath }} | ||
| condition: succeededOrFailed() | ||
|
|
| - script: | | ||
| curl -o "${{ variables.workingDir }}/codecov" https://cli.codecov.io/latest/linux/codecov | ||
| chmod +x "${{ variables.workingDir }}/codecov" | ||
| displayName: Download CodeCov CLI |
| - script: >- | ||
| "${{ variables.workingDir }}/codecov" | ||
| --verbose | ||
| upload-process | ||
| --fail-on-error | ||
| --git-service github | ||
| --slug dotnet/SqlClient | ||
| -f "${{ variables.workingDir }}/merge/cobertura.xml" | ||
| -F $(Build.DefinitionName) | ||
| displayName: Upload coverage to CodeCov | ||
| env: | ||
| CODECOV_TOKEN: ${{ parameters.codeCovApiToken }} |
Description
This is the last PR before operationalizing the PR pipeline.
Testing
https://dev.azure.com/SqlClientDrivers/ADO.Net/_build/results?buildId=156200&view=results
https://sqlclientdrivers.visualstudio.com/public/_build/results?buildId=156192&view=results
https://dev.azure.com/SqlClientDrivers/ADO.Net/_build/results?buildId=156112&view=results