ci(coverage): collect code coverage and publish a summary (refs #15) #370
@@ -101,7 +101,40 @@ jobs:
|
||||
run: dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Test
|
||||
run: dotnet test --configuration Release --no-build --blame-hang-timeout "2m" --verbosity normal
|
||||
run: >-
|
||||
dotnet test --configuration Release --no-build --blame-hang-timeout "2m" --verbosity normal
|
||||
--collect:"XPlat Code Coverage" --settings coverlet.runsettings --results-directory ./coverage
|
||||
|
||||
# Coverage reporting (ersatztv#15 scope item 4): coverlet.collector emits a Cobertura report
|
||||
# per test project (via --collect above); ReportGenerator merges them into a human-readable
|
||||
# summary printed to the log and the job step summary. No floor is enforced yet ("decide on a
|
||||
# floor later"), so this step is purely informational — continue-on-error keeps a missing
|
||||
# report or a transient tool-install failure from ever blocking a build.
|
||||
- name: Coverage summary
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s globstar nullglob
|
||||
reports=(coverage/**/coverage.cobertura.xml)
|
||||
if [ ${#reports[@]} -eq 0 ]; then
|
||||
echo "No coverage reports found under ./coverage -- skipping summary."
|
||||
exit 0
|
||||
fi
|
||||
echo "Found ${#reports[@]} coverage report(s)."
|
||||
# `update` is install-or-update (idempotent, unlike `install` which errors if the tool
|
||||
# is already present under set -e); pinned for reproducible summary output.
|
||||
dotnet tool update --global dotnet-reportgenerator-globaltool --version 5.5.10 >/dev/null
|
||||
export PATH="$PATH:$HOME/.dotnet/tools"
|
||||
reportgenerator \
|
||||
"-reports:coverage/**/coverage.cobertura.xml" \
|
||||
"-targetdir:coverage/report" \
|
||||
"-reporttypes:TextSummary;MarkdownSummaryGithub"
|
||||
echo "::group::Coverage summary"
|
||||
cat coverage/report/Summary.txt
|
||||
echo "::endgroup::"
|
||||
if [ -n "${GITHUB_STEP_SUMMARY:-}" ] && [ -f coverage/report/SummaryGithub.md ]; then
|
||||
cat coverage/report/SummaryGithub.md >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
migrations:
|
||||
name: EF migration integrity (SQLite + MySql)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
*.*~
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
# Code-coverage output (dotnet test --results-directory ./coverage, ersatztv#15)
|
||||
/coverage/
|
||||
*.pyc
|
||||
.worktrees/
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliWrap" />
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="LanguageExt.Core" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="NUnit" />
|
||||
<PackageReference Include="NUnit.Analyzers">
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Coverage collector config (ersatztv#15). Passed to dotnet test via
|
||||
"settings coverlet.runsettings" alongside collect:"XPlat Code Coverage".
|
||||
The two EF Core migration folders are ~2.59M lines of generated snapshot code
|
||||
(vs ~200k lines of authored code); instrumenting them balloons coverlet memory
|
||||
and OOM killed the shared Build and test job (exit 137). Excluding generated
|
||||
migration code bounds memory and makes the reported percentage reflect authored
|
||||
code. Keep this comment free of double hyphens (invalid in XML comments).
|
||||
-->
|
||||
<RunSettings>
|
||||
<DataCollectionRunSettings>
|
||||
<DataCollectors>
|
||||
<DataCollector friendlyName="XPlat Code Coverage">
|
||||
<Configuration>
|
||||
<ExcludeByFile>**/Migrations/*.cs</ExcludeByFile>
|
||||
<ExcludeByAttribute>GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute</ExcludeByAttribute>
|
||||
<Exclude>[*]*.Migrations.*</Exclude>
|
||||
<SkipAutoProps>true</SkipAutoProps>
|
||||
</Configuration>
|
||||
</DataCollector>
|
||||
</DataCollectors>
|
||||
</DataCollectionRunSettings>
|
||||
</RunSettings>
|
||||
@@ -95,6 +95,18 @@ long builds (observed 31 min) stalling every PR run.
|
||||
Docker build) → `dotnet build -c Release` → `dotnet test -c Release --no-build`. Gates
|
||||
the image build.
|
||||
|
||||
- **Code coverage** (ersatztv#15): `dotnet test` runs with `--collect:"XPlat Code Coverage"
|
||||
--settings coverlet.runsettings --results-directory ./coverage`, so `coverlet.collector`
|
||||
(referenced by every `*.Tests` project) emits a Cobertura report per project. A follow-up
|
||||
**Coverage summary** step merges them with ReportGenerator (`TextSummary` to the log,
|
||||
`MarkdownSummaryGithub` to the job step summary). No floor is enforced yet ("decide on a
|
||||
floor later" — #15); the step is `continue-on-error: true`, so a missing report or a
|
||||
transient tool install never blocks a build.
|
||||
- **`coverlet.runsettings` excludes generated EF migration code** (`**/Migrations/*.cs`,
|
||||
~2.59M generated lines vs ~200k authored). Instrumenting it OOM-killed the shared `test`
|
||||
job (exit 137); excluding it cuts the instrumented surface ~126× (2.5M→20k coverable
|
||||
lines in the whole-solution `Architecture.Tests` process) and makes the percentage reflect
|
||||
authored code.
|
||||
- **Shallow checkout**: `fetch-depth: 1` (ersatztv#190) — this job never runs `git
|
||||
describe`/`git log`, only `build` needs full history/tags for version computation, so
|
||||
`test` and `migrations` both check out shallow. `build`'s checkout stays `fetch-depth: 0`.
|
||||
|
||||
Reference in New Issue
Block a user