Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,42 @@ jobs:

test:
runs-on: ubuntu-latest
permissions:
contents: read
# Needed to post/update the coverage report comment on the PR.
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
# hatch-vcs reads tags from `.git/` inside the build context
# to derive the version; the default shallow checkout doesn't
# include enough history for that.
fetch-depth: 0
- name: Build test image
run: docker compose --profile test build --no-cache test
- name: Run tests
run: docker compose --profile test run --rm test
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Run tests and diff-cover
run: bash scripts/run_coverage.sh
# Render coverage + test results as a single sticky PR comment. Runs even
# when diff-cover fails the gate above, so the report is always posted; the
# coverage.xml and junit.xml it reads are written before diff-cover runs.
- name: Coverage report comment
if: ${{ always() && github.event_name == 'pull_request' }}
uses: MishaKav/pytest-coverage-comment@v1.10.0
with:
pytest-coverage-path: pytest-coverage.txt
junitxml-path: junit.xml
title: Coverage report
# Post diff-cover's own report as a second sticky comment. This is the one
# that explains a red check: it shows the changed-line coverage and the
# specific uncovered lines that fell below the threshold. run_coverage.sh
# writes diff-cover-report.md before the gate exits.
- name: Diff coverage comment
if: ${{ always() && github.event_name == 'pull_request' }}
uses: marocchino/sticky-pull-request-comment@v3.0.5
with:
header: diff-coverage
path: diff-cover-report.md
ignore_empty: true

test-js:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ pip-log.txt
.coverage
.coverage-js
.coverage.xml
diff-cover-report.md
junit.xml
pytest-coverage.txt
.tox
test-results/

Expand Down
9 changes: 8 additions & 1 deletion deploy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,14 @@ COPY README.md README.md
COPY .git .git
COPY sparkmeter sparkmeter

RUN uv sync --locked --no-dev
# Supply the version explicitly when the build context has no usable .git —
# e.g. a git worktree, whose .git is a file pointing outside the context, so
# hatch-vcs/setuptools-scm cannot derive the version. Empty (the default)
# leaves hatch-vcs to read .git as usual. sparkmeter is the only source build
# in this step, so the global (non-dist-scoped) variable is unambiguous here.
ARG SETUPTOOLS_SCM_PRETEND_VERSION=""
RUN SETUPTOOLS_SCM_PRETEND_VERSION="$SETUPTOOLS_SCM_PRETEND_VERSION" \
uv sync --locked --no-dev

RUN apk del .build-deps

Expand Down
55 changes: 55 additions & 0 deletions scripts/run_coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Build the test image, run the suite, and gate the changed lines with
# diff-cover. Coverage is produced inside the container and streamed out to the
# host, where git history is available to diff against the compare branch.
set -euo pipefail

compare_branch="${DIFF_COVER_COMPARE_BRANCH:-origin/main}"
fail_under="${DIFF_COVER_FAIL_UNDER:-90}"

# A git worktree's .git is a file, not a directory, and is unreadable inside the
# build context, so hatch-vcs cannot derive the version. Supply a placeholder in
# that case; a normal checkout leaves it empty and reads .git as usual.
version=""
if [ -f .git ]; then
version="0.0.0"
fi

docker compose --profile test build \
--build-arg SETUPTOOLS_SCM_PRETEND_VERSION="$version" \
test

# pytest writes .coverage.xml (see setup.cfg addopts) and, via --junitxml, a
# junit report; its terminal output (the term-missing coverage table) is teed to
# pytest-coverage.txt for the PR report comment. All three are produced in the
# container, tarred to stdout, and unpacked on the host. pytest's output is teed
# to stderr, not stdout, so stdout carries only the tar stream.
docker compose --profile test run --rm test \
sh -c 'uv run pytest -n auto --junitxml=/app/junit.xml 2>&1 | tee /app/pytest-coverage.txt 1>&2; tar -c -C /app .coverage.xml junit.xml pytest-coverage.txt' \
| tar -x

# diff-cover gates the changed lines and always prints a terminal report. It
# also writes a markdown report, which is appended to the GitHub Actions job
# summary below. Under Actions it additionally emits GitHub annotations on
# stdout, so each uncovered changed line is flagged inline on the PR diff.
report_md="diff-cover-report.md"
formats="markdown:$report_md"
if [ -n "${GITHUB_ACTIONS:-}" ]; then
formats="$formats,github-annotations:error"
fi

# --fail-under exits non-zero when changed-line coverage is below the threshold.
# Capture that instead of aborting so the report still reaches the job summary.
set +e
uvx "diff-cover==10.3.0" .coverage.xml \
--compare-branch "$compare_branch" \
--fail-under "$fail_under" \
--format "$formats"
diff_cover_rc=$?
set -e

if [ -n "${GITHUB_STEP_SUMMARY:-}" ] && [ -f "$report_md" ]; then
cat "$report_md" >> "$GITHUB_STEP_SUMMARY"
fi

exit "$diff_cover_rc"
123 changes: 0 additions & 123 deletions scripts/validatecoverage

This file was deleted.

1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ python_classes = *Test Test*
output=.coverage.xml

[coverage:run]
relative_files = True
omit =
**/test_*.py
sparkmeter/tests/base.py
Expand Down
Loading