Files
Charon/scripts/go-test-coverage.sh

185 lines
6.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# ⚠️ DEPRECATED: This script is deprecated and will be removed in v2.0.0
# Please use: .github/skills/scripts/skill-runner.sh test-backend-coverage
# For more info: docs/AGENT_SKILLS_MIGRATION.md
echo "⚠️ WARNING: This script is deprecated and will be removed in v2.0.0" >&2
echo " Please use: .github/skills/scripts/skill-runner.sh test-backend-coverage" >&2
echo " For more info: docs/AGENT_SKILLS_MIGRATION.md" >&2
echo "" >&2
sleep 1
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BACKEND_DIR="$ROOT_DIR/backend"
COVERAGE_FILE="$BACKEND_DIR/coverage.txt"
MIN_COVERAGE="${CHARON_MIN_COVERAGE:-${CPM_MIN_COVERAGE:-85}}"
# Perf asserts are sensitive to -race overhead; loosen defaults for hook runs
export PERF_MAX_MS_GETSTATUS_P95="${PERF_MAX_MS_GETSTATUS_P95:-25ms}"
export PERF_MAX_MS_GETSTATUS_P95_PARALLEL="${PERF_MAX_MS_GETSTATUS_P95_PARALLEL:-50ms}"
export PERF_MAX_MS_LISTDECISIONS_P95="${PERF_MAX_MS_LISTDECISIONS_P95:-75ms}"
# trap 'rm -f "$COVERAGE_FILE"' EXIT
cd "$BACKEND_DIR"
# Packages to exclude from coverage (main packages and infrastructure code)
# These are entrypoints and initialization code that don't benefit from unit tests
EXCLUDE_PACKAGES=(
"github.com/Wikid82/charon/backend/cmd/api"
"github.com/Wikid82/charon/backend/cmd/seed"
"github.com/Wikid82/charon/backend/internal/trace"
"github.com/Wikid82/charon/backend/integration"
)
# Try to run tests to produce coverage file; some toolchains may return a non-zero
# exit if certain coverage tooling is unavailable (e.g. covdata) while still
# producing a usable coverage file. Capture the status so we can report real
# test failures after the coverage check.
# Note: Using -v for verbose output and -race for race detection
GO_TEST_STATUS=0
TEST_OUTPUT_FILE=$(mktemp)
trap 'rm -f "$TEST_OUTPUT_FILE"' EXIT
if command -v gotestsum &> /dev/null; then
if ! gotestsum --format pkgname -- -race -mod=readonly -coverprofile="$COVERAGE_FILE" ./... 2>&1 | tee "$TEST_OUTPUT_FILE"; then
GO_TEST_STATUS=$?
fi
else
if ! go test -race -v -mod=readonly -coverprofile="$COVERAGE_FILE" ./... 2>&1 | tee "$TEST_OUTPUT_FILE"; then
GO_TEST_STATUS=$?
fi
fi
if [ "$GO_TEST_STATUS" -ne 0 ]; then
echo "Warning: go test returned non-zero (status ${GO_TEST_STATUS}); checking coverage file presence"
echo ""
echo "============================================"
echo "FAILED TEST SUMMARY:"
echo "============================================"
grep -E "(FAIL:|--- FAIL:)" "$TEST_OUTPUT_FILE" || echo "No specific failures captured in output"
echo "============================================"
fi
# Filter out excluded packages from coverage file
if [ -f "$COVERAGE_FILE" ]; then
echo "Filtering excluded packages from coverage report..."
FILTERED_COVERAGE="${COVERAGE_FILE}.filtered"
# Build sed command with all patterns at once (more efficient than loop)
SED_PATTERN=""
for pkg in "${EXCLUDE_PACKAGES[@]}"; do
if [ -z "$SED_PATTERN" ]; then
SED_PATTERN="\|^${pkg}|d"
else
SED_PATTERN="${SED_PATTERN};\|^${pkg}|d"
fi
done
# Use non-blocking sed with explicit input/output (avoids -i hang issues)
timeout 30 sed "$SED_PATTERN" "$COVERAGE_FILE" > "$FILTERED_COVERAGE" || {
echo "Error: Coverage filtering failed or timed out"
echo "Using unfiltered coverage file"
cp "$COVERAGE_FILE" "$FILTERED_COVERAGE"
}
mv "$FILTERED_COVERAGE" "$COVERAGE_FILE"
echo "Coverage filtering complete"
fi
if [ ! -f "$COVERAGE_FILE" ]; then
echo "Error: coverage file not generated by go test"
exit 1
fi
# Generate coverage report once with timeout protection
# NOTE: Large repos can produce big coverage profiles; allow more time for parsing.
COVERAGE_OUTPUT=$(timeout 180 go tool cover -func="$COVERAGE_FILE" 2>&1) || {
echo "Error: go tool cover failed or timed out after 180 seconds"
echo "This may indicate corrupted coverage data or memory issues"
exit 1
}
# Extract and display the summary line (total coverage)
TOTAL_LINE=$(echo "$COVERAGE_OUTPUT" | awk '/^total:/ {line=$0} END {print line}')
if [ -z "$TOTAL_LINE" ]; then
echo "Error: Coverage report missing 'total:' line"
echo "Coverage output:"
echo "$COVERAGE_OUTPUT"
exit 1
fi
echo "$TOTAL_LINE"
# Extract total coverage percentage (format: "total: (statements)% (branches)% (functions)% (lines)% XX.X%")
# Field $3 is the third space-separated token (line count %)
# We need to remove trailing '%' character
TOTAL_PERCENT=$(echo "$TOTAL_LINE" | awk '{
if (NF < 3) {
print "ERROR: Invalid coverage line format" > "/dev/stderr"
exit 1
}
# Extract last field and remove trailing %
last_field = $NF
if (last_field !~ /^[0-9]+(\.[0-9]+)?%$/) {
printf "ERROR: Last field is not a valid percentage: %s\n", last_field > "/dev/stderr"
exit 1
}
# Remove trailing %
gsub(/%$/, "", last_field)
print last_field
}')
if [ -z "$TOTAL_PERCENT" ] || [ "$TOTAL_PERCENT" = "ERROR" ]; then
echo "Error: Could not extract coverage percentage from: $TOTAL_LINE"
exit 1
fi
# Validate that extracted value is numeric (allows decimals and integers)
if ! echo "$TOTAL_PERCENT" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
echo "Error: Extracted coverage value is not numeric: '$TOTAL_PERCENT'"
echo "Source line: $TOTAL_LINE"
exit 1
fi
echo "Computed coverage: ${TOTAL_PERCENT}% (minimum required ${MIN_COVERAGE}%)"
export TOTAL_PERCENT
export MIN_COVERAGE
python3 - <<'PY'
import os
import sys
from decimal import Decimal, InvalidOperation
try:
total = Decimal(os.environ['TOTAL_PERCENT'])
except InvalidOperation as e:
print(f"Error: TOTAL_PERCENT is not numeric: '{os.environ['TOTAL_PERCENT']}' ({e})", file=sys.stderr)
sys.exit(1)
except KeyError:
print("Error: TOTAL_PERCENT environment variable not set", file=sys.stderr)
sys.exit(1)
try:
minimum = Decimal(os.environ['MIN_COVERAGE'])
except InvalidOperation as e:
print(f"Error: MIN_COVERAGE is not numeric: '{os.environ['MIN_COVERAGE']}' ({e})", file=sys.stderr)
sys.exit(1)
except KeyError:
print("Error: MIN_COVERAGE environment variable not set", file=sys.stderr)
sys.exit(1)
if total < minimum:
print(f"Coverage {total}% is below required {minimum}% (set CHARON_MIN_COVERAGE or CPM_MIN_COVERAGE to override)", file=sys.stderr)
sys.exit(1)
PY
echo "Coverage requirement met"
# Bubble up real test failures (after printing coverage info) so pre-commit
# reflects the actual test status.
if [ "$GO_TEST_STATUS" -ne 0 ]; then
exit "$GO_TEST_STATUS"
fi