Files
leptos-shadcn-ui/scripts/generate_coverage_report.sh
Ubuntu 9d10b97571 drover: task-1767765269828955016
Task: Add test coverage reporting
2026-01-10 07:04:56 +00:00

519 lines
16 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# Automated Test Coverage Report Generator for Leptos ShadCN UI
# =============================================================================
# This script generates comprehensive test coverage reports using cargo-llvm-cov
# and supports multiple output formats (HTML, JSON, LCov, terminal).
#
# Usage:
# ./scripts/generate_coverage_report.sh [OPTIONS]
#
# Options:
# --workspace Generate coverage for entire workspace (default)
# --package <name> Generate coverage for specific package
# --format <fmt> Output format: html, json, lcov, terminal, all (default: all)
# --output-dir <dir> Custom output directory (default: coverage-reports/)
# --open Open HTML report in browser after generation
# --fail-under <pct> Fail if coverage below percentage (default: 95)
# --verbose Enable verbose output
# --ci CI mode (optimized for CI/CD pipelines)
# --help Show this help message
#
# Examples:
# ./scripts/generate_coverage_report.sh
# ./scripts/generate_coverage_report.sh --package leptos-shadcn-button
# ./scripts/generate_coverage_report.sh --format html --open
# ./scripts/generate_coverage_report.sh --ci --fail-under 90
#
# =============================================================================
set -euo pipefail
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# Default values
WORKSPACE_FLAG="--workspace"
PACKAGE_FLAG=""
OUTPUT_FORMATS="all"
OUTPUT_DIR="${PROJECT_ROOT}/coverage-reports"
OPEN_REPORT=false
FAIL_UNDER=95
VERBOSE=false
CI_MODE=false
COVERAGE_DIR="${PROJECT_ROOT}/target/coverage"
# Colors for terminal output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly PURPLE='\033[0;35m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m' # No Color
# =============================================================================
# Helper Functions
# =============================================================================
log_info() {
echo -e "${BLUE}[INFO]${NC} $*"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $*"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $*"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $*"
}
log_verbose() {
if [[ "${VERBOSE}" == true ]]; then
echo -e "${PURPLE}[VERBOSE]${NC} $*"
fi
}
print_header() {
echo ""
echo -e "${CYAN}=============================================================================${NC}"
echo -e "${CYAN}$*${NC}"
echo -e "${CYAN}=============================================================================${NC}"
echo ""
}
show_help() {
cat << EOF
${CYAN}Automated Test Coverage Report Generator${NC}
${GREEN}Usage:${NC}
$0 [OPTIONS]
${GREEN}Options:${NC}
--workspace Generate coverage for entire workspace (default)
--package <name> Generate coverage for specific package
--format <fmt> Output format: html, json, lcov, terminal, all (default: all)
--output-dir <dir> Custom output directory (default: coverage-reports/)
--open Open HTML report in browser after generation
--fail-under <pct> Fail if coverage below percentage (default: 95)
--verbose Enable verbose output
--ci CI mode (optimized for CI/CD pipelines)
--help Show this help message
${GREEN}Examples:${NC}
$0
$0 --package leptos-shadcn-button
$0 --format html --open
$0 --ci --fail-under 90
${GREEN}Environment Variables:${NC}
COVERAGE_DIR Coverage data directory (default: target/coverage)
COVERAGE_OUTPUT_DIR Report output directory (default: coverage-reports)
COVERAGE_MINIMUM_* Minimum coverage thresholds
EOF
}
# =============================================================================
# Argument Parsing
# =============================================================================
parse_arguments() {
while [[ $# -gt 0 ]]; do
case $1 in
--workspace)
WORKSPACE_FLAG="--workspace"
shift
;;
--package)
PACKAGE_FLAG="-p $2"
shift 2
;;
--format)
OUTPUT_FORMATS="$2"
shift 2
;;
--output-dir)
OUTPUT_DIR="$2"
shift 2
;;
--open)
OPEN_REPORT=true
shift
;;
--fail-under)
FAIL_UNDER="$2"
shift 2
;;
--verbose)
VERBOSE=true
shift
;;
--ci)
CI_MODE=true
shift
;;
--help)
show_help
exit 0
;;
*)
log_error "Unknown option: $1"
show_help
exit 1
;;
esac
done
}
# =============================================================================
# Dependency Checking
# =============================================================================
check_dependencies() {
print_header "Checking Dependencies"
local missing_deps=()
# Check for cargo
if ! command -v cargo &> /dev/null; then
missing_deps+=("cargo")
fi
# Check for cargo-llvm-cov
if ! cargo llvm-cov --version &> /dev/null; then
missing_deps+=("cargo-llvm-cov")
fi
# Check for grcov (optional, for alternative coverage)
if ! command -v grcov &> /dev/null; then
log_verbose "grcov not found (optional, for alternative coverage reports)"
fi
if [[ ${#missing_deps[@]} -gt 0 ]]; then
log_error "Missing required dependencies: ${missing_deps[*]}"
echo ""
log_info "Install missing dependencies:"
for dep in "${missing_deps[@]}"; do
case $dep in
cargo-llvm-cov)
echo " cargo install cargo-llvm-cov"
;;
*)
echo " Please install: $dep"
;;
esac
done
exit 1
fi
log_success "All dependencies satisfied"
}
# =============================================================================
# Coverage Report Generation
# =============================================================================
setup_coverage_environment() {
print_header "Setting Up Coverage Environment"
# Create output directory
mkdir -p "${OUTPUT_DIR}"
mkdir -p "${COVERAGE_DIR}"
# Set environment variables for coverage
export CARGO_INCREMENTAL=0
export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%p-%m.profraw"
log_verbose "Coverage directory: ${COVERAGE_DIR}"
log_verbose "Output directory: ${OUTPUT_DIR}"
log_verbose "Fail under threshold: ${FAIL_UNDER}%"
log_success "Coverage environment configured"
}
clean_coverage_data() {
log_info "Cleaning previous coverage data..."
rm -rf "${COVERAGE_DIR}"/*.profraw
rm -rf "${PROJECT_ROOT}/target/coverage"/*.profdata
log_success "Coverage data cleaned"
}
generate_coverage_reports() {
print_header "Generating Coverage Reports"
local cargo_flags=""
local coverage_flags=""
# Build cargo flags
if [[ -n "${WORKSPACE_FLAG}" ]]; then
cargo_flags="${WORKSPACE_FLAG}"
fi
if [[ -n "${PACKAGE_FLAG}" ]]; then
cargo_flags="${cargo_flags} ${PACKAGE_FLAG}"
fi
# CI mode optimizations
if [[ "${CI_MODE}" == true ]]; then
coverage_flags="--no-fail-fast"
fi
log_info "Running tests with coverage instrumentation..."
log_verbose "Cargo flags: ${cargo_flags}"
# Generate coverage based on format
case "${OUTPUT_FORMATS}" in
html)
log_info "Generating HTML coverage report..."
cargo llvm-cov ${cargo_flags} ${coverage_flags} \
--html \
--output-dir "${OUTPUT_DIR}/html" \
--fail-under "${FAIL_UNDER}"
;;
json)
log_info "Generating JSON coverage report..."
cargo llvm-cov ${cargo_flags} ${coverage_flags} \
--json \
--output-path "${OUTPUT_DIR}/coverage.json" \
--fail-under "${FAIL_UNDER}"
;;
lcov)
log_info "Generating LCov coverage report..."
cargo llvm-cov ${cargo_flags} ${coverage_flags} \
--lcov \
--output-path "${OUTPUT_DIR}/coverage.lcov" \
--fail-under "${FAIL_UNDER}"
;;
terminal)
log_info "Generating terminal coverage report..."
cargo llvm-cov ${cargo_flags} ${coverage_flags} \
--text \
--fail-under "${FAIL_UNDER}"
;;
all)
log_info "Generating all coverage report formats..."
cargo llvm-cov ${cargo_flags} ${coverage_flags} \
--html \
--output-dir "${OUTPUT_DIR}/html" \
--json \
--output-path "${OUTPUT_DIR}/coverage.json" \
--lcov \
--output-path "${OUTPUT_DIR}/coverage.lcov" \
--text \
--fail-under "${FAIL_UNDER}"
;;
*)
log_error "Unknown output format: ${OUTPUT_FORMATS}"
exit 1
;;
esac
log_success "Coverage reports generated"
}
generate_summary_report() {
print_header "Coverage Summary Report"
local summary_file="${OUTPUT_DIR}/summary.txt"
# Extract coverage information from JSON report
if [[ -f "${OUTPUT_DIR}/coverage.json" ]]; then
log_info "Generating coverage summary..."
# Parse JSON and create human-readable summary
cat > "${summary_file}" << EOF
Leptos ShadCN UI - Test Coverage Summary
==========================================
Generated: $(date)
Coverage Threshold: ${FAIL_UNDER}%
EOF
# Add coverage statistics if available
if command -v jq &> /dev/null; then
jq -r '"Overall Coverage: " + (.coverage | tostring) + "%"' \
"${OUTPUT_DIR}/coverage.json" 2>/dev/null >> "${summary_file}" || true
fi
log_success "Summary report generated: ${summary_file}"
fi
# Display terminal summary
if [[ "${OUTPUT_FORMATS}" != "terminal" ]] && [[ "${OUTPUT_FORMATS}" != "all" ]]; then
log_info "Generating terminal summary..."
cargo llvm-cov ${WORKSPACE_FLAG} ${PACKAGE_FLAG} --text 2>&1 | head -50
fi
}
generate_coverage_badge() {
log_info "Generating coverage badge..."
local badge_dir="${OUTPUT_DIR}/badges"
mkdir -p "${badge_dir}"
# Extract coverage percentage
local coverage_pct=""
if [[ -f "${OUTPUT_DIR}/coverage.json" ]] && command -v jq &> /dev/null; then
coverage_pct=$(jq -r '.coverage // "unknown"' "${OUTPUT_DIR}/coverage.json" 2>/dev/null || echo "unknown")
else
# Fallback: extract from terminal output
coverage_pct=$(cargo llvm-cov ${WORKSPACE_FLAG} ${PACKAGE_FLAG} --text 2>/dev/null | grep -oP '\d+\.\d+%' | head -1 || echo "unknown")
fi
if [[ "${coverage_pct}" != "unknown" ]]; then
# Generate SVG badge
local color="red"
local pct_num=$(echo "${coverage_pct}" | sed 's/%//')
if (( $(echo "${pct_num} >= 95" | bc -l) )); then
color="brightgreen"
elif (( $(echo "${pct_num} >= 90" | bc -l) )); then
color="green"
elif (( $(echo "${pct_num} >= 80" | bc -l) )); then
color="yellow"
elif (( $(echo "${pct_num} >= 70" | bc -l) )); then
color="orange"
fi
cat > "${badge_dir}/coverage.svg" << EOF
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="a">
<rect width="120" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#a)">
<path fill="#555" d="M0 0h60v20H0z"/>
<path fill="${color}" d="M60 0h60v20H60z"/>
<path fill="url(#b)" d="M0 0h120v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="30" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="30" y="14">coverage</text>
<text x="90" y="15" fill="#010101" fill-opacity=".3">${coverage_pct}</text>
<text x="90" y="14">${coverage_pct}</text>
</g>
</svg>
EOF
log_success "Coverage badge generated: ${badge_dir}/coverage.svg"
else
log_warning "Could not generate coverage badge"
fi
}
open_html_report() {
if [[ "${OPEN_REPORT}" == true ]] && [[ -d "${OUTPUT_DIR}/html" ]]; then
log_info "Opening HTML coverage report..."
local index_file="${OUTPUT_DIR}/html/index.html"
if [[ -f "${index_file}" ]]; then
# Open in default browser (cross-platform)
if command -v xdg-open &> /dev/null; then
xdg-open "${index_file}"
elif command -v open &> /dev/null; then
open "${index_file}"
elif command -v start &> /dev/null; then
start "${index_file}"
else
log_warning "Could not open browser. Please open ${index_file} manually."
fi
else
log_warning "HTML report not found at ${index_file}"
fi
fi
}
generate_ci_report() {
if [[ "${CI_MODE}" == true ]]; then
print_header "CI/CD Report Generation"
local ci_report_file="${OUTPUT_DIR}/ci-report.json"
log_info "Generating CI/CD optimized report..."
# Create CI-friendly JSON report
if [[ -f "${OUTPUT_DIR}/coverage.json" ]]; then
cp "${OUTPUT_DIR}/coverage.json" "${ci_report_file}"
# Add CI metadata
if command -v jq &> /dev/null; then
jq --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg branch "${GIT_BRANCH:-unknown}" \
--arg commit "${GIT_COMMIT:-unknown}" \
'. + {
timestamp: $timestamp,
branch: $branch,
commit: $commit,
project: "leptos-shadcn-ui"
}' "${ci_report_file}" > "${ci_report_file}.tmp"
mv "${ci_report_file}.tmp" "${ci_report_file}"
fi
log_success "CI report generated: ${ci_report_file}"
fi
fi
}
display_final_summary() {
print_header "Coverage Report Generation Complete"
echo -e "${GREEN}Output Directory:${NC} ${OUTPUT_DIR}"
echo ""
if [[ "${OUTPUT_FORMATS}" == "all" ]] || [[ "${OUTPUT_FORMATS}" == "html" ]]; then
if [[ -d "${OUTPUT_DIR}/html" ]]; then
echo -e "${GREEN}HTML Report:${NC} ${OUTPUT_DIR}/html/index.html"
fi
fi
if [[ "${OUTPUT_FORMATS}" == "all" ]] || [[ "${OUTPUT_FORMATS}" == "json" ]]; then
if [[ -f "${OUTPUT_DIR}/coverage.json" ]]; then
echo -e "${GREEN}JSON Report:${NC} ${OUTPUT_DIR}/coverage.json"
fi
fi
if [[ "${OUTPUT_FORMATS}" == "all" ]] || [[ "${OUTPUT_FORMATS}" == "lcov" ]]; then
if [[ -f "${OUTPUT_DIR}/coverage.lcov" ]]; then
echo -e "${GREEN}LCov Report:${NC} ${OUTPUT_DIR}/coverage.lcov"
fi
fi
if [[ -f "${OUTPUT_DIR}/summary.txt" ]]; then
echo -e "${GREEN}Summary:${NC} ${OUTPUT_DIR}/summary.txt"
fi
if [[ -f "${OUTPUT_DIR}/badges/coverage.svg" ]]; then
echo -e "${GREEN}Badge:${NC} ${OUTPUT_DIR}/badges/coverage.svg"
fi
echo ""
log_success "Coverage report generation completed successfully!"
}
# =============================================================================
# Main Execution
# =============================================================================
main() {
parse_arguments "$@"
check_dependencies
setup_coverage_environment
clean_coverage_data
generate_coverage_reports
generate_summary_report
generate_coverage_badge
generate_ci_report
open_html_report
display_final_summary
}
# Run main function
main "$@"