Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -0,0 +1,299 @@
#!/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 "$@"