Skip to content

chore: add coverage tracking for multi-platform-controller #354

chore: add coverage tracking for multi-platform-controller

chore: add coverage tracking for multi-platform-controller #354

Workflow file for this run

name: Generate Konflux Coverage Dashboard
on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: '0 3 * * *' # Daily at 03:00 UTC
workflow_dispatch:
jobs:
generate-coverage:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read # Allow reading PR information
steps:
- name: Checkout this repo (dashboard)
uses: actions/checkout@v4
with:
# For pull requests, this will checkout the PR branch with merge commit
# For push events, this will checkout the pushed branch
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: false # Disable caching since this isn't a Go project
- name: Install tools
run: |
sudo apt-get update
sudo apt-get install -y jq
pip install yq
- name: Install and setup envtest
run: |
# Install setup-envtest
go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
# Setup envtest binaries and get the path
ENVTEST_ASSETS_DIR=$(setup-envtest use -p path)
echo "KUBEBUILDER_ASSETS=$ENVTEST_ASSETS_DIR" >> $GITHUB_ENV
echo "Envtest assets installed at: $ENVTEST_ASSETS_DIR"
- name: Prepare workspace
run: mkdir workspace
- name: Checkout gh-pages
uses: actions/checkout@v4
with:
ref: gh-pages
path: gh-pages
- name: Clone repos and calculate coverage
run: |
cd workspace
echo "[" > ../_temp_coverage.json
OLD_COVERAGE_JSON=$(cat ../gh-pages/coverage.json || echo "[]")
# Iterate through all repository configuration files
for config_file in ../repos/*.yaml; do
REPO=$(yq -r '.name' "$config_file")
REPO_NAME=$(basename "$REPO")
echo "→ Processing $REPO (from $(basename "$config_file"))..."
# Get exclude patterns BEFORE entering repo directory
EXCLUDE_DIRS_ARRAY=$(yq '.exclude_dirs // []' "$config_file")
EXCLUDE_FILES=$(yq '.exclude_files // []' "$config_file")
echo "→ Cloning $REPO..."
git clone --depth 1 "https://github.com/$REPO.git"
cd "$REPO_NAME"
# Build the exclude pattern more carefully
EXCLUDE_PATTERN=""
if [ "$EXCLUDE_DIRS_ARRAY" != "[]" ]; then
# Process each exclude pattern individually
PATTERN_COUNT=$(echo "$EXCLUDE_DIRS_ARRAY" | yq 'length')
PATTERNS=""
for k in $(seq 0 $((PATTERN_COUNT - 1))); do
PATTERN=$(echo "$EXCLUDE_DIRS_ARRAY" | yq ".[$k]" | tr -d '"')
echo " Processing exclude pattern: '$PATTERN'"
# Handle different pattern types
case "$PATTERN" in
*/*)
# Pattern with slashes - treat as path pattern
if [[ "$PATTERN" == /* ]]; then
# Starts with slash, use as-is but escape special chars
ESCAPED_PATTERN=$(echo "$PATTERN" | sed 's/[[\.*^$()+{}|]/\\&/g')
else
# Add leading slash and treat trailing slash specially
if [[ "$PATTERN" == */ ]]; then
# Ends with slash - match directory component
DIR_NAME=${PATTERN%/}
ESCAPED_PATTERN="/${DIR_NAME}(/|$)"
else
# Has slash but doesn't end with one
ESCAPED_PATTERN="/$(echo "$PATTERN" | sed 's/[[\.*^$()+{}|]/\\&/g')"
fi
fi
;;
*)
# Simple directory name - match as path component
ESCAPED_PATTERN="/${PATTERN}(/|$)"
;;
esac
echo " Escaped pattern: '$ESCAPED_PATTERN'"
if [ -z "$PATTERNS" ]; then
PATTERNS="$ESCAPED_PATTERN"
else
PATTERNS="$PATTERNS|$ESCAPED_PATTERN"
fi
done
EXCLUDE_PATTERN="$PATTERNS"
echo " Final exclude pattern: '$EXCLUDE_PATTERN'"
fi
# Get packages and apply exclusions
ALL_PACKAGES=$(go list ./... 2>/dev/null || true)
echo " All packages found:"
echo "$ALL_PACKAGES" | while read pkg; do echo " $pkg"; done
if [ -n "$EXCLUDE_PATTERN" ] && [ -n "$ALL_PACKAGES" ]; then
echo " Applying exclusion pattern: $EXCLUDE_PATTERN"
INCLUDED_PACKAGES=$(echo "$ALL_PACKAGES" | grep -vE "$EXCLUDE_PATTERN" || true)
echo " Packages after exclusion:"
echo "$INCLUDED_PACKAGES" | while read pkg; do [ -n "$pkg" ] && echo " $pkg"; done
echo " Original packages: $(echo "$ALL_PACKAGES" | wc -l)"
echo " Included packages: $(echo "$INCLUDED_PACKAGES" | wc -l)"
else
INCLUDED_PACKAGES="$ALL_PACKAGES"
echo " No exclusion pattern applied"
fi
STATUS="ok"
COVERAGE="0.0"
if [ -n "$INCLUDED_PACKAGES" ]; then
echo " Running tests with coverage..."
# Fail fast - let test failures fail the workflow for visibility
# Filter out packages with no test files to avoid covdata errors
TESTABLE_PACKAGES=""
for pkg in $INCLUDED_PACKAGES; do
if go list -f '{{if or .TestGoFiles .XTestGoFiles}}{{.ImportPath}}{{end}}' $pkg 2>/dev/null | grep -q .; then
TESTABLE_PACKAGES="$TESTABLE_PACKAGES $pkg"
fi
done
if [ -z "$TESTABLE_PACKAGES" ]; then
echo " ❌ No packages with test files found"
STATUS="failed"
COVERAGE=null
else
go test -coverprofile=coverage_raw.out $TESTABLE_PACKAGES
fi
if [ -f coverage_raw.out ]; then
cp coverage_raw.out coverage.out
# Remove excluded files from coverage
FILE_COUNT=$(echo "$EXCLUDE_FILES" | yq 'length')
for j in $(seq 0 $((FILE_COUNT - 1))); do
FILE=$(echo "$EXCLUDE_FILES" | yq ".[$j]" | tr -d '"')
echo " Removing $FILE from coverage"
sed -i "/$FILE/d" coverage.out || true
done
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | tr -d '%')
# Extract per-package coverage by parsing coverage profile directly
# This gives statement-weighted coverage, not function-count average
PACKAGES_JSON=$(awk '
NR==1 { next } # Skip "mode: set" line
{
# Parse: file.go:start.col,end.col numStmts covered
split($1, parts, ":")
file = parts[1]
numStmts = $2
covered = ($3 > 0) ? numStmts : 0
# Extract package path (everything before last /)
n=split(file, pathParts, "/")
pkg=""
for(i=1; i<n; i++) {
if(i>1) pkg = pkg "/"
pkg = pkg pathParts[i]
}
# Accumulate covered and total statements per package
pkgTotal[pkg] += numStmts
pkgCovered[pkg] += covered
}
END {
printf "["
first=1
for(pkg in pkgTotal) {
if(!first) printf ","
cov = (pkgTotal[pkg] > 0) ? (pkgCovered[pkg] / pkgTotal[pkg] * 100) : 0
printf "{\"package\":\"%s\",\"coverage\":%.1f}", pkg, cov
first=0
}
printf "]"
}' coverage.out)
# Generate HTML report
go tool cover -html=coverage.out -o coverage.html
mkdir -p ../../gh-pages/coverage/$REPO
cp coverage.html ../../gh-pages/coverage/$REPO/index.html
else
echo " ❌ No coverage file generated - tests may not have run properly"
STATUS="failed"
COVERAGE=null
PACKAGES_JSON="[]"
fi
else
echo " ❌ No testable packages found"
STATUS="failed"
COVERAGE=null
PACKAGES_JSON="[]"
fi
echo " → $REPO coverage: $COVERAGE% (status: $STATUS)"
echo " {\"repo\": \"$REPO\", \"coverage\": $COVERAGE, \"status\": \"$STATUS\", \"packages\": $PACKAGES_JSON }," >> ../../_temp_coverage.json
cd ..
done
cd ..
sed -i '$ s/,$//' _temp_coverage.json
echo "]" >> _temp_coverage.json
- name: Wrap coverage.json with run_url
run: |
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
jq -n \
--arg run_url "$RUN_URL" \
--argjson data "$(cat _temp_coverage.json)" \
'{run_url: $run_url, data: $data}' > coverage.json
- name: Commit and push updated coverage.json
# Only push to gh-pages from main branch pushes and scheduled runs (not on PRs)
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
run: |
cp coverage.json gh-pages/coverage.json
cp index.html gh-pages/index.html
cd gh-pages
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git add coverage.json coverage/ index.html
if git diff --cached --quiet; then
echo "No changes to commit"
else
git commit -m "Update coverage data on $(date --utc)"
git push origin gh-pages
fi