MIL-010 task 6: the coverage report reaches the workflow as an artifact (ADR-0010, RC-073)

Task: MIL-010#6
Refs #160
This commit is contained in:
2026-10-14 13:54:01 +08:00
parent 4fc0a8cab3
commit 0dc5132a2e
8 changed files with 142 additions and 24 deletions
+15 -1
View File
@@ -13,7 +13,9 @@ name: Code metrics
# #
# It counts the code files and the lines of code of the repository (comment # It counts the code files and the lines of code of the repository (comment
# and blank lines are not counted), reads an optional Cobertura XML coverage # and blank lines are not counted), reads an optional Cobertura XML coverage
# report and commits three SVG badges to a branch of the calling repository. # report (an artifact of an earlier job of the caller, or a file of the
# repository) and commits three SVG badges to a branch of the calling
# repository.
# It runs no test, needs no secret and writes only the badges branch. # It runs no test, needs no secret and writes only the badges branch.
on: on:
workflow_call: workflow_call:
@@ -30,6 +32,10 @@ on:
description: "Path, relative to the repository root, of a Cobertura XML coverage report; empty writes no coverage badge" description: "Path, relative to the repository root, of a Cobertura XML coverage report; empty writes no coverage badge"
type: string type: string
default: "" default: ""
coverage-artifact:
description: "Name of an artifact, uploaded by an earlier job of the same run, that holds the coverage report; empty reads coverage-report from the checkout"
type: string
default: ""
coverage-warn: coverage-warn:
description: "Coverage in percent below which the coverage badge is red" description: "Coverage in percent below which the coverage badge is red"
type: string type: string
@@ -57,6 +63,7 @@ jobs:
WORKING_DIRECTORY: ${{ inputs.working-directory }} WORKING_DIRECTORY: ${{ inputs.working-directory }}
EXCLUDE: ${{ inputs.exclude }} EXCLUDE: ${{ inputs.exclude }}
COVERAGE_REPORT: ${{ inputs.coverage-report }} COVERAGE_REPORT: ${{ inputs.coverage-report }}
COVERAGE_ARTIFACT: ${{ inputs.coverage-artifact }}
COVERAGE_WARN: ${{ inputs.coverage-warn }} COVERAGE_WARN: ${{ inputs.coverage-warn }}
COVERAGE_GOOD: ${{ inputs.coverage-good }} COVERAGE_GOOD: ${{ inputs.coverage-good }}
BADGES_BRANCH: ${{ inputs.badges-branch }} BADGES_BRANCH: ${{ inputs.badges-branch }}
@@ -74,6 +81,13 @@ jobs:
- name: Validate the inputs - name: Validate the inputs
run: bash shared-scripts/tools/code-metrics.sh validate run: bash shared-scripts/tools/code-metrics.sh validate
- name: Download the coverage report
if: ${{ inputs.coverage-artifact != '' }}
uses: actions/download-artifact@v4.1.8
with:
name: ${{ inputs.coverage-artifact }}
path: coverage-artifact
- name: Count the code and write the badges - name: Count the code and write the badges
run: bash shared-scripts/tools/code-metrics.sh measure run: bash shared-scripts/tools/code-metrics.sh measure
@@ -9,7 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; reviewed (RC-072) | pending | | 2026-10-14 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; reviewed (RC-072) | pending |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | The report reaches the workflow as an artifact of the caller's run (input `coverage-artifact`), because a called workflow runs in its own job; reviewed (RC-073) | pending |
Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`. Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`.
@@ -112,8 +113,15 @@ The rules that follow from it:
and a report with no valid lines are failures with the category and a report with no valid lines are failures with the category
`[configuration]` that names the file; no coverage badge is written. An `[configuration]` that names the file; no coverage badge is written. An
empty `coverage-report` means no coverage badge and a notice. The workflow empty `coverage-report` means no coverage badge and a notice. The workflow
does not run tests; the caller runs them before the call and the report does not run tests. A called workflow runs in its own job, so it cannot see
must be in the workspace (the same job, or an artifact the caller fetched). the files of the caller's test job: the caller runs its tests in an earlier
job, uploads the report as an artifact and gives the artifact's name as the
input `coverage-artifact`. The workflow then downloads that artifact into
the directory `coverage-artifact` of its workspace and `coverage-report`
names the file inside the artifact. Without `coverage-artifact`,
`coverage-report` is a path in the checkout of the repository (a report the
repository commits). `coverage-artifact` without `coverage-report` is a
`[configuration]` failure.
- **Badges.** Three flat SVG files with a label and a value, written to the - **Badges.** Three flat SVG files with a label and a value, written to the
root of the `badges` branch with fixed names: `code-files.svg` (label root of the `badges` branch with fixed names: `code-files.svg` (label
`code files`), `code-lines.svg` (label `lines of code`) and `coverage.svg` `code files`), `code-lines.svg` (label `lines of code`) and `coverage.svg`
@@ -135,7 +143,8 @@ The rules that follow from it:
the guide tells a caller to start the workflow on pushes to its default the guide tells a caller to start the workflow on pushes to its default
branch only. branch only.
- **Inputs.** `working-directory` (default `.`), `exclude` (default empty), - **Inputs.** `working-directory` (default `.`), `exclude` (default empty),
`coverage-report` (default empty), `coverage-warn` (default `60`), `coverage-report` (default empty), `coverage-artifact` (default empty),
`coverage-warn` (default `60`),
`coverage-good` (default `80`), `badges-branch` (default `badges`) and `coverage-good` (default `80`), `badges-branch` (default `badges`) and
`tools-ref` (default `v1`; pin it to the same exact tag as the workflow). `tools-ref` (default `v1`; pin it to the same exact tag as the workflow).
There is no tool version input: the tools are files of this repository and There is no tool version input: the tools are files of this repository and
@@ -174,12 +183,16 @@ The rules that follow from it:
`contents: read` of the quality checks; a caller that grants less cannot use `contents: read` of the quality checks; a caller that grants less cannot use
it. The write is limited to the `badges` branch by the script, not by the it. The write is limited to the `badges` branch by the script, not by the
host. host.
- A caller must run its tests and produce a Cobertura XML report before the - A caller must run its tests and produce a Cobertura XML report in an
call; other formats need a conversion until a later release adds them. earlier job and upload it as an artifact; other formats need a conversion
until a later release adds them. The download needs the artifact service
of the instance to serve a called workflow, which task 8 of MIL-010
verifies.
- A fixed set of thresholds and colours; a caller can move the two thresholds - A fixed set of thresholds and colours; a caller can move the two thresholds
only. only.
- Security: the script runs with a token that can write to the repository. - Security: the script runs with a token that can write to the repository.
The files are ours and no code is downloaded, but a caller that pins The files are ours and no code is downloaded (the artifact is a report that
is parsed as XML with entity declarations refused), but a caller that pins
`tools-ref` to a moving ref takes later changes of the script; the guide `tools-ref` to a moving ref takes later changes of the script; the guide
says to pin the exact tag. The script writes only the four named files, and says to pin the exact tag. The script writes only the four named files, and
the branch name input is validated so that it cannot name the default branch the branch name input is validated so that it cannot name the default branch
+1 -1
View File
@@ -11,7 +11,7 @@ each document lives in *this* project and the next version to use.
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 | | SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
| PP | Project Plan | docs/project-plan.md | 002 | | PP | Project Plan | docs/project-plan.md | 002 |
| MIL | Milestone / Gateway | docs/milestones/*.md | 011 | | MIL | Milestone / Gateway | docs/milestones/*.md | 011 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 073 | | RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 074 |
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
| ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0011 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0011 |
| UCD | Use Case Diagram | docs/use-case-diagram.md | 002 | | UCD | Use Case Diagram | docs/use-case-diagram.md | 002 |
@@ -0,0 +1,54 @@
# Review Record: ADR-0010 (the coverage report as an artifact of the caller)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-073 |
| CrossReference | [ADR-0010], [QC-ADR-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | pending |
---
## Artifact Under Review
- Instance reviewed: [ADR-0010]
- Checklist used: [QC-ADR-001]
- Scope: delta re-review of the Coverage report and Inputs rules, the Consequences and the Version History after the implementation of MIL-010 task 6 showed that a called workflow cannot see the files of the caller's test job; earlier record RC-072
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | The ID (`ADR-NNNN`, 4-digit) matches the filename number and is unique and sequential | Pass | Unchanged. |
| 2 | Context states the problem, the forces (cost, risk, constraints) and the options evaluated | Pass | Unchanged; the new force (a called workflow runs in its own job) is stated in the Decision rule that it explains. |
| 3 | At least two options were considered, or the absence of alternatives is justified | Pass | Unchanged. For the report: an artifact of the caller's run, or a file committed to the repository; both are supported. |
| 4 | Decision is stated in one or two unhedged sentences and matches one of the evaluated options | Pass | The Decision sentence is unchanged; the coverage rule is a refinement of it. |
| 5 | Consequences list both Positive and Negative outcomes | Pass | The negative consequence about the report now names the earlier job, the upload and the dependency on the artifact service, which task 8 verifies. |
| 6 | Quality-attribute impacts (performance, security, portability, etc.) are named where the decision affects them | Pass | The security bullet says the artifact is parsed as XML with entity declarations refused. |
| 7 | Affected Artifacts are listed as links, and each is also in CrossReference | Pass | Unchanged. |
| 8 | Status changes are recorded as new Version History rows with a Change summary and Commit link; retained rows are never rewritten | Pass | A new row; the earlier Accepted row became Deprecated and was not rewritten otherwise. |
| 9 | A superseded or deprecated ADR names its successor, and the successor links back | N-A | The ADR is amended, not superseded. |
| 10 | Written in business or technical language of the domain, with no unexplained abbreviations | Pass | Artifact is the term of the Actions runtime. |
| 11 | Status is one of `Proposed`, `Accepted`, `Rejected`, `Deprecated` or `Superseded by ADR-NNNN`, and every Version History row uses only these | Pass | Deprecated and Accepted. |
## Overall Verdict
Go - all applicable criteria pass; the amendment keeps the decisions of the user of 2026-10-14 and corrects a mechanism that cannot work as first written. The AI records the decision as stand-in for S01; there is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None | - | - |
---
[ADR-0010]: ../../adr/adr-0010-code-metrics-badges-contract.md
[QC-ADR-001]: ../../../framework/qc/artifacts/qc-adr.md
+5 -3
View File
@@ -4,13 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | TM-001 | | ID | TM-001 |
| CrossReference | [BC-001], [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009], [MIL-010], [UC-008], [RC-001], [RC-002], [RC-003], [RC-004], [RC-005], [RC-006], [RC-007], [RC-008], [RC-009], [RC-010], [RC-011], [RC-012], [RC-013], [RC-014], [RC-015], [RC-016], [RC-017], [RC-018], [RC-019], [RC-020], [RC-021], [RC-022], [RC-023], [RC-024], [RC-025], [RC-026], [RC-027], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032], [RC-033], [RC-034], [RC-035], [RC-036], [RC-037], [RC-038], [RC-039], [RC-040], [RC-041], [RC-042], [RC-043], [RC-044], [RC-045], [RC-046], [RC-047], [RC-048], [RC-049], [RC-050], [RC-051], [RC-052], [RC-053], [RC-054], [RC-055], [RC-056], [RC-057], [RC-058], [RC-059], [RC-060], [RC-061], [RC-062], [RC-063], [RC-064], [RC-065], [RC-066], [RC-067], [RC-068], [RC-069], [RC-070], [RC-071], [RC-072], [ADR-0007], [ADR-0008], [ADR-0009], [ADR-0010], [UC-007], [UC-006], [ADR-0006], [UC-005], [ADR-0005], [UCD-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [ADR-0001], [ADR-0002], [ADR-0003], [ADR-0004] | | CrossReference | [BC-001], [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009], [MIL-010], [UC-008], [RC-001], [RC-002], [RC-003], [RC-004], [RC-005], [RC-006], [RC-007], [RC-008], [RC-009], [RC-010], [RC-011], [RC-012], [RC-013], [RC-014], [RC-015], [RC-016], [RC-017], [RC-018], [RC-019], [RC-020], [RC-021], [RC-022], [RC-023], [RC-024], [RC-025], [RC-026], [RC-027], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032], [RC-033], [RC-034], [RC-035], [RC-036], [RC-037], [RC-038], [RC-039], [RC-040], [RC-041], [RC-042], [RC-043], [RC-044], [RC-045], [RC-046], [RC-047], [RC-048], [RC-049], [RC-050], [RC-051], [RC-052], [RC-053], [RC-054], [RC-055], [RC-056], [RC-057], [RC-058], [RC-059], [RC-060], [RC-061], [RC-062], [RC-063], [RC-064], [RC-065], [RC-066], [RC-067], [RC-068], [RC-069], [RC-070], [RC-071], [RC-072], [RC-073], [ADR-0007], [ADR-0008], [ADR-0009], [ADR-0010], [UC-007], [UC-006], [ADR-0006], [UC-005], [ADR-0005], [UCD-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [ADR-0001], [ADR-0002], [ADR-0003], [ADR-0004] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Proposed | Jens Tirsvad Nielsen | S01 | Added UC-008, RC-069 to RC-071 (code metrics badges, MIL-010 task 1) | pending |
| 2026-10-14 | Proposed | Jens Tirsvad Nielsen | S01 | Added ADR-0010, RC-072 (contract of the code metrics badges, MIL-010 task 2) | pending | | 2026-10-14 | Proposed | Jens Tirsvad Nielsen | S01 | Added ADR-0010, RC-072 (contract of the code metrics badges, MIL-010 task 2) | pending |
| 2026-10-14 | Proposed | Jens Tirsvad Nielsen | S01 | Added RC-073 (ADR-0010: the coverage report as an artifact, MIL-010 task 6) | pending |
--- ---
@@ -31,7 +31,7 @@ updated whenever an artifact instance is created or reviewed.
| [MIL-002] | MIL | [BC-001], [PP-001], [MIL-001] | [MIL-003], [MIL-005] | [RC-005], [RC-027] | | [MIL-002] | MIL | [BC-001], [PP-001], [MIL-001] | [MIL-003], [MIL-005] | [RC-005], [RC-027] |
| [MIL-003] | MIL | [BC-001], [PP-001], [MIL-002] | [MIL-004] | [RC-006], [RC-028], [RC-033], [RC-039] | | [MIL-003] | MIL | [BC-001], [PP-001], [MIL-002] | [MIL-004] | [RC-006], [RC-028], [RC-033], [RC-039] |
| [MIL-004] | MIL | [BC-001], [PP-001], [MIL-003] | - | [RC-007], [RC-029], [RC-034], [RC-048] | | [MIL-004] | MIL | [BC-001], [PP-001], [MIL-003] | - | [RC-007], [RC-029], [RC-034], [RC-048] |
| [ADR-0010] | ADR | [MIL-010], [UC-008], [US-001], [ADR-0005], [ADR-0006] | - | [RC-072] | | [ADR-0010] | ADR | [MIL-010], [UC-008], [US-001], [ADR-0005], [ADR-0006] | - | [RC-072], [RC-073] |
| [RC-001] | RC | [BC-001] | - | - | | [RC-001] | RC | [BC-001] | - | - |
| [RC-002] | RC | [PP-001] | - | - | | [RC-002] | RC | [PP-001] | - | - |
| [RC-003] | RC | [MIL-001] | - | - | | [RC-003] | RC | [MIL-001] | - | - |
@@ -129,6 +129,7 @@ updated whenever an artifact instance is created or reviewed.
| [RC-070] | RC | [US-001] | - | - | | [RC-070] | RC | [US-001] | - | - |
| [RC-071] | RC | [UCD-001] | - | - | | [RC-071] | RC | [UCD-001] | - | - |
| [RC-072] | RC | [ADR-0010] | - | - | | [RC-072] | RC | [ADR-0010] | - | - |
| [RC-073] | RC | [ADR-0010] | - | - |
## Coverage Notes ## Coverage Notes
@@ -235,6 +236,7 @@ updated whenever an artifact instance is created or reviewed.
[RC-070]: ./reviews/rc-070-us-001-metrics.md [RC-070]: ./reviews/rc-070-us-001-metrics.md
[RC-071]: ./reviews/rc-071-ucd-001-metrics.md [RC-071]: ./reviews/rc-071-ucd-001-metrics.md
[RC-072]: ./reviews/rc-072-adr-0010.md [RC-072]: ./reviews/rc-072-adr-0010.md
[RC-073]: ./reviews/rc-073-adr-0010-artifact.md
[ADR-0008]: ../adr/adr-0008-cpp-csharp-quality-check-contract.md [ADR-0008]: ../adr/adr-0008-cpp-csharp-quality-check-contract.md
[ADR-0009]: ../adr/adr-0009-wiki-publishing-reusable-workflow.md [ADR-0009]: ../adr/adr-0009-wiki-publishing-reusable-workflow.md
[ADR-0007]: ../adr/adr-0007-release-publish-destination.md [ADR-0007]: ../adr/adr-0007-release-publish-destination.md
+28
View File
@@ -40,6 +40,7 @@ class TestDefinition:
"working-directory": ".", "working-directory": ".",
"exclude": "", "exclude": "",
"coverage-report": "", "coverage-report": "",
"coverage-artifact": "",
"coverage-warn": "60", "coverage-warn": "60",
"coverage-good": "80", "coverage-good": "80",
"badges-branch": "badges", "badges-branch": "badges",
@@ -59,6 +60,7 @@ class TestDefinition:
"WORKING_DIRECTORY", "WORKING_DIRECTORY",
"EXCLUDE", "EXCLUDE",
"COVERAGE_REPORT", "COVERAGE_REPORT",
"COVERAGE_ARTIFACT",
"COVERAGE_WARN", "COVERAGE_WARN",
"COVERAGE_GOOD", "COVERAGE_GOOD",
"BADGES_BRANCH", "BADGES_BRANCH",
@@ -139,6 +141,7 @@ def run(command: str, repo: Path, tmp_path: Path, python3_bin: Path, **env: str)
"WORKING_DIRECTORY": ".", "WORKING_DIRECTORY": ".",
"EXCLUDE": "", "EXCLUDE": "",
"COVERAGE_REPORT": "", "COVERAGE_REPORT": "",
"COVERAGE_ARTIFACT": "",
"COVERAGE_WARN": "60", "COVERAGE_WARN": "60",
"COVERAGE_GOOD": "80", "COVERAGE_GOOD": "80",
"BADGES_BRANCH": "badges", "BADGES_BRANCH": "badges",
@@ -187,6 +190,7 @@ class TestValidate:
("WORKING_DIRECTORY", "../x", "working-directory must be a relative path"), ("WORKING_DIRECTORY", "../x", "working-directory must be a relative path"),
("WORKING_DIRECTORY", "nope", "working-directory 'nope' does not exist"), ("WORKING_DIRECTORY", "nope", "working-directory 'nope' does not exist"),
("COVERAGE_REPORT", "/tmp/c.xml", "coverage-report must be a relative path"), ("COVERAGE_REPORT", "/tmp/c.xml", "coverage-report must be a relative path"),
("COVERAGE_ARTIFACT", "cov", "coverage-artifact 'cov' needs coverage-report"),
("BADGES_BRANCH", "main", "must not be a default branch"), ("BADGES_BRANCH", "main", "must not be a default branch"),
("BADGES_BRANCH", "master", "must not be a default branch"), ("BADGES_BRANCH", "master", "must not be a default branch"),
("BADGES_BRANCH", "", "must not be a default branch"), ("BADGES_BRANCH", "", "must not be a default branch"),
@@ -255,6 +259,30 @@ class TestPublish:
assert result.returncode == 0, result.stdout assert result.returncode == 0, result.stdout
assert "coverage.svg" in branch_files(remote) assert "coverage.svg" in branch_files(remote)
def test_a_report_from_an_artifact_is_read_below_the_artifact_directory(
self, repo, remote, tmp_path, python3_bin
):
artifact = repo / "coverage-artifact"
artifact.mkdir()
(artifact / "cov.xml").write_text(COVERAGE, newline="\n")
result = measure_and_publish(
repo, tmp_path, python3_bin, COVERAGE_ARTIFACT="coverage", COVERAGE_REPORT="cov.xml"
)
assert result.returncode == 0, result.stdout
assert "coverage.svg" in branch_files(remote)
def test_a_report_missing_from_the_artifact_names_the_path(self, repo, tmp_path, python3_bin):
result = run(
"measure",
repo,
tmp_path,
python3_bin,
COVERAGE_ARTIFACT="coverage",
COVERAGE_REPORT="cov.xml",
)
assert result.returncode == 1
assert "coverage-report 'coverage-artifact/cov.xml' does not exist" in result.stdout
def test_a_missing_report_fails_names_the_file_and_changes_nothing( def test_a_missing_report_fails_names_the_file_and_changes_nothing(
self, repo, remote, tmp_path, python3_bin self, repo, remote, tmp_path, python3_bin
): ):
+9 -9
View File
@@ -35,21 +35,21 @@ class ConfigurationError(Exception):
def read_coverage(path: Path) -> int: def read_coverage(path: Path) -> int:
"""Return the line coverage of a Cobertura XML report as a whole percent, half up.""" """Return the line coverage of a Cobertura XML report as a whole percent, half up."""
if not path.is_file(): if not path.is_file():
raise ConfigurationError(f"coverage-report '{path}' does not exist.") raise ConfigurationError(f"coverage-report '{path.as_posix()}' does not exist.")
data = path.read_bytes() data = path.read_bytes()
if b"<!ENTITY" in data: if b"<!ENTITY" in data:
raise ConfigurationError( raise ConfigurationError(
f"coverage-report '{path}' declares entities, which are refused." f"coverage-report '{path.as_posix()}' declares entities, which are refused."
) )
try: try:
root = ElementTree.fromstring(data) root = ElementTree.fromstring(data)
except ElementTree.ParseError as error: except ElementTree.ParseError as error:
raise ConfigurationError( raise ConfigurationError(
f"coverage-report '{path}' is not well-formed XML: {error}." f"coverage-report '{path.as_posix()}' is not well-formed XML: {error}."
) from error ) from error
if root.tag != "coverage": if root.tag != "coverage":
raise ConfigurationError( raise ConfigurationError(
f"coverage-report '{path}' is not a Cobertura report (root element '{root.tag}')." f"coverage-report '{path.as_posix()}' is not a Cobertura report (root element '{root.tag}')."
) )
covered, valid = root.get("lines-covered"), root.get("lines-valid") covered, valid = root.get("lines-covered"), root.get("lines-valid")
try: try:
@@ -57,27 +57,27 @@ def read_coverage(path: Path) -> int:
covered_lines, valid_lines = int(covered), int(valid) covered_lines, valid_lines = int(covered), int(valid)
if valid_lines <= 0: if valid_lines <= 0:
raise ConfigurationError( raise ConfigurationError(
f"coverage-report '{path}' has no valid lines." f"coverage-report '{path.as_posix()}' has no valid lines."
) )
if not 0 <= covered_lines <= valid_lines: if not 0 <= covered_lines <= valid_lines:
raise ConfigurationError( raise ConfigurationError(
f"coverage-report '{path}' has lines-covered outside 0..lines-valid." f"coverage-report '{path.as_posix()}' has lines-covered outside 0..lines-valid."
) )
fraction = Decimal(covered_lines) / Decimal(valid_lines) fraction = Decimal(covered_lines) / Decimal(valid_lines)
else: else:
rate = root.get("line-rate") rate = root.get("line-rate")
if rate is None: if rate is None:
raise ConfigurationError( raise ConfigurationError(
f"coverage-report '{path}' has neither lines-valid and lines-covered nor line-rate." f"coverage-report '{path.as_posix()}' has neither lines-valid and lines-covered nor line-rate."
) )
fraction = Decimal(rate) fraction = Decimal(rate)
if not Decimal(0) <= fraction <= Decimal(1): if not Decimal(0) <= fraction <= Decimal(1):
raise ConfigurationError( raise ConfigurationError(
f"coverage-report '{path}' has a line-rate outside 0..1." f"coverage-report '{path.as_posix()}' has a line-rate outside 0..1."
) )
except (ValueError, InvalidOperation) as error: except (ValueError, InvalidOperation) as error:
raise ConfigurationError( raise ConfigurationError(
f"coverage-report '{path}' has a value that is not a number." f"coverage-report '{path.as_posix()}' has a value that is not a number."
) from error ) from error
return int((fraction * 100).quantize(Decimal(1), rounding=ROUND_HALF_UP)) return int((fraction * 100).quantize(Decimal(1), rounding=ROUND_HALF_UP))
+10 -3
View File
@@ -10,8 +10,8 @@
# and nothing when none of them changed # and nothing when none of them changed
# #
# Settings come from the environment, never from interpolated expressions: # Settings come from the environment, never from interpolated expressions:
# WORKING_DIRECTORY, EXCLUDE, COVERAGE_REPORT, COVERAGE_WARN, COVERAGE_GOOD, # WORKING_DIRECTORY, EXCLUDE, COVERAGE_REPORT, COVERAGE_ARTIFACT,
# BADGES_BRANCH, BADGES_OUT (the directory of the badge files; a temporary # COVERAGE_WARN, COVERAGE_GOOD, BADGES_BRANCH, BADGES_OUT (the directory of the badge files; a temporary
# directory under RUNNER_TEMP by default). # directory under RUNNER_TEMP by default).
# Every failure is printed as ::error::[category] message. # Every failure is printed as ::error::[category] message.
@@ -41,6 +41,9 @@ validate_inputs() {
relative_path "$COVERAGE_REPORT" || relative_path "$COVERAGE_REPORT" ||
fail "coverage-report must be a relative path inside the repository." fail "coverage-report must be a relative path inside the repository."
fi fi
if [ -n "$COVERAGE_ARTIFACT" ] && [ -z "$COVERAGE_REPORT" ]; then
fail "coverage-artifact '$COVERAGE_ARTIFACT' needs coverage-report, the file inside the artifact."
fi
case "$BADGES_BRANCH" in case "$BADGES_BRANCH" in
main | master | HEAD | "") fail "badges-branch '$BADGES_BRANCH' must not be a default branch." ;; main | master | HEAD | "") fail "badges-branch '$BADGES_BRANCH' must not be a default branch." ;;
esac esac
@@ -55,9 +58,13 @@ measure() {
python3 "$TOOLS_DIR/count_loc.py" \ python3 "$TOOLS_DIR/count_loc.py" \
--directory "$WORKING_DIRECTORY" --exclude "$EXCLUDE" --output "$metrics" --directory "$WORKING_DIRECTORY" --exclude "$EXCLUDE" --output "$metrics"
rm -rf "$OUT_DIR" rm -rf "$OUT_DIR"
report="$COVERAGE_REPORT"
if [ -n "$COVERAGE_ARTIFACT" ]; then
report="coverage-artifact/$COVERAGE_REPORT"
fi
python3 "$TOOLS_DIR/badges.py" \ python3 "$TOOLS_DIR/badges.py" \
--metrics "$metrics" --output-dir "$OUT_DIR" \ --metrics "$metrics" --output-dir "$OUT_DIR" \
--coverage-report "$COVERAGE_REPORT" \ --coverage-report "$report" \
--coverage-warn "$COVERAGE_WARN" --coverage-good "$COVERAGE_GOOD" --coverage-warn "$COVERAGE_WARN" --coverage-good "$COVERAGE_GOOD"
rm -f "$metrics" rm -f "$metrics"
} }