300 lines
8.8 KiB
Bash
300 lines
8.8 KiB
Bash
#!/usr/bin/env bash
|
|
# Test Category Runner
|
|
# Sprint: CI/CD Enhancement - Script Consolidation
|
|
#
|
|
# Purpose: Run tests for a specific category across all test projects
|
|
# Usage: ./run-test-category.sh <category> [options]
|
|
#
|
|
# Options:
|
|
# --fail-on-empty Fail if no tests are found for the category
|
|
# --collect-coverage Collect code coverage data
|
|
# --verbose Show detailed output
|
|
#
|
|
# Exit Codes:
|
|
# 0 - Success (all tests passed or no tests found)
|
|
# 1 - One or more tests failed
|
|
# 2 - Invalid usage
|
|
|
|
set -euo pipefail
|
|
|
|
# Source shared libraries if available
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
|
|
if [[ -f "$REPO_ROOT/devops/scripts/lib/logging.sh" ]]; then
|
|
source "$REPO_ROOT/devops/scripts/lib/logging.sh"
|
|
else
|
|
# Minimal logging fallback
|
|
log_info() { echo "[INFO] $*"; }
|
|
log_error() { echo "[ERROR] $*" >&2; }
|
|
log_debug() { [[ -n "${DEBUG:-}" ]] && echo "[DEBUG] $*"; }
|
|
log_step() { echo "==> $*"; }
|
|
fi
|
|
|
|
if [[ -f "$REPO_ROOT/devops/scripts/lib/exit-codes.sh" ]]; then
|
|
source "$REPO_ROOT/devops/scripts/lib/exit-codes.sh"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# Constants
|
|
# =============================================================================
|
|
|
|
readonly FIND_PATTERN='\( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \)'
|
|
readonly EXCLUDE_PATHS='! -path "*/node_modules/*" ! -path "*/.git/*" ! -path "*/bin/*" ! -path "*/obj/*"'
|
|
readonly EXCLUDE_FILES='! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj"'
|
|
|
|
# =============================================================================
|
|
# Functions
|
|
# =============================================================================
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $(basename "$0") <category> [options]
|
|
|
|
Run tests for a specific test category across all test projects.
|
|
|
|
Arguments:
|
|
category Test category (Unit, Architecture, Contract, Integration,
|
|
Security, Golden, Performance, Benchmark, AirGap, Chaos,
|
|
Determinism, Resilience, Observability)
|
|
|
|
Options:
|
|
--fail-on-empty Exit with error if no tests found for the category
|
|
--collect-coverage Collect XPlat Code Coverage data
|
|
--verbose Show detailed test output
|
|
--results-dir DIR Custom results directory (default: ./TestResults/<category>)
|
|
--help Show this help message
|
|
|
|
Environment Variables:
|
|
DOTNET_VERSION .NET SDK version (default: uses installed version)
|
|
TZ Timezone (should be UTC for determinism)
|
|
|
|
Examples:
|
|
$(basename "$0") Unit
|
|
$(basename "$0") Integration --collect-coverage
|
|
$(basename "$0") Performance --results-dir ./perf-results
|
|
EOF
|
|
}
|
|
|
|
find_test_projects() {
|
|
local search_dir="${1:-src}"
|
|
|
|
# Use eval to properly expand the find pattern
|
|
eval "find '$search_dir' $FIND_PATTERN -type f $EXCLUDE_PATHS $EXCLUDE_FILES" | sort
|
|
}
|
|
|
|
sanitize_project_name() {
|
|
local proj="$1"
|
|
# Replace slashes with underscores, remove .csproj extension
|
|
echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj$||'
|
|
}
|
|
|
|
run_tests() {
|
|
local category="$1"
|
|
local results_dir="$2"
|
|
local collect_coverage="$3"
|
|
local verbose="$4"
|
|
local fail_on_empty="$5"
|
|
|
|
local passed=0
|
|
local failed=0
|
|
local skipped=0
|
|
local no_tests=0
|
|
|
|
mkdir -p "$results_dir"
|
|
|
|
local projects
|
|
projects=$(find_test_projects "$REPO_ROOT/src")
|
|
|
|
if [[ -z "$projects" ]]; then
|
|
log_error "No test projects found"
|
|
return 1
|
|
fi
|
|
|
|
local project_count
|
|
project_count=$(echo "$projects" | grep -c '.csproj' || echo "0")
|
|
log_info "Found $project_count test projects"
|
|
|
|
local category_lower
|
|
category_lower=$(echo "$category" | tr '[:upper:]' '[:lower:]')
|
|
|
|
while IFS= read -r proj; do
|
|
[[ -z "$proj" ]] && continue
|
|
|
|
local proj_name
|
|
proj_name=$(sanitize_project_name "$proj")
|
|
local trx_name="${proj_name}-${category_lower}.trx"
|
|
|
|
# GitHub Actions grouping
|
|
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
|
echo "::group::Testing $proj ($category)"
|
|
else
|
|
log_step "Testing $proj ($category)"
|
|
fi
|
|
|
|
# Build dotnet test command
|
|
local cmd="dotnet test \"$proj\""
|
|
cmd+=" --filter \"Category=$category\""
|
|
cmd+=" --configuration Release"
|
|
cmd+=" --logger \"trx;LogFileName=$trx_name\""
|
|
cmd+=" --results-directory \"$results_dir\""
|
|
|
|
if [[ "$collect_coverage" == "true" ]]; then
|
|
cmd+=" --collect:\"XPlat Code Coverage\""
|
|
fi
|
|
|
|
if [[ "$verbose" == "true" ]]; then
|
|
cmd+=" --verbosity normal"
|
|
else
|
|
cmd+=" --verbosity minimal"
|
|
fi
|
|
|
|
# Execute tests
|
|
local exit_code=0
|
|
eval "$cmd" 2>&1 || exit_code=$?
|
|
|
|
if [[ $exit_code -eq 0 ]]; then
|
|
# Check if TRX was created (tests actually ran)
|
|
if [[ -f "$results_dir/$trx_name" ]]; then
|
|
((passed++))
|
|
log_info "PASS: $proj"
|
|
else
|
|
((no_tests++))
|
|
log_debug "SKIP: $proj (no $category tests)"
|
|
fi
|
|
else
|
|
# Check if failure was due to no tests matching the filter
|
|
if [[ -f "$results_dir/$trx_name" ]]; then
|
|
((failed++))
|
|
log_error "FAIL: $proj"
|
|
else
|
|
((no_tests++))
|
|
log_debug "SKIP: $proj (no $category tests or build error)"
|
|
fi
|
|
fi
|
|
|
|
# Close GitHub Actions group
|
|
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
|
echo "::endgroup::"
|
|
fi
|
|
|
|
done <<< "$projects"
|
|
|
|
# Generate summary
|
|
log_info ""
|
|
log_info "=========================================="
|
|
log_info "$category Test Summary"
|
|
log_info "=========================================="
|
|
log_info "Passed: $passed"
|
|
log_info "Failed: $failed"
|
|
log_info "No Tests: $no_tests"
|
|
log_info "Total: $project_count"
|
|
log_info "=========================================="
|
|
|
|
# GitHub Actions summary
|
|
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
|
{
|
|
echo "## $category Test Summary"
|
|
echo ""
|
|
echo "| Metric | Count |"
|
|
echo "|--------|-------|"
|
|
echo "| Passed | $passed |"
|
|
echo "| Failed | $failed |"
|
|
echo "| No Tests | $no_tests |"
|
|
echo "| Total Projects | $project_count |"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
|
|
# Determine exit code
|
|
if [[ $failed -gt 0 ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$fail_on_empty" == "true" ]] && [[ $passed -eq 0 ]]; then
|
|
log_error "No tests found for category: $category"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# =============================================================================
|
|
# Main
|
|
# =============================================================================
|
|
|
|
main() {
|
|
local category=""
|
|
local results_dir=""
|
|
local collect_coverage="false"
|
|
local verbose="false"
|
|
local fail_on_empty="false"
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--help|-h)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--fail-on-empty)
|
|
fail_on_empty="true"
|
|
shift
|
|
;;
|
|
--collect-coverage)
|
|
collect_coverage="true"
|
|
shift
|
|
;;
|
|
--verbose|-v)
|
|
verbose="true"
|
|
shift
|
|
;;
|
|
--results-dir)
|
|
results_dir="$2"
|
|
shift 2
|
|
;;
|
|
-*)
|
|
log_error "Unknown option: $1"
|
|
usage
|
|
exit 2
|
|
;;
|
|
*)
|
|
if [[ -z "$category" ]]; then
|
|
category="$1"
|
|
else
|
|
log_error "Unexpected argument: $1"
|
|
usage
|
|
exit 2
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validate category
|
|
if [[ -z "$category" ]]; then
|
|
log_error "Category is required"
|
|
usage
|
|
exit 2
|
|
fi
|
|
|
|
# Validate category name
|
|
local valid_categories="Unit Architecture Contract Integration Security Golden Performance Benchmark AirGap Chaos Determinism Resilience Observability"
|
|
if ! echo "$valid_categories" | grep -qw "$category"; then
|
|
log_error "Invalid category: $category"
|
|
log_error "Valid categories: $valid_categories"
|
|
exit 2
|
|
fi
|
|
|
|
# Set default results directory
|
|
if [[ -z "$results_dir" ]]; then
|
|
results_dir="./TestResults/$category"
|
|
fi
|
|
|
|
log_info "Running $category tests..."
|
|
log_info "Results directory: $results_dir"
|
|
|
|
run_tests "$category" "$results_dir" "$collect_coverage" "$verbose" "$fail_on_empty"
|
|
}
|
|
|
|
main "$@"
|