From b99a8e9c1c572999eca8ca312abe5170bd9915a3 Mon Sep 17 00:00:00 2001 From: jogibear9988 Date: Wed, 23 Sep 2026 11:07:14 +0200 Subject: [PATCH] Show merged code coverage alongside homepage test results --- .github/scripts/render-homepage-tests.py | 36 +++++++++++++++--- .github/scripts/test-homepage-tests.py | 47 +++++++++++++++++++++++- .github/workflows/pages.yml | 22 +++++++++-- README.md | 2 +- docs/README.md | 9 ++++- docs/_src/home.html | 4 +- docs/assets/site.css | 8 +++- docs/guide/altering-tables.html | 2 +- docs/guide/api-map.html | 2 +- docs/guide/auto-reversing.html | 2 +- docs/guide/cli.html | 2 +- docs/guide/columns.html | 2 +- docs/guide/conditional.html | 2 +- docs/guide/configuration.html | 2 +- docs/guide/connections.html | 2 +- docs/guide/constraints.html | 2 +- docs/guide/contributing.html | 2 +- docs/guide/creating-tables.html | 2 +- docs/guide/data.html | 2 +- docs/guide/defaults-collations.html | 2 +- docs/guide/dependency-injection.html | 2 +- docs/guide/extensions.html | 2 +- docs/guide/faq.html | 2 +- docs/guide/foreign-keys.html | 2 +- docs/guide/index.html | 2 +- docs/guide/indexes.html | 2 +- docs/guide/installation.html | 2 +- docs/guide/maintenance.html | 2 +- docs/guide/mysql.html | 2 +- docs/guide/oracle.html | 2 +- docs/guide/other-providers.html | 2 +- docs/guide/postgresql.html | 2 +- docs/guide/preview.html | 2 +- docs/guide/profiles.html | 2 +- docs/guide/providers.html | 2 +- docs/guide/quick-start.html | 2 +- docs/guide/runners.html | 2 +- docs/guide/schema.html | 2 +- docs/guide/sql-server.html | 2 +- docs/guide/sql.html | 2 +- docs/guide/sqlite.html | 2 +- docs/guide/tags.html | 2 +- docs/guide/testing.html | 2 +- docs/guide/transactions.html | 2 +- docs/guide/upgrading.html | 2 +- docs/guide/versioning.html | 2 +- docs/index.html | 6 +-- 47 files changed, 153 insertions(+), 59 deletions(-) diff --git a/.github/scripts/render-homepage-tests.py b/.github/scripts/render-homepage-tests.py index f3e4ea59..7f768dc3 100644 --- a/.github/scripts/render-homepage-tests.py +++ b/.github/scripts/render-homepage-tests.py @@ -9,7 +9,29 @@ "MariaDB", "Firebird", "Db2", "Informix", "Sybase", "Hana"} -def render(results, run): +def render_coverage(report, run): + if report is None or not pathlib.Path(report).is_file(): + return '

Code coverage unavailable for this run: no merged coverage artifact is available.

