diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml index 427b1e401..fee965db9 100644 --- a/.gitea/workflows/docker-build.yml +++ b/.gitea/workflows/docker-build.yml @@ -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) diff --git a/.gitignore b/.gitignore index f44e9cb5a..55bc3d56c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ *.*~ project.lock.json .DS_Store +# Code-coverage output (dotnet test --results-directory ./coverage, ersatztv#15) +/coverage/ *.pyc .worktrees/ diff --git a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj index bdc224593..e20aaa355 100644 --- a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj +++ b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj @@ -8,6 +8,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/ErsatzTV.Tests/ErsatzTV.Tests.csproj b/ErsatzTV.Tests/ErsatzTV.Tests.csproj index 76c49cf5a..8db215ef5 100644 --- a/ErsatzTV.Tests/ErsatzTV.Tests.csproj +++ b/ErsatzTV.Tests/ErsatzTV.Tests.csproj @@ -11,6 +11,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/coverlet.runsettings b/coverlet.runsettings new file mode 100644 index 000000000..1c3d08a7c --- /dev/null +++ b/coverlet.runsettings @@ -0,0 +1,24 @@ + + + + + + + + **/Migrations/*.cs + GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute + [*]*.Migrations.* + true + + + + + diff --git a/docs/ci-cd.md b/docs/ci-cd.md index 2e0e233b0..d47d87b64 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -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`.