From 5b18ba176e3b89965fa874b918a045e5a44cbe0f Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Tue, 2 Jun 2026 11:33:28 -0400 Subject: [PATCH 1/3] chore(ci): add emoji status to coverage comments, update check-coverage copy --- .github/workflows/ci.yml | 20 +++++++++++++++----- scripts/check-coverage.py | 8 +++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58123e6..c2eee55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,17 @@ jobs: id: summary run: | output=$(forge coverage --no-match-coverage "(\.t\.sol|Test\.sol)$" 2>/dev/null | grep "^|" || echo "| (no coverage data) |") + # Compute a status line: 🟢 if Total row shows 100% functions, 🟡 otherwise + total=$(echo "$output" | grep "^| Total" || echo "") + if echo "$total" | grep -qE "100\.00%[^|]+100\.00%[^|]+100\.00%[^|]+100\.00%"; then + status="🟢 **100% coverage** — all lines, statements, branches, and functions hit." + elif [ -z "$total" ]; then + status="⚠️ Coverage data unavailable." + else + status="🟡 Near-full coverage — one or more metrics below 100%." + fi { + echo "status=$status" echo "output<<__EOF__" echo "$output" echo "__EOF__" @@ -91,7 +101,9 @@ jobs: edit-mode: replace body: | - ### Forge Coverage (`src/lib/`) + ### 📊 Forge Coverage (`src/lib/`) + + ${{ steps.summary.outputs.status }} ``` ${{ steps.summary.outputs.output }} @@ -149,11 +161,9 @@ jobs: ### Interface Coverage - ${{ steps.check.outputs.exit_code == '0' && '✅ All interface functions have test coverage.' || '❌ Interface functions with no test coverage found.' }} + ${{ steps.check.outputs.exit_code == '0' && '✅ All mock functions have test coverage.' || '❌ Mock functions with no test coverage found.' }} - ``` - ${{ steps.check.outputs.output }} - ``` + ${{ steps.check.outputs.exit_code != '0' && format('```\n{0}\n```', steps.check.outputs.output) || '' }} - name: Fail if gaps found if: steps.check.outputs.exit_code != '0' diff --git a/scripts/check-coverage.py b/scripts/check-coverage.py index f883558..4956326 100644 --- a/scripts/check-coverage.py +++ b/scripts/check-coverage.py @@ -34,7 +34,8 @@ def generate_lcov() -> None: text=True, ) if result.returncode != 0: - print(f"forge coverage failed:\n{result.stderr}", file=sys.stderr) + print(f"forge coverage failed: +{result.stderr}", file=sys.stderr) sys.exit(1) @@ -76,14 +77,15 @@ def main() -> int: if uncovered: total = sum(len(fns) for fns in uncovered.values()) - print(f"Mock functions with no test coverage ({total}):\n") + print(f"Mock functions with no test coverage ({total}): +") for source_file, fns in sorted(uncovered.items()): rel = source_file.replace(str(ROOT) + "/", "") for fn in fns: print(f" {rel}: {fn}") return 1 - print("Coverage OK — all mock functions have test coverage.") + print("All mock functions have test coverage.") return 0 From 2ea279b606cdb85cd538bf8a9330f5c0f55c1405 Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Tue, 2 Jun 2026 15:41:03 -0400 Subject: [PATCH 2/3] fix(ci): fix f-string, remove mock copy, render coverage as GFM table with per-row emoji --- .github/workflows/ci.yml | 61 ++++++++++++++++++++++++++++++--------- scripts/check-coverage.py | 9 +++--- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2eee55..fab3274 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,20 +60,57 @@ jobs: - name: Capture coverage summary id: summary run: | - output=$(forge coverage --no-match-coverage "(\.t\.sol|Test\.sol)$" 2>/dev/null | grep "^|" || echo "| (no coverage data) |") - # Compute a status line: 🟢 if Total row shows 100% functions, 🟡 otherwise - total=$(echo "$output" | grep "^| Total" || echo "") - if echo "$total" | grep -qE "100\.00%[^|]+100\.00%[^|]+100\.00%[^|]+100\.00%"; then - status="🟢 **100% coverage** — all lines, statements, branches, and functions hit." - elif [ -z "$total" ]; then + raw=$(forge coverage --no-match-coverage "(\.t\.sol|Test\.sol)$" 2>/dev/null | grep "^|" || echo "") + + if [ -z "$raw" ]; then status="⚠️ Coverage data unavailable." + table="_(no coverage data)_" else - status="🟡 Near-full coverage — one or more metrics below 100%." + # Transform forge output into a proper GFM table: + # - strip test/lib/mocks/ and src/lib/ path prefixes + # - show percentages only (drop raw hit counts) + # - add per-row emoji: ✅ for 100%, ⚠️ otherwise + table=$(echo "$raw" | awk -v CHECK="✅" -v WARN="⚠️" -F'|' ' + BEGIN { + print "| File | Lines | Stmts | Branches | Funcs |" + print "|------|-------|-------|----------|-------|" + } + /\+/ { next } + /% Lines/ { next } + NF < 5 { next } + { + file = $2 + gsub(/^[[:space:]]+|[[:space:]]+$/, "", file) + if (file == "") next + for (i = 3; i <= 6; i++) { + match($i, /[0-9]+\.[0-9]+%/) + pct[i-2] = substr($i, RSTART, RLENGTH) + } + gsub(/^test\/lib\/mocks\//, "", file) + gsub(/^src\/lib\//, "", file) + all100 = (pct[1]=="100.00%" && pct[2]=="100.00%" && pct[3]=="100.00%" && pct[4]=="100.00%") + if (file == "Total") { + printf "| **%s** | **%s** | **%s** | **%s** | **%s** |\n", file, pct[1], pct[2], pct[3], pct[4] + } else if (all100) { + printf "| %s %s | %s | %s | %s | %s |\n", CHECK, file, pct[1], pct[2], pct[3], pct[4] + } else { + printf "| %s %s | %s | %s | %s | %s |\n", WARN, file, pct[1], pct[2], pct[3], pct[4] + } + } + ') + + total=$(echo "$raw" | grep "^| Total" || echo "") + if echo "$total" | grep -qE "100\.00%[^|]+100\.00%[^|]+100\.00%[^|]+100\.00%"; then + status="🟢 **100% coverage** — all lines, statements, branches, and functions hit." + else + status="🟡 Near-full coverage — one or more metrics below 100%." + fi fi + { echo "status=$status" - echo "output<<__EOF__" - echo "$output" + echo "table<<__EOF__" + printf "%s\n" "$table" echo "__EOF__" } >> "$GITHUB_OUTPUT" @@ -105,9 +142,7 @@ jobs: ${{ steps.summary.outputs.status }} - ``` - ${{ steps.summary.outputs.output }} - ``` + ${{ steps.summary.outputs.table }} Full report: [download artifact](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}). To browse locally: `make coverage` (runs forge coverage + genhtml + opens the HTML report). @@ -161,7 +196,7 @@ jobs: ### Interface Coverage - ${{ steps.check.outputs.exit_code == '0' && '✅ All mock functions have test coverage.' || '❌ Mock functions with no test coverage found.' }} + ${{ steps.check.outputs.exit_code == '0' && '✅ All interface functions have test coverage.' || '❌ Interface functions with no test coverage found.' }} ${{ steps.check.outputs.exit_code != '0' && format('```\n{0}\n```', steps.check.outputs.output) || '' }} diff --git a/scripts/check-coverage.py b/scripts/check-coverage.py index 4956326..214a5cc 100644 --- a/scripts/check-coverage.py +++ b/scripts/check-coverage.py @@ -34,8 +34,7 @@ def generate_lcov() -> None: text=True, ) if result.returncode != 0: - print(f"forge coverage failed: -{result.stderr}", file=sys.stderr) + print("forge coverage failed:\n" + result.stderr, file=sys.stderr) sys.exit(1) @@ -77,15 +76,15 @@ def main() -> int: if uncovered: total = sum(len(fns) for fns in uncovered.values()) - print(f"Mock functions with no test coverage ({total}): -") + print(f"Functions with no test coverage ({total}):") + print() for source_file, fns in sorted(uncovered.items()): rel = source_file.replace(str(ROOT) + "/", "") for fn in fns: print(f" {rel}: {fn}") return 1 - print("All mock functions have test coverage.") + print("All interface functions have test coverage.") return 0 From d5320dee4de95488b51bb46d0313b0372cb269b4 Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Tue, 2 Jun 2026 15:57:04 -0400 Subject: [PATCH 3/3] =?UTF-8?q?chore(ci):=20use=203-tier=20color=20thresho?= =?UTF-8?q?lds=20for=20coverage=20rows=20(=F0=9F=9F=A2=E2=89=A599=20?= =?UTF-8?q?=F0=9F=9F=A1=E2=89=A595=20=F0=9F=94=B4<95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fab3274..77bcfe0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,11 +66,8 @@ jobs: status="⚠️ Coverage data unavailable." table="_(no coverage data)_" else - # Transform forge output into a proper GFM table: - # - strip test/lib/mocks/ and src/lib/ path prefixes - # - show percentages only (drop raw hit counts) - # - add per-row emoji: ✅ for 100%, ⚠️ otherwise - table=$(echo "$raw" | awk -v CHECK="✅" -v WARN="⚠️" -F'|' ' + # Per-row color: 🟢 ≥99% on all metrics, 🟡 ≥95%, 🔴 below 95% + table=$(echo "$raw" | awk -v GREEN="🟢" -v YELLOW="🟡" -v RED="🔴" -F'|' ' BEGIN { print "| File | Lines | Stmts | Branches | Funcs |" print "|------|-------|-------|----------|-------|" @@ -88,23 +85,31 @@ jobs: } gsub(/^test\/lib\/mocks\//, "", file) gsub(/^src\/lib\//, "", file) - all100 = (pct[1]=="100.00%" && pct[2]=="100.00%" && pct[3]=="100.00%" && pct[4]=="100.00%") if (file == "Total") { printf "| **%s** | **%s** | **%s** | **%s** | **%s** |\n", file, pct[1], pct[2], pct[3], pct[4] - } else if (all100) { - printf "| %s %s | %s | %s | %s | %s |\n", CHECK, file, pct[1], pct[2], pct[3], pct[4] } else { - printf "| %s %s | %s | %s | %s | %s |\n", WARN, file, pct[1], pct[2], pct[3], pct[4] + min_pct = 999 + for (j = 1; j <= 4; j++) { + val = pct[j]; gsub(/%/, "", val); val += 0 + if (val < min_pct) min_pct = val + } + if (min_pct >= 99) emoji = GREEN + else if (min_pct >= 95) emoji = YELLOW + else emoji = RED + printf "| %s %s | %s | %s | %s | %s |\n", emoji, file, pct[1], pct[2], pct[3], pct[4] } } ') + # Status line uses same thresholds applied to Total row minimum total=$(echo "$raw" | grep "^| Total" || echo "") - if echo "$total" | grep -qE "100\.00%[^|]+100\.00%[^|]+100\.00%[^|]+100\.00%"; then - status="🟢 **100% coverage** — all lines, statements, branches, and functions hit." - else - status="🟡 Near-full coverage — one or more metrics below 100%." - fi + min_total=$(echo "$total" | grep -oE "[0-9]+\.[0-9]+%" | sed 's/%//' | sort -n | head -1) + status=$(echo "$min_total" | awk '{ + if ($1 >= 99) print "🟢 ≥99% across all metrics." + else if ($1 >= 95) print "🟡 ≥95% across all metrics \xe2\x80\x94 some metrics below 99%." + else print "🔴 Below 95% on one or more metrics." + }') + [ -z "$status" ] && status="⚠️ Coverage data unavailable." fi {