From 5ecd5d003f00c6f3c876c244a4f78ac7ef994dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Martin?= Date: Tue, 6 Oct 2026 13:56:08 +0200 Subject: [PATCH] Coverage of lib/ by the host tests, and badges in the README scripts/coverage.sh builds the host tests with coverage counters and reports with gcovr: 94.6% of the 3,193 lines of lib/ today (lib/SD and src/ have no host tests and aren't counted). CI runs it on every push, puts the figure in the job's summary, and on main publishes a coverage badge and a latest-release badge to the branch 'badges'. The README shows them next to Gitea's own badge for the workflow. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT --- .gitea/workflows/ci.yml | 26 +++++++++++++++++++--- README.md | 4 ++++ docker/Dockerfile | 2 +- platformio.ini | 11 ++++++++++ scripts/coverage.sh | 21 ++++++++++++++++++ scripts/coverage_badge.py | 46 +++++++++++++++++++++++++++++++++++++++ scripts/coverage_link.py | 4 ++++ 7 files changed, 110 insertions(+), 4 deletions(-) create mode 100755 scripts/coverage.sh create mode 100755 scripts/coverage_badge.py create mode 100644 scripts/coverage_link.py diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 70669fb..2ee7dce 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,5 +1,6 @@ # CI and releases (docs/milestones/R1.md). -# Any push: host tests, then the release firmware and the Debug Build. +# Any push: host tests, the release firmware and the Debug Build, then the tests' +# coverage of lib/; on main, its badge is published to the branch `badges`. # A tag v*: the same, then a Gitea release with the signed Update File. # Run by hand: the release of a tag that exists already (the ones from before CI). # @@ -9,7 +10,7 @@ name: CI on: push: - branches: ['**'] + branches: ['**', '!badges'] # badges holds what CI itself publishes: the coverage badge tags: ['v*'] workflow_dispatch: inputs: @@ -32,7 +33,7 @@ jobs: run: | apt-get update -qq apt-get install -y -qq --no-install-recommends git build-essential openssl >/dev/null - pip install -q --no-cache-dir --root-user-action=ignore platformio + pip install -q --no-cache-dir --root-user-action=ignore platformio gcovr pio --version; df -h /pio | tail -1; ls /pio | head - name: Check out @@ -49,6 +50,25 @@ jobs: if: github.event_name == 'push' run: scripts/ci.sh + - name: Coverage of lib/ by the host tests + if: github.event_name == 'push' + run: scripts/coverage.sh + + # The README's badges are files on a branch of their own, replaced at each push to main. + - name: Publish the badges + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/coverage') + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + rm -rf /tmp/badges && mkdir /tmp/badges + cp .pio/coverage/coverage.svg .pio/coverage/summary.json /tmp/badges/ + scripts/coverage_badge.py --plain release "$(git describe --tags --abbrev=0)" /tmp/badges/release.svg + cd /tmp/badges + git init -q -b badges . + git add . + git -c user.name="roro9stack CI" -c user.email="ci@git.twis.la" commit -q -m "Coverage of ${{ github.ref_name }} at ${{ github.sha }}" + git push -q --force "$(echo "${{ github.server_url }}" | sed "s#://#://ci:${GITEA_TOKEN}@#")/${{ github.repository }}.git" badges + - name: Which release id: release run: | diff --git a/README.md b/README.md index 0270dce..dd52b03 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # roro9stack +[![CI](https://git.twis.la/twisla/roro9stack/actions/workflows/ci.yml/badge.svg?branch=main)](https://git.twis.la/twisla/roro9stack/actions?workflow=ci.yml) [![Coverage of lib/ by the host tests](https://git.twis.la/twisla/roro9stack/raw/branch/badges/coverage.svg)](#build-and-test-local-ci) [![Latest release](https://git.twis.la/twisla/roro9stack/raw/branch/badges/release.svg)](https://git.twis.la/twisla/roro9stack/releases/latest) + A multi-app firmware for the **M5Stack Cardputer ADV** with the **Cap LoRa-1262**. It's a Meshtastic-compatible mesh messenger, plus Wi-Fi tools, IRC, GNSS and more. Licensed GPL-3.0. - Domain language: [CONTEXT.md](CONTEXT.md) @@ -18,6 +20,8 @@ scripts/ci.sh This runs the host-side unit tests (`test/`, `native` environment), then builds the firmware. The output is `.pio/build/cardputer-adv/firmware.factory.bin`. +`scripts/coverage.sh` runs the same tests with coverage counters and writes a line-by-line report to `.pio/coverage/index.html`. The badge above is its figure for `main`: the share of the lines of `lib/` that the host tests run. `lib/` is the logic that compiles on a PC; `lib/SD` (the card's driver) and `src/` (the Apps, the Services, everything that needs the device) have no host tests and aren't in that figure. + The framework is rebuilt with the TLS settings in `platformio.ini` (`custom_sdkconfig`, ADR 0006), so the first build after a fresh checkout takes about 4 minutes; later builds take under a minute. ## CI and releases diff --git a/docker/Dockerfile b/docker/Dockerfile index b67e2b4..7c3220b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends git build-essential \ && rm -rf /var/lib/apt/lists/* -RUN pip install --no-cache-dir platformio +RUN pip install --no-cache-dir platformio gcovr # Toolchains and libraries are cached in a named volume mounted here. RUN mkdir -p /pio && chmod 777 /pio diff --git a/platformio.ini b/platformio.ini index 0a10b9a..2b3c541 100644 --- a/platformio.ini +++ b/platformio.ini @@ -46,3 +46,14 @@ build_flags = platform = native lib_ldf_mode = deep+ build_flags = -std=gnu++17 + +; The same tests, built to count which lines of lib/ they run (scripts/coverage.sh). +[env:native-coverage] +extends = env:native +build_flags = + ${env:native.build_flags} + --coverage + -O0 +extra_scripts = + ${env.extra_scripts} + pre:scripts/coverage_link.py diff --git a/scripts/coverage.sh b/scripts/coverage.sh new file mode 100755 index 0000000..40c7a97 --- /dev/null +++ b/scripts/coverage.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# How much of lib/ the host tests run: builds them with coverage counters, runs them, and reports. +# Usage: scripts/coverage.sh [out folder, default .pio/coverage] +# Out: summary.json (gcovr), coverage.svg (the README's badge), index.html (line by line). +# What it measures: the lines of lib/ that compile on a PC. Not lib/SD (the card's driver) and not +# src/ (the Apps, the Services, everything that needs the device): those have no host tests. +set -euo pipefail +source "$(dirname "$0")/_docker.sh" +DOCKER_EXTRA=() +OUT="${1:-.pio/coverage}" + +run_in_container bash -c ' + set -e + git config --global --add safe.directory "$PWD" + rm -rf .pio/build/native-coverage "'"$OUT"'" + mkdir -p "'"$OUT"'" + pio test -e native-coverage | tail -1 + gcovr -r . --filter "lib/" --object-directory .pio/build/native-coverage \ + --json-summary "'"$OUT"'/summary.json" --html-details "'"$OUT"'/index.html" --print-summary | tail -4 + python3 scripts/coverage_badge.py "'"$OUT"'/summary.json" "'"$OUT"'/coverage.svg" +' diff --git a/scripts/coverage_badge.py b/scripts/coverage_badge.py new file mode 100755 index 0000000..eb08c7b --- /dev/null +++ b/scripts/coverage_badge.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Draws the README's coverage badge from gcovr's summary. + +Usage: scripts/coverage_badge.py + scripts/coverage_badge.py --plain