diff --git a/.github/scripts/coverage_history.py b/.github/scripts/coverage_history.py index a0461071..33aeb235 100755 --- a/.github/scripts/coverage_history.py +++ b/.github/scripts/coverage_history.py @@ -32,6 +32,7 @@ render regenerate the human-readable COVERAGE.md from that CSV compare render a markdown coverage diff against the last recorded entry, for posting on a pull request + badge emit shields.io endpoint JSON for the latest recorded total Standard library only, so it runs on any CI image without extra setup. """ @@ -563,6 +564,55 @@ def delta(current: float, module: str, field: str) -> str: print(body) +# shields.io endpoint schema: https://shields.io/badges/endpoint-badge +BADGE_SCHEMA_VERSION = 1 + +# Floor percentages, highest first. Anything under the last floor is red. +BADGE_COLORS = ( + (90.0, "brightgreen"), + (75.0, "green"), + (60.0, "yellowgreen"), + (40.0, "yellow"), + (20.0, "orange"), +) + + +def badge_color(percentage: float) -> str: + for floor, color in BADGE_COLORS: + if percentage >= floor: + return color + return "red" + + +def cmd_badge(args: argparse.Namespace) -> None: + """Write shields.io endpoint JSON for one suite's latest TOTAL row. + + Read by an endpoint badge in README.md. The file is published on the + coverage history branch rather than main, so refreshing the badge costs + contributors nothing (see coverage-history.yml for why that matters). + + A suite with no recorded entry yet renders as "unknown" rather than + failing, so the badge degrades quietly instead of breaking the README. + """ + total = latest_entry(read_history(args.csv), args.suite).get("TOTAL") + if total is None: + payload = {"message": "unknown", "color": "lightgrey"} + else: + percentage = float(total[args.metric]) + payload = { + "message": f"{percentage:.0f}%", + "color": badge_color(percentage), + } + + payload = {"schemaVersion": BADGE_SCHEMA_VERSION, "label": args.label, **payload} + text = json.dumps(payload, indent=2) + "\n" + if args.out: + with open(args.out, "w", encoding="utf-8") as handle: + handle.write(text) + else: + sys.stdout.write(text) + + def add_common(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--suite", @@ -630,6 +680,30 @@ def main() -> None: ) compare.set_defaults(func=cmd_compare) + badge = subparsers.add_parser( + "badge", help="emit shields.io endpoint JSON for the latest total" + ) + badge.add_argument( + "--suite", + choices=sorted(SUITES), + default="instrumentation", + help="which suite to report (default: instrumentation)", + ) + badge.add_argument("--csv", default=DEFAULT_CSV) + badge.add_argument("--out", default="", help="write here instead of stdout") + badge.add_argument( + "--label", + default="coverage (instrumented)", + help="text on the left half of the badge", + ) + badge.add_argument( + "--metric", + default="line_pct", + choices=("line_pct", "branch_pct", "instruction_pct"), + help="which percentage to display (default: line_pct)", + ) + badge.set_defaults(func=cmd_badge) + args = parser.parse_args() args.func(args) diff --git a/.github/scripts/push_coverage_history.sh b/.github/scripts/push_coverage_history.sh index 0d6baa7e..40795851 100755 --- a/.github/scripts/push_coverage_history.sh +++ b/.github/scripts/push_coverage_history.sh @@ -35,17 +35,25 @@ DATA_DIR="${DATA_DIR:-coverage-data}" DATA_BRANCH="${DATA_BRANCH:-coverage-history}" SCRIPT="${SCRIPT:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/coverage_history.py}" +# Every generated file published on the data branch. COVERAGE.md and the badge +# are both derived from history.csv, so they are regenerated, not merged. +FILES=(history.csv COVERAGE.md coverage-badge.json) + cd "$DATA_DIR" git config user.name 'googlemaps-bot' git config user.email 'googlemaps-bot@google.com' -if git diff --quiet -- history.csv COVERAGE.md; then +# Stage first and compare the index: a newly generated file is untracked, and +# "git diff" does not see untracked files, so checking before staging would +# skip the commit the first time a new output appears. +git add -- "${FILES[@]}" + +if git diff --cached --quiet -- "${FILES[@]}"; then echo "Coverage history unchanged; nothing to commit." exit 0 fi -git add history.csv COVERAGE.md git commit -m "$MESSAGE" for attempt in 1 2 3; do @@ -65,13 +73,15 @@ for attempt in 1 2 3; do python3 "$SCRIPT" merge --csv history.csv --ours "$OURS" python3 "$SCRIPT" render --csv history.csv --out COVERAGE.md + python3 "$SCRIPT" badge --csv history.csv --out coverage-badge.json + + git add -- "${FILES[@]}" - if git diff --quiet -- history.csv COVERAGE.md; then + if git diff --cached --quiet -- "${FILES[@]}"; then echo "Our rows are already on $DATA_BRANCH; nothing left to push." exit 0 fi - git add history.csv COVERAGE.md git commit -m "$MESSAGE" done diff --git a/.github/workflows/coverage-history.yml b/.github/workflows/coverage-history.yml index f8a036c6..c0647ac6 100644 --- a/.github/workflows/coverage-history.yml +++ b/.github/workflows/coverage-history.yml @@ -35,7 +35,7 @@ concurrency: cancel-in-progress: false env: - # Branch holding history.csv and COVERAGE.md. + # Branch holding history.csv, COVERAGE.md and coverage-badge.json. DATA_BRANCH: coverage-history # How long to wait for the pull request's emulator run to finish before # giving up and recording unit coverage only. instrumentation-test.yml takes @@ -111,6 +111,8 @@ jobs: --commit "$COMMIT_SHA" --pr "$PR_NUMBER" --subject "$SUBJECT" python3 .github/scripts/coverage_history.py render \ --csv coverage-data/history.csv --out coverage-data/COVERAGE.md + python3 .github/scripts/coverage_history.py badge \ + --csv coverage-data/history.csv --out coverage-data/coverage-badge.json - name: Push unit coverage run: | @@ -189,6 +191,8 @@ jobs: --commit "$COMMIT_SHA" --pr "$PR_NUMBER" --subject "$SUBJECT" python3 .github/scripts/coverage_history.py render \ --csv coverage-data/history.csv --out coverage-data/COVERAGE.md + python3 .github/scripts/coverage_history.py badge \ + --csv coverage-data/history.csv --out coverage-data/coverage-badge.json - name: Push instrumentation coverage if: steps.emulator.outputs.run_id != '' diff --git a/README.md b/README.md index aebcb08b..8b1d6a66 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ![Release](https://github.com/googlemaps/android-maps-compose/workflows/Release/badge.svg) ![Stable](https://img.shields.io/badge/stability-stable-green) [![Tests/Build](https://github.com/googlemaps/android-maps-compose/actions/workflows/test.yml/badge.svg)](https://github.com/googlemaps/android-maps-compose/actions/workflows/test.yml) +[![Instrumented coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fgooglemaps%2Fandroid-maps-compose%2Fcoverage-history%2Fcoverage-badge.json)](https://github.com/googlemaps/android-maps-compose/blob/coverage-history/COVERAGE.md) ![Contributors](https://img.shields.io/github/contributors/googlemaps/android-maps-compose?color=green) [![License](https://img.shields.io/github/license/googlemaps/android-maps-compose?color=blue)][license]