chore: git cache cleanup
This commit is contained in:
292
scripts/go-test-coverage.sh
Executable file
292
scripts/go-test-coverage.sh
Executable file
@@ -0,0 +1,292 @@
|
||||
#!/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:-87}}"
|
||||
|
||||
generate_test_encryption_key() {
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl rand -base64 32 | tr -d '\n'
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
python3 - <<'PY'
|
||||
import base64
|
||||
import os
|
||||
|
||||
print(base64.b64encode(os.urandom(32)).decode())
|
||||
PY
|
||||
return
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
ensure_encryption_key() {
|
||||
local key_source="existing"
|
||||
local decoded_key_hex=""
|
||||
local decoded_key_bytes=0
|
||||
|
||||
if [[ -z "${CHARON_ENCRYPTION_KEY:-}" ]]; then
|
||||
key_source="generated"
|
||||
CHARON_ENCRYPTION_KEY="$(generate_test_encryption_key)"
|
||||
fi
|
||||
|
||||
if [[ -z "${CHARON_ENCRYPTION_KEY:-}" ]]; then
|
||||
echo "Error: Could not provision CHARON_ENCRYPTION_KEY automatically."
|
||||
echo "Install openssl or python3, or set CHARON_ENCRYPTION_KEY manually to a base64-encoded 32-byte key."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! decoded_key_hex=$(printf '%s' "$CHARON_ENCRYPTION_KEY" | base64 --decode 2>/dev/null | od -An -tx1 -v | tr -d ' \n'); then
|
||||
key_source="regenerated"
|
||||
CHARON_ENCRYPTION_KEY="$(generate_test_encryption_key)"
|
||||
if ! decoded_key_hex=$(printf '%s' "$CHARON_ENCRYPTION_KEY" | base64 --decode 2>/dev/null | od -An -tx1 -v | tr -d ' \n'); then
|
||||
echo "Error: CHARON_ENCRYPTION_KEY could not be decoded and regeneration failed."
|
||||
echo "Set CHARON_ENCRYPTION_KEY to a valid base64-encoded 32-byte key."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
decoded_key_bytes=$(( ${#decoded_key_hex} / 2 ))
|
||||
if [[ "$decoded_key_bytes" -ne 32 ]]; then
|
||||
key_source="regenerated"
|
||||
CHARON_ENCRYPTION_KEY="$(generate_test_encryption_key)"
|
||||
if ! decoded_key_hex=$(printf '%s' "$CHARON_ENCRYPTION_KEY" | base64 --decode 2>/dev/null | od -An -tx1 -v | tr -d ' \n'); then
|
||||
echo "Error: CHARON_ENCRYPTION_KEY has invalid length and regeneration failed."
|
||||
echo "Set CHARON_ENCRYPTION_KEY to a valid base64-encoded 32-byte key."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
decoded_key_bytes=$(( ${#decoded_key_hex} / 2 ))
|
||||
if [[ "$decoded_key_bytes" -ne 32 ]]; then
|
||||
echo "Error: Could not provision a valid 32-byte CHARON_ENCRYPTION_KEY."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
export CHARON_ENCRYPTION_KEY
|
||||
|
||||
if [[ "$key_source" == "generated" ]]; then
|
||||
echo "Info: CHARON_ENCRYPTION_KEY was not set; generated an ephemeral test key."
|
||||
elif [[ "$key_source" == "regenerated" ]]; then
|
||||
echo "Warning: CHARON_ENCRYPTION_KEY was invalid; generated an ephemeral test key."
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_encryption_key
|
||||
|
||||
# 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/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
|
||||
set +e
|
||||
gotestsum --format pkgname -- -race -mod=readonly -coverprofile="$COVERAGE_FILE" ./... 2>&1 | tee "$TEST_OUTPUT_FILE"
|
||||
GO_TEST_STATUS=$?
|
||||
set -e
|
||||
else
|
||||
set +e
|
||||
go test -race -v -mod=readonly -coverprofile="$COVERAGE_FILE" ./... 2>&1 | tee "$TEST_OUTPUT_FILE"
|
||||
GO_TEST_STATUS=$?
|
||||
set -e
|
||||
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 statement coverage percentage from go tool cover summary line
|
||||
STATEMENT_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 "$STATEMENT_PERCENT" ] || [ "$STATEMENT_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 "$STATEMENT_PERCENT" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
|
||||
echo "Error: Extracted coverage value is not numeric: '$STATEMENT_PERCENT'"
|
||||
echo "Source line: $TOTAL_LINE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Compute line coverage directly from coverprofile blocks (authoritative gate in this script)
|
||||
# Format per line:
|
||||
# file:startLine.startCol,endLine.endCol numStatements count
|
||||
LINE_PERCENT=$(awk '
|
||||
BEGIN {
|
||||
total_lines = 0
|
||||
covered_lines = 0
|
||||
}
|
||||
NR == 1 {
|
||||
next
|
||||
}
|
||||
{
|
||||
split($1, pos, ":")
|
||||
if (length(pos) < 2) {
|
||||
next
|
||||
}
|
||||
|
||||
file = pos[1]
|
||||
split(pos[2], ranges, ",")
|
||||
split(ranges[1], start_parts, ".")
|
||||
split(ranges[2], end_parts, ".")
|
||||
|
||||
start_line = start_parts[1] + 0
|
||||
end_line = end_parts[1] + 0
|
||||
count = $3 + 0
|
||||
|
||||
if (start_line <= 0 || end_line <= 0 || end_line < start_line) {
|
||||
next
|
||||
}
|
||||
|
||||
for (line = start_line; line <= end_line; line++) {
|
||||
key = file ":" line
|
||||
if (!(key in seen_total)) {
|
||||
seen_total[key] = 1
|
||||
total_lines++
|
||||
}
|
||||
if (count > 0 && !(key in seen_covered)) {
|
||||
seen_covered[key] = 1
|
||||
covered_lines++
|
||||
}
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (total_lines == 0) {
|
||||
print "0.0"
|
||||
exit 0
|
||||
}
|
||||
printf "%.1f", (covered_lines * 100.0) / total_lines
|
||||
}
|
||||
' "$COVERAGE_FILE")
|
||||
|
||||
if [ -z "$LINE_PERCENT" ]; then
|
||||
echo "Error: Could not compute line coverage from $COVERAGE_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! echo "$LINE_PERCENT" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
|
||||
echo "Error: Computed line coverage is not numeric: '$LINE_PERCENT'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Statement coverage: ${STATEMENT_PERCENT}%"
|
||||
echo "Line coverage: ${LINE_PERCENT}%"
|
||||
echo "Coverage gate (line coverage): minimum required ${MIN_COVERAGE}%"
|
||||
|
||||
if awk -v current="$LINE_PERCENT" -v minimum="$MIN_COVERAGE" 'BEGIN { exit !(current + 0 < minimum + 0) }'; then
|
||||
echo "Coverage ${LINE_PERCENT}% is below required ${MIN_COVERAGE}% (set CHARON_MIN_COVERAGE or CPM_MIN_COVERAGE to override)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user