' + root = ET.parse(report).getroot() + if root.tag != 'coverage': + raise ValueError('Expected a Cobertura coverage report') + items = [] + for metric, label in (("lines", "line coverage"), ("branches", "branch coverage")): + covered = int(root.attrib[f'{metric}-covered']) + total = int(root.attrib[f'{metric}-valid']) + if not 0 <= covered <= total or (metric == 'lines' and total == 0): + raise ValueError(f'Invalid {metric} coverage') + rate = f'{100 * covered / total:.2f}%' if total else 'N/A' + items.append(f'
  • {rate}{label}{covered:,} / {total:,} covered
  • ') + url = html.escape(run.get('coverage_url', run['html_url']), quote=True) + return ('

    Code coverage

    Production assemblies, merged across unit and database suites; ' + 'shared code is counted once. ' + f'Download the full HTML coverage report ↗ ' + '(code-coverage artifact; open index.html).

    ') + + +def render(results, run, coverage=None): suites = {} for path in pathlib.Path(results).rglob("*.trx"): name = path.stem @@ -27,22 +49,24 @@ def render(results, run): sha = html.escape(run["head_sha"][:7]) date = html.escape(run["updated_at"]) conclusion = html.escape(run["conclusion"] or "unknown") - source = f'

    CI run · {sha} · {date} · workflow: {conclusion}. Latest completed master run at deployment time.

    ' + source = f'

    CI run · {sha} · {date} · workflow: {conclusion}. Latest completed master run at deployment time.

    ' + coverage_html = render_coverage(coverage, run) if set(suites) != EXPECTED: - return source + f'

    Incomplete test results: {len(suites)} of {len(EXPECTED)} suites available. Full totals unavailable; inspect the run for failures or missing artifacts.

    ' + return source + f'

    Incomplete test results: {len(suites)} of {len(EXPECTED)} suites available. Full totals unavailable; inspect the run for failures or missing artifacts.

    ' + coverage_html totals = {key: sum(s[key] for s in suites.values()) for key in ("total", "executed", "passed", "failed")} totals["skipped"] = totals["total"] - totals["executed"] totals["other"] = totals["executed"] - totals["passed"] - totals["failed"] counts = '' - return counts + source + return counts + coverage_html + source if __name__ == "__main__": - results, metadata, page = sys.argv[1:] + results, metadata, page = sys.argv[1:4] + coverage = sys.argv[4] if len(sys.argv) > 4 else None target = pathlib.Path(page) content = target.read_text(encoding="utf-8") start = "" end = "" before, remainder = content.split(start, 1) _, after = remainder.split(end, 1) - target.write_text(before + start + "\n" + render(results, json.loads(pathlib.Path(metadata).read_text(encoding="utf-8"))) + "\n" + end + after, encoding="utf-8") + target.write_text(before + start + "\n" + render(results, json.loads(pathlib.Path(metadata).read_text(encoding="utf-8")), coverage) + "\n" + end + after, encoding="utf-8") diff --git a/.github/scripts/test-homepage-tests.py b/.github/scripts/test-homepage-tests.py index b39a5acc..9d98862d 100644 --- a/.github/scripts/test-homepage-tests.py +++ b/.github/scripts/test-homepage-tests.py @@ -51,10 +51,53 @@ def test_duplicate_suite_rejected(self): with self.assertRaises(ValueError): renderer.render(self.root, self.run) - def test_invalid_counts_rejected(self): + def test_invalid_counts_rejected(self): self.populate(passed=8) with self.assertRaises(ValueError): - renderer.render(self.root, self.run) + renderer.render(self.root, self.run) + + def coverage(self, lines=75, total=100, branches=2, branch_total=3): + path = self.root / 'Cobertura.xml' + path.write_text(f'') + return path + + def test_merged_coverage_and_report_link(self): + self.populate() + self.run['coverage_url'] = self.run['html_url'] + '/artifacts/123' + result = renderer.render(self.root, self.run, self.coverage()) + for text in ('75.00%line coverage', '66.67%branch coverage', + '75 / 100 covered', '2 / 3 covered', '/artifacts/123', '48executed'): + self.assertIn(text, result) + + def test_missing_coverage_preserves_test_results(self): + self.populate() + result = renderer.render(self.root, self.run, self.root / 'missing.xml') + self.assertIn('Code coverage unavailable for this run', result) + self.assertIn('48executed', result) + self.assertNotIn('0.00%', result) + + def test_zero_branches_is_not_zero_percent(self): + result = renderer.render(self.root, self.run, self.coverage(branches=0, branch_total=0)) + self.assertIn('N/Abranch coverage', result) + + def test_invalid_coverage_rejected(self): + for values in ({'lines': 101}, {'lines': -1}, {'total': 0}, {'branches': 4}): + with self.subTest(values=values), self.assertRaises(ValueError): + renderer.render(self.root, self.run, self.coverage(**values)) + + def test_incomplete_tests_still_identify_coverage_source(self): + result = renderer.render(self.root, self.run, self.coverage()) + self.assertIn('Incomplete test results: 0 of 12', result) + self.assertIn('75.00%', result) + self.assertIn('abc1234', result) + + def test_suite_coverage_is_not_used_as_merged_coverage(self): + self.populate() + self.coverage() + result = renderer.render(self.root, self.run) + self.assertIn('Code coverage unavailable', result) + self.assertNotIn('75.00%', result) if __name__ == '__main__': diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 7d3192af..6f319e69 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -44,7 +44,7 @@ jobs: run: | python3 -B .github/scripts/build-docs.py --check python3 -B .github/scripts/verify-docs.py - - name: Verify test-count renderer + - name: Verify test-count and coverage renderer run: python3 .github/scripts/test-homepage-tests.py - name: Select latest completed master test run id: tests @@ -58,6 +58,14 @@ jobs: }); const run = data.workflow_runs[0]; if (run) { + const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, { + ...context.repo, run_id: run.id, per_page: 100 + }); + const coverage = artifacts.find(a => a.name === 'code-coverage' && !a.expired); + if (coverage) { + run.coverage_url = `${run.html_url}/artifacts/${coverage.id}`; + core.setOutput('has-coverage', 'true'); + } fs.writeFileSync('ci-run.json', JSON.stringify(run)); core.setOutput('run-id', String(run.id)); } @@ -69,9 +77,17 @@ jobs: run-id: ${{ steps.tests.outputs.run-id }} pattern: test-results-* path: ci-results - - name: Render test counts and provenance + - name: Download merged coverage from the same run + if: steps.tests.outputs.has-coverage == 'true' + uses: actions/download-artifact@v4 + with: + github-token: ${{ github.token }} + run-id: ${{ steps.tests.outputs.run-id }} + name: code-coverage + path: ci-coverage + - name: Render test counts, coverage and provenance if: steps.tests.outputs.run-id != '' - run: python3 .github/scripts/render-homepage-tests.py ci-results ci-run.json docs/index.html + run: python3 .github/scripts/render-homepage-tests.py ci-results ci-run.json docs/index.html ci-coverage/Cobertura.xml - uses: actions/configure-pages@v5 - uses: actions/upload-pages-artifact@v4 with: diff --git a/README.md b/README.md index 321a55b2..39adda22 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Source target: .NET 9](https://img.shields.io/badge/source_target-.NET_9-512BD4)](src/Migrator/DotNetProjects.Migrator.csproj) [![License: MPL-1.1](https://img.shields.io/badge/license-MPL--1.1-blue.svg)](https://www.mozilla.org/en-US/MPL/1.1/) -[Homepage](https://dotnetprojects.github.io/Migrator.NET/) · [Documentation](https://dotnetprojects.github.io/Migrator.NET/guide/) · [NuGet](https://www.nuget.org/packages/DotNetProjects.Migrator/) · [Releases](https://github.com/dotnetprojects/Migrator.NET/releases) · [Issues](https://github.com/dotnetprojects/Migrator.NET/issues) · [Feature comparison](https://dotnetprojects.github.io/Migrator.NET/#compare) · [CI test counts](https://dotnetprojects.github.io/Migrator.NET/#test-results) +[Homepage](https://dotnetprojects.github.io/Migrator.NET/) · [Documentation](https://dotnetprojects.github.io/Migrator.NET/guide/) · [NuGet](https://www.nuget.org/packages/DotNetProjects.Migrator/) · [Releases](https://github.com/dotnetprojects/Migrator.NET/releases) · [Issues](https://github.com/dotnetprojects/Migrator.NET/issues) · [Feature comparison](https://dotnetprojects.github.io/Migrator.NET/#compare) · [CI test results & coverage](https://dotnetprojects.github.io/Migrator.NET/#test-results) DotNetProjects.Migrator is a fork of [Migrator.NET](https://github.com/migratordotnet/Migrator.NET). Write each schema change as a numbered C# class, commit it alongside your application, and use the runner to bring a database to the required version. The database records which migrations have already been applied. diff --git a/docs/README.md b/docs/README.md index 341d9d91..ed1bcd22 100644 --- a/docs/README.md +++ b/docs/README.md @@ -45,13 +45,18 @@ The comparison distinguishes source capabilities from guarantees about released The quick start targets .NET 9 and installs the core library and SQLite driver through NuGet. Sample validation uses local project references to catch API drift. The SQLite driver version matches the repository test dependency. -## Status badges and test counts +## Status badges, test counts and code coverage The homepage links to master CI results and NuGet, and identifies the MPL-1.1 license. Pages also redeploys when master CI completes. During deployment it selects the latest completed master push run (including failures), downloads its TRX artifacts, and renders executed/passed/failed/skipped/other counts with the run URL, commit and timestamp. -Counts are a deployment snapshot, not code coverage or a guarantee about every provider. +The same run supplies the merged `code-coverage` artifact. Pages displays line and branch +coverage percentages and covered/total counts for production assemblies, with a link to +the downloadable HTML report (open `index.html`). Shared code is counted once in the +merged report; suite percentages are never added or averaged. A missing or expired +coverage artifact is shown as unavailable, never as 0% or replaced by another run. +Counts and coverage are a deployment snapshot, not a guarantee about every provider. Missing suites produce an incomplete-results message, never a partial success total. The committed/local page shows a fallback link until deployment supplies results. The renderer only changes the uploaded Pages artifact; it does not commit generated counts. Preserve `TEST_RESULTS_START` and `TEST_RESULTS_END` in `_src/home.html`. diff --git a/docs/_src/home.html b/docs/_src/home.html index f8897003..77b8e576 100644 --- a/docs/_src/home.html +++ b/docs/_src/home.html @@ -25,8 +25,8 @@

    Database changes,
    written in C#.

    FROM EMPTY FOLDER TO FIRST TABLE

    Start with
    one change.

    Install the library and your database driver. Add the migration above, wire up the runner, and apply it. SQLite makes a convenient first database.

    Follow the complete quick start ↗
    {{install}}

    THE MIGRATION MANUAL

    Past “hello, table.”

    Detailed guides, paired examples, and provider notes.
    Browse every chapter ↗

    BRING YOUR DATABASE

    One migration system.
    Many dialects.

    Capabilities follow the database engine. The provider guide explains aliases, drivers and operation-specific behavior.

    -

    BUILD AND DATABASE TESTS

    Evidence from CI.

    The CI matrix runs unit tests and 11 database suites, then checks for missing or duplicate test assignments. Counts represent test executions across the matrix; skipped tests are shown separately.

    -

    Test totals are populated during deployment from the latest completed master CI run. View CI runs and test-result artifacts ↗.

    +

    TEST RESULTS AND CODE COVERAGE

    Evidence from CI.

    The CI matrix runs unit tests and 11 database suites, then checks for missing or duplicate test assignments. Counts represent test executions across the matrix; skipped tests are shown separately. Line and branch coverage measure production code across the combined suites.

    +

    Test totals and code coverage are populated during deployment from the latest completed master CI run. View CI runs and test-result artifacts ↗.

    {{comparison}}

    EXPLICIT CHANGES. A LASTING RECORD.

    The next version
    starts with a change.

    Open the manual ↗Contribute on GitHub →
    diff --git a/docs/assets/site.css b/docs/assets/site.css index 7d708b18..65556885 100644 --- a/docs/assets/site.css +++ b/docs/assets/site.css @@ -553,7 +553,13 @@ img { font-size:14px; color:var(--muted); } -.test-counts { +.coverage-counts small { + display: block; + margin-top: 6px; + color: var(--muted); + font-size: 12px; +} +.test-counts { display:flex; list-style:none; gap:35px; diff --git a/docs/guide/altering-tables.html b/docs/guide/altering-tables.html index 0a3121a9..e0c79ec3 100644 --- a/docs/guide/altering-tables.html +++ b/docs/guide/altering-tables.html @@ -1,6 +1,6 @@ -Altering tables · Migrator.NET +Altering tables · Migrator.NET

    Shared commands · both styles

    THE MIGRATION MANUAL

    Past “hello, table.”

    Detailed guides, paired examples, and provider notes.
    Browse every chapter ↗

    BRING YOUR DATABASE

    One migration system.
    Many dialects.

    Capabilities follow the database engine. The provider guide explains aliases, drivers and operation-specific behavior.

    -

    BUILD AND DATABASE TESTS

    Evidence from CI.

    The CI matrix runs unit tests and 11 database suites, then checks for missing or duplicate test assignments. Counts represent test executions across the matrix; skipped tests are shown separately.

    -

    Test totals are populated during deployment from the latest completed master CI run. View CI runs and test-result artifacts ↗.

    +

    TEST RESULTS AND CODE COVERAGE

    Evidence from CI.

    The CI matrix runs unit tests and 11 database suites, then checks for missing or duplicate test assignments. Counts represent test executions across the matrix; skipped tests are shown separately. Line and branch coverage measure production code across the combined suites.

    +

    Test totals and code coverage are populated during deployment from the latest completed master CI run. View CI runs and test-result artifacts ↗.