Skip to content
Open
51 changes: 51 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Coverage

on:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: write

jobs:
coverage:
name: Code Coverage
timeout-minutes: 60
runs-on: ${{ github.repository_owner == 'intel' && 'intel-ubuntu-latest' || 'ubuntu-latest' }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Dart
uses: dart-lang/setup-dart@v1

- name: Install dependencies
run: tool/gh_actions/install_dependencies.sh

- name: Install Icarus Verilog
run: tool/gh_actions/install_iverilog.sh

- name: Generate coverage and badge
run: tool/generate_coverage.sh --generate-svg

- name: Commit coverage badge
run: |
BRANCH_NAME="${{ github.ref_name }}"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"

# Fetch and checkout badges branch
git fetch origin badges:badges 2>/dev/null || git checkout --orphan badges
git checkout badges 2>/dev/null || true

# Clean and copy badge
git rm -rf . 2>/dev/null || true
mkdir -p coverage
cp /tmp/coverage-badge.svg "coverage/${BRANCH_NAME}.svg"

git add "coverage/${BRANCH_NAME}.svg"
git diff --staged --quiet || git commit -m "Update coverage badge for ${BRANCH_NAME} [skip ci]"

git push origin badges --force
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ doc/api/

# Other stuff
tmp_test/
coverage/
*.sv
*.vcd
*_fsm.md
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ dependencies:

dev_dependencies:
benchmark_harness: ^2.2.0
coverage: ^1.10.0
yaml: ^3.1.1
122 changes: 112 additions & 10 deletions tool/generate_coverage.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
#!/bin/bash

# Copyright (C) 2022-2023 Intel Corporation
# Copyright (C) 2022-2026 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause
#
# generate_coverage.sh
# Determines code coverage by tests and generates an HTML representation.
# and SVG badge with additional arguments for CI integration.
#
# 2022 May 5
# Author: Max Korbel <max.korbel@intel.com>
# Author: Maifee Ul Asad<maifeeulasad@gmail.com>

### WARNING ###
# The "x" option outputs all script commands. This allows you to track
Expand All @@ -16,15 +18,115 @@ set -euxo pipefail

#=============#

declare -r coverage_dir='build/coverage'
declare -r html_dir="${coverage_dir}/genhtml"
# Parse arguments
GENERATE_SVG=false
for arg in "$@"; do
case $arg in
--generate-svg)
GENERATE_SVG=true
shift
;;
*)
;;
esac
done

# requires enabling "coverage":
# > dart pub global activate coverage
dart pub global run coverage:test_with_coverage --branch-coverage --out=${coverage_dir}
# Remove old coverage data
rm -rf coverage

# requires installing "lcov":
# > sudo apt install lcov
genhtml --output-directory=${html_dir} --rc lcov_branch_coverage=1 ${coverage_dir}/lcov.info
# Run tests with coverage
dart test --coverage=coverage || true

printf '\n%s\n\n' "Open ${html_dir}/index.html to review code coverage results."
# Check if coverage was generated
if [ ! -d "coverage" ]; then
echo "Error: Coverage directory not created"
exit 1
fi

# Format to LCOV
dart run coverage:format_coverage \
--lcov \
--in=coverage \
--out=coverage/lcov.info \
--packages=.dart_tool/package_config.json \
--report-on=lib

# Generate HTML report
genhtml -o coverage/html coverage/lcov.info --branch-coverage
printf '\n%s\n\n' "Open coverage/html/index.html to review code coverage results."

# Install lcov if needed
if ! command -v lcov &> /dev/null; then
if [ -n "${CI:-}" ] || [ -n "${GITHUB_ACTIONS:-}" ]; then
sudo apt update -y
sudo apt install -y lcov
fi
fi

# Extract coverage percentage
if command -v lcov &> /dev/null; then
SUMMARY=$(lcov --summary coverage/lcov.info 2>&1 | grep -E "lines\.*:")
PERCENT=$(echo "$SUMMARY" | grep -oP '\d+\.\d+' | head -1)
echo "Coverage: ${PERCENT}%"
else
PERCENT="Generation Failed"
echo "Coverage generation failed"
fi

# Determine color
if (( $(echo "$PERCENT >= 90" | bc -l) )); then
COLOR="#4c1" # bright green
elif (( $(echo "$PERCENT >= 80" | bc -l) )); then
COLOR="#97ca00" # green
elif (( $(echo "$PERCENT >= 70" | bc -l) )); then
COLOR="#dfb317" # yellow
elif (( $(echo "$PERCENT >= 60" | bc -l) )); then
COLOR="#fe7d37" # orange
else
COLOR="#e05d44" # red
fi
if [ "$PERCENT" = "Generation Failed" ]; then
COLOR="#e05d44" # red
fi

# Calculate dimensions
if [ "$PERCENT" = "Generation Failed" ]; then
TEXT="Error: coverage collection failed!"
else
TEXT="${PERCENT}%"
fi
TEXT_LEN=${#TEXT}
TEXT_WIDTH=$((TEXT_LEN * 63))
LABEL_WIDTH=63
VALUE_WIDTH=$((TEXT_WIDTH / 10 + 10))
TOTAL_WIDTH=$((LABEL_WIDTH + VALUE_WIDTH))

# Generate SVG badge only if requested
if [ "$GENERATE_SVG" = true ]; then
# Style credit goes to shields.io
cat > /tmp/coverage-badge.svg << EOFSVG
<svg xmlns="http://www.w3.org/2000/svg" width="${TOTAL_WIDTH}" height="20" role="img" aria-label="coverage: ${TEXT}">
<title>coverage: ${TEXT}</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="r">
<rect width="${TOTAL_WIDTH}" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#r)">
<rect width="${LABEL_WIDTH}" height="20" fill="#555"/>
<rect x="${LABEL_WIDTH}" width="${VALUE_WIDTH}" height="20" fill="${COLOR}"/>
<rect width="${TOTAL_WIDTH}" height="20" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
<text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">coverage</text>
<text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">coverage</text>
<text aria-hidden="true" x="$((LABEL_WIDTH * 10 + VALUE_WIDTH * 5))" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="${TEXT_WIDTH}">${TEXT}</text>
<text x="$((LABEL_WIDTH * 10 + VALUE_WIDTH * 5))" y="140" transform="scale(.1)" fill="#fff" textLength="${TEXT_WIDTH}">${TEXT}</text>
</g>
</svg>
EOFSVG

echo "Generated SVG badge at /tmp/coverage-badge.svg"
fi