Summary
Transition StyleCop Analyzers from OpenCover-based code coverage to coverlet for both CI and local development:
- Replace OpenCover + xunit.console invocations in build scripts with coverlet.
- Keep using ReportGenerator and Codecov, but feed them coverlet-generated Cobertura reports.
- Update the local coverage script to use coverlet instead of OpenCover.
- Document new local workflows (including a “focus on changed code” option) in
CONTRIBUTING.md.
The structure of this issue is intentionally detailed so it can be executed step-by-step and/or handed off to an automation agent later. :contentReference[oaicite:0]{index=0}
Background / current state
- Azure Pipelines uses the
build/build-and-test.yml template. It runs tests for each language version via build/test.yml and then merges OpenCover reports with ReportGenerator into a Cobertura report that is uploaded to Codecov using the CodecovUploader tool. :contentReference[oaicite:1]{index=1}
build/test.yml currently runs tests via xunit.console.x86.exe wrapped in OpenCover.Console.exe, writing OpenCover.StyleCopAnalyzers.CSharp{N}.xml files into build/OpenCover.Reports, then publishes those XMLs as artifacts for the coverage merge stage. :contentReference[oaicite:2]{index=2}
- The
Publish_Code_Coverage_* stage in build/build-and-test.yml:
- Downloads
coverageResults-cs* artifacts.
- Uses
ReportGenerator.exe from the ReportGenerator NuGet package to merge OpenCover.*.xml files and emit Cobertura.xml.
- Uses
CodecovUploader to upload the merged Cobertura report to Codecov. :contentReference[oaicite:3]{index=3}
- There is a local helper script
build/opencover-report.ps1 that:
- Builds the solution (unless
-NoBuild).
- Locates
OpenCover, ReportGenerator, and xunit.runner.console from .nuget\packages.config.
- Runs each test assembly (C# 6–13) under OpenCover, merging results into a single
OpenCover.StyleCopAnalyzers.xml.
- Generates an HTML report with ReportGenerator and prints the path to
OpenCover.Reports\index.htm. :contentReference[oaicite:4]{index=4}
.codecov.yml configures Codecov to:
- Disable project/patch status checks.
- Apply a
fixes mapping from build/StyleCop.Analyzers/ paths back to StyleCop.Analyzers/.
- Group coverage by
production and test flags.
- Use a
diff layout for comments. :contentReference[oaicite:5]{index=5}
CONTRIBUTING.md is minimal and currently does not describe any local code coverage workflows. :contentReference[oaicite:6]{index=6}
OpenCover is effectively archived and unmaintained, while coverlet is the recommended cross‑platform tool for .NET code coverage. Moving to coverlet should simplify tooling and reduce future maintenance.
Goals
- Replace OpenCover with coverlet for both CI and local coverage runs.
- Retain Codecov integration in CI, using a merged Cobertura report as the primary artifact.
- Provide a local coverage workflow that:
- Builds the repository.
- Runs tests under coverlet.
- Produces human-readable HTML coverage (via ReportGenerator).
- Add a “changed code” workflow for local use:
- Allow contributors to focus coverage review on files/lines changed in the current branch relative to
master (or main).
- This can be approximate (file- and line-level filtering) rather than a full “two-coverage-runs delta” implementation.
- Document the new workflows in
CONTRIBUTING.md.
Non-goals
- Changing the test framework (still xUnit) or the structure of test projects.
- Changing which branches Codecov tracks or how Codecov comments on PRs, beyond what’s required to keep it working with coverlet.
- Enforcing new coverage thresholds in CI (can be a follow-up).
High-level approach
- Introduce coverlet as the coverage engine (likely via
coverlet.console or coverlet.msbuild, depending on what integrates best with the existing xUnit console / .NET Framework + .NET 6 test mix).
- Update CI scripts:
- Replace the OpenCover invocation in
build/test.yml with coverlet, writing per-language Cobertura reports.
- Adjust the merge/upload stage in
build/build-and-test.yml to pick up coverlet’s Cobertura XML instead of OpenCover XML.
- Update
build/opencover-report.ps1:
- Either migrate it in-place (keeping the name but using coverlet) or introduce a new
build/coverage-report.ps1 and deprecate the old script.
- Add an optional “diff-only” mode to the local script that:
- Uses
git diff vs. the main branch to find changed files.
- Filters the ReportGenerator output to those files (and their uncovered lines) for focused reviews.
- Update
CONTRIBUTING.md with instructions for:
- Running full coverage locally.
- Running diff-focused coverage locally.
- How these relate to codecov.io in CI.
Detailed steps
1. Introduce coverlet and update dependencies
1.1. Decide on coverlet integration mode
- Options:
coverlet.console wrapping xunit.console.x86.exe (most similar to current OpenCover.Console pattern).
coverlet.msbuild + dotnet test for the .NET 6 test targets (requires more restructuring for .NET Framework runs).
- Given the current CI pattern and the existing
opencover-report.ps1 design, the simplest migration is likely:
- Use
coverlet.console with xUnit console for all frameworks.
1.2. Update .nuget/packages.config
- Add a new entry for the selected coverlet package (e.g.,
coverlet.console).
- Remove the
OpenCover package entry.
- Keep
ReportGenerator and CodecovUploader (unless we decide to change how we upload to Codecov).
- Keep
xunit.runner.console while we’re still using it as the test runner.
Implementation note: the CI scripts already parse .nuget\packages.config to locate package versions, so the new coverlet entry should follow the same pattern used for OpenCover. :contentReference[oaicite:7]{index=7}
2. Replace OpenCover with coverlet in Azure Pipelines
2.1. Update build/test.yml “Run tests” script
Current behavior (simplified):
- Resolve
OpenCover and xunit.runner.console from .nuget\packages.config.
- Compose the test assembly path for a given C# / framework version.
- Run:
&$opencover_console `
-register:Path32 `
-threshold:1 `
-oldStyle `
-returntargetcode `
-hideskipped:All `
-filter:"+[StyleCop*]*" `
-excludebyattribute:*.ExcludeFromCodeCoverage* `
-excludebyfile:*\*Designer.cs `
-output:"$report_folder\OpenCover.StyleCopAnalyzers.CSharp${{ parameters.LangVersion }}.xml" `
-target:"$xunit_runner_console_${{ parameters.FrameworkVersion }}" `
-targetargs:"$target_dll_csharp${{ parameters.LangVersion }} -noshadow -xml StyleCopAnalyzers.CSharp${{ parameters.LangVersion }}.xunit.xml"
…then publish build/OpenCover.Reports as coverageResults-cs{LangVersion}. ([GitHub]1)
Target behavior:
Work items:
2.2. Update the Publish_Code_Coverage_* stage in build/build-and-test.yml
Current behavior:
-
Downloads coverageResults-cs* artifacts containing OpenCover.*.xml files.
-
Runs ReportGenerator like:
&$report_generator `
-targetdir:$(Pipeline.Workspace)/coverageResults-final `
-reporttypes:Cobertura `
"-reports:$(Pipeline.Workspace)/coverageResults-*/OpenCover.*.xml"
-
Uploads coverageResults-final/Cobertura.xml to Codecov via CodecovUploader. ([GitHub]2)
Target behavior:
Work items:
3. Migrate the local build/opencover-report.ps1 script
Current behavior (simplified): ([GitHub]4)
- Parameters:
-Debug, -NoBuild, -NoReport, -AppVeyor, -Azure.
- Builds the solution (unless
-NoBuild).
- Resolves
OpenCover, ReportGenerator, xunit.runner.console from .nuget\packages.config.
- Runs each test assembly (C# 6–13) under OpenCover with merge-by-hash, producing a single
OpenCover.StyleCopAnalyzers.xml.
- Uses
ReportGenerator.exe to create HTML coverage in OpenCover.Reports and prints a friendly message.
Target behavior:
-
Replace OpenCover.Console.exe usage with coverlet:
-
Keep or refine the parameters:
-Debug / -Release mapping to configuration.
-NoBuild to skip the build.
-NoReport to skip HTML generation (for CI-like callers).
-
Rename output directories and files to something like:
build\coverage\coverage.cobertura.xml (primary Cobertura report).
build\coverage\index.htm (HTML root from ReportGenerator).
-
Consider renaming the script to coverage-report.ps1 and:
- Keep
opencover-report.ps1 as a thin wrapper that delegates to coverage-report.ps1 with a warning about deprecation, or
- Update the existing script in-place but update references in docs to call it “coverage report” rather than “OpenCover report”.
Work items:
4. Local “changed code” coverage workflow
Goal: provide a workflow for contributors to focus on coverage of code they’ve changed in their branch relative to master (or main).
Proposed design (script-level):
Extend the (renamed) coverage-report.ps1 script with options:
Example workflows (to be documented, see section 5):
Work items:
Note: Codecov already provides PR-level diff coverage in CI via its diff comment layout; this step is focused on giving contributors a similar view locally, before pushing. ([GitHub]3)
5. Update CONTRIBUTING.md with coverage workflows
Current CONTRIBUTING.md describes prerequisites and general guidance but not coverage workflows. ([GitHub]5)
Proposed additions:
Work items:
Acceptance criteria
Open questions
Please feel free to tweak any of the names (script names, parameter names, artifact folder names) to better align with existing conventions in the repo while implementing this.
::contentReference[oaicite:14]{index=14}
Summary
Transition StyleCop Analyzers from OpenCover-based code coverage to coverlet for both CI and local development:
CONTRIBUTING.md.The structure of this issue is intentionally detailed so it can be executed step-by-step and/or handed off to an automation agent later. :contentReference[oaicite:0]{index=0}
Background / current state
build/build-and-test.ymltemplate. It runs tests for each language version viabuild/test.ymland then merges OpenCover reports with ReportGenerator into a Cobertura report that is uploaded to Codecov using theCodecovUploadertool. :contentReference[oaicite:1]{index=1}build/test.ymlcurrently runs tests viaxunit.console.x86.exewrapped inOpenCover.Console.exe, writingOpenCover.StyleCopAnalyzers.CSharp{N}.xmlfiles intobuild/OpenCover.Reports, then publishes those XMLs as artifacts for the coverage merge stage. :contentReference[oaicite:2]{index=2}Publish_Code_Coverage_*stage inbuild/build-and-test.yml:coverageResults-cs*artifacts.ReportGenerator.exefrom theReportGeneratorNuGet package to mergeOpenCover.*.xmlfiles and emitCobertura.xml.CodecovUploaderto upload the merged Cobertura report to Codecov. :contentReference[oaicite:3]{index=3}build/opencover-report.ps1that:-NoBuild).OpenCover,ReportGenerator, andxunit.runner.consolefrom.nuget\packages.config.OpenCover.StyleCopAnalyzers.xml.OpenCover.Reports\index.htm. :contentReference[oaicite:4]{index=4}.codecov.ymlconfigures Codecov to:fixesmapping frombuild/StyleCop.Analyzers/paths back toStyleCop.Analyzers/.productionandtestflags.difflayout for comments. :contentReference[oaicite:5]{index=5}CONTRIBUTING.mdis minimal and currently does not describe any local code coverage workflows. :contentReference[oaicite:6]{index=6}OpenCover is effectively archived and unmaintained, while coverlet is the recommended cross‑platform tool for .NET code coverage. Moving to coverlet should simplify tooling and reduce future maintenance.
Goals
master(ormain).CONTRIBUTING.md.Non-goals
High-level approach
coverlet.consoleorcoverlet.msbuild, depending on what integrates best with the existing xUnit console / .NET Framework + .NET 6 test mix).build/test.ymlwith coverlet, writing per-language Cobertura reports.build/build-and-test.ymlto pick up coverlet’s Cobertura XML instead of OpenCover XML.build/opencover-report.ps1:build/coverage-report.ps1and deprecate the old script.git diffvs. the main branch to find changed files.CONTRIBUTING.mdwith instructions for:Detailed steps
1. Introduce coverlet and update dependencies
1.1. Decide on coverlet integration mode
coverlet.consolewrappingxunit.console.x86.exe(most similar to currentOpenCover.Consolepattern).coverlet.msbuild+dotnet testfor the .NET 6 test targets (requires more restructuring for .NET Framework runs).opencover-report.ps1design, the simplest migration is likely:coverlet.consolewith xUnit console for all frameworks.1.2. Update
.nuget/packages.configcoverlet.console).OpenCoverpackage entry.ReportGeneratorandCodecovUploader(unless we decide to change how we upload to Codecov).xunit.runner.consolewhile we’re still using it as the test runner.2. Replace OpenCover with coverlet in Azure Pipelines
2.1. Update
build/test.yml“Run tests” scriptCurrent behavior (simplified):
OpenCoverandxunit.runner.consolefrom.nuget\packages.config.…then publish
build/OpenCover.ReportsascoverageResults-cs{LangVersion}. ([GitHub]1)Target behavior:
Replace the
OpenCover.Console.execall with coverlet, e.g.:coverlet.console.exefrom.nuget\packages.config(similar to howOpenCover.Console.exeis located today).xunit.console.x86.exevia coverlet’s--targetand--targetargsoptions, generating Cobertura output for each C# version and configuration.build/coverlet/coverage.CSharp{LangVersion}.cobertura.xml(or similar), and publish that folder as thecoverageResults-cs{LangVersion}artifact instead ofOpenCover.Reports.Preserve:
+[StyleCop*]*, excluding*.ExcludeFromCodeCoverage*and*Designer.cs), adapted to coverlet’s syntax.*.xunit.xml) forPublishTestResults@2to consume, as done today.Work items:
build/test.ymlwith a coverlet invocation while keeping xUnit as the underlying runner.PublishTestResults@2.coverageResults-cs{LangVersion}as before, but containing Cobertura coverage files instead of OpenCover XML.2.2. Update the
Publish_Code_Coverage_*stage inbuild/build-and-test.ymlCurrent behavior:
Downloads
coverageResults-cs*artifacts containingOpenCover.*.xmlfiles.Runs ReportGenerator like:
Uploads
coverageResults-final/Cobertura.xmlto Codecov viaCodecovUploader. ([GitHub]2)Target behavior:
Keep ReportGenerator + CodecovUploader, but:
-reports:at coverlet’s Cobertura XML files (e.g.,coverageResults-*/coverage.*.cobertura.xml).-reporttypes:Coberturaso we still produce a singleCobertura.xmlfor Codecov.HtmlInline_AzurePipelines) for Azure Pipelines’ browsing UX, if desired.Work items:
build/build-and-test.ymlto consume coverlet Cobertura reports instead of OpenCover XMLs.Cobertura.xmlundercoverageResults-final) so the CodecovUploader invocation stays simple..codecov.ymlfixesconfiguration. ([GitHub]3)3. Migrate the local
build/opencover-report.ps1scriptCurrent behavior (simplified): ([GitHub]4)
-Debug,-NoBuild,-NoReport,-AppVeyor,-Azure.-NoBuild).OpenCover,ReportGenerator,xunit.runner.consolefrom.nuget\packages.config.OpenCover.StyleCopAnalyzers.xml.ReportGenerator.exeto create HTML coverage inOpenCover.Reportsand prints a friendly message.Target behavior:
Replace
OpenCover.Console.exeusage with coverlet:Resolve
coverlet.consolefrom.nuget\packages.config.Either:
Keep or refine the parameters:
-Debug/-Releasemapping to configuration.-NoBuildto skip the build.-NoReportto skip HTML generation (for CI-like callers).Rename output directories and files to something like:
build\coverage\coverage.cobertura.xml(primary Cobertura report).build\coverage\index.htm(HTML root from ReportGenerator).Consider renaming the script to
coverage-report.ps1and:opencover-report.ps1as a thin wrapper that delegates tocoverage-report.ps1with a warning about deprecation, orWork items:
Replace all references to
OpenCoverinbuild/opencover-report.ps1with coverlet equivalents.Update the report folder name(s) to reflect coverlet (e.g.,
coverageinstead ofOpenCover.Reports).Keep ReportGenerator integration but point it at coverlet Cobertura reports.
Validate that a local run:
4. Local “changed code” coverage workflow
Goal: provide a workflow for contributors to focus on coverage of code they’ve changed in their branch relative to
master(ormain).Proposed design (script-level):
Extend the (renamed)
coverage-report.ps1script with options:-DiffBase <branchOrCommit>:origin/master(ororigin/main, whichever matches the repo).git diff --name-only --diff-filter=AM <DiffBase>...HEADto find changed source files (e.g.,*.cs).-DiffOnlyor-ChangedFilesOnly:-filefiltersrestricted to the changed files (converted into appropriate patterns) and produce an additional output directory, e.g.,build\coverage-diff.TextSummaryorMarkdownSummary) to dump a quick summary of coverage for only changed files to the console.Example workflows (to be documented, see section 5):
Full coverage:
Diff-only coverage against
origin/master:Work items:
-DiffBase/-DiffOnlyparameters to the coverage script.git diffto determine the set of changed files relative to the base.filefilters(and/orclassfilters) to generate a coverage report limited to the changed files.5. Update
CONTRIBUTING.mdwith coverage workflowsCurrent
CONTRIBUTING.mddescribes prerequisites and general guidance but not coverage workflows. ([GitHub]5)Proposed additions:
Add a new “Code coverage” (or “Running tests and coverage”) section, including:
Prerequisites
init.ps1.Run all tests with coverage
Example commands, e.g.:
Expected artifacts:
build\coverage\Cobertura.xml).build\coverage\index.htm).Check coverage for only your changes
Example commands, e.g.:
Explain high-level behavior:
Relation to CI
Work items:
Add a “Code coverage” section to
CONTRIBUTING.mddescribing:Acceptance criteria
CI still passes on the primary branch and PRs.
Azure Pipelines:
Local script:
build/coverage-report.ps1(or updatedopencover-report.ps1) runs without requiring extra manual steps beyondinit.ps1.CONTRIBUTING.md:Open questions
Exact coverlet package & invocation
coverlet.consolefor all test frameworks, or split betweencoverlet.consolefor .NET Framework andcoverlet.msbuildfor .NET 6?Script naming / compatibility
build/opencover-report.ps1as the canonical entrypoint (despite the misleading name), or introducebuild/coverage-report.ps1and keepopencover-report.ps1as a thin compatibility shim?Diff coverage depth
TextDeltaSummary), which is more complex but more accurate?Please feel free to tweak any of the names (script names, parameter names, artifact folder names) to better align with existing conventions in the repo while implementing this.