Files
blackroad-operating-system/test_all.sh
Claude c50abba250 Add comprehensive Test Orchestrator pattern for monorepo testing
This commit implements a unified test orchestration system that coordinates
all test suites across the BlackRoad Operating System monorepo, providing
consistent testing behavior between local development and CI/CD environments.

## Core Components

### 1. Test Orchestrator Script (test_all.sh)
- Unified interface to run all test suites
- Smart suite detection with existence checks
- Two operational modes:
  * Best-effort: Run all suites, report summary (default)
  * Strict mode: Fail-fast on first error (--strict)
- Color-coded, structured output with summary table
- Modular suite functions for easy extension
- Result tracking with pass/fail/skip status and duration
- Verbose mode for detailed test output

Supported test suites:
- Backend (FastAPI + pytest)
- Agents (200+ AI agent ecosystem)
- Operator Engine (GitHub automation)
- Python SDK (pytest)
- TypeScript SDK (Jest)
- Frontend (structure validation)

### 2. GitHub Actions Workflow (.github/workflows/test-orchestrator.yml)
- Runs orchestrator in CI using same script as local dev
- Service containers (Postgres, Redis) for integration tests
- Multi-language runtime setup (Python 3.11, Node 20)
- Dependency caching for faster builds
- Test artifact uploads (coverage, reports)
- Manual workflow dispatch with suite selection
- Coverage reporting for PRs (Codecov integration)
- Automatic PR status comments

### 3. Comprehensive Documentation (TESTING.md)
- Complete testing guide for developers and AI assistants
- Quick start examples
- Suite-by-suite documentation
- Local development setup instructions
- CI/CD integration guide
- Test writing best practices
- Troubleshooting FAQ with common issues and solutions
- Framework-specific examples

## Reusable Templates (.templates/test-orchestrator/)

Created generic templates for use in other repositories:

### Template Files
- test_all.sh.template - Generic orchestrator script
- test-orchestrator.yml.template - Generic CI workflow
- TESTING.md.template - Generic testing documentation
- PROMPTS.md - AI assistant prompts for implementation
- README.md - Template usage guide and customization instructions

### Key Features
- Clear placeholders ({{REPO_NAME}}, {{PROJECT_DESCRIPTION}}, etc.)
- Comprehensive inline comments
- Framework-agnostic design (Python/Node/Go/Rust examples)
- Adaptation guides for different project structures
- AI assistant prompts for Claude, Copilot, ChatGPT

### Use Cases
- Multi-language monorepos
- Microservices architectures
- Data science projects
- Infrastructure projects
- Any project needing unified test orchestration

## Benefits

1. **Consistency**: Same test experience locally and in CI
2. **Discoverability**: New contributors know exactly how to run tests
3. **Maintainability**: Single pattern to learn and maintain
4. **Extensibility**: Easy to add new test suites
5. **CI-Friendly**: Optimized for GitHub Actions
6. **Reusability**: Templates can be copied to any repo

## Usage

Local development:
  ./test_all.sh                    # Run all suites
  ./test_all.sh --strict           # Fail-fast mode
  ./test_all.sh --suite backend    # Run specific suite
  ./test_all.sh --verbose          # Detailed output

CI triggers automatically on:
  - Push to main, claude/**, copilot/**, codex/** branches
  - Pull requests to main
  - Manual workflow dispatch

## Migration Notes

This implementation:
- Preserves existing test scripts (scripts/run_backend_tests.sh)
- Works alongside existing CI workflows
- Can be adopted gradually or all at once
- Requires no changes to existing test code

## Future Enhancements

Potential additions:
- Matrix testing across Python/Node versions
- Performance benchmarking suite
- Flaky test detection
- Test result caching
- Slack/Discord notifications

---

Pattern adapted for: BlackRoad Operating System monorepo
Designed for: Maximum reusability across projects
Target audience: Developers, DevOps engineers, AI assistants
2025-11-18 07:42:01 +00:00

647 lines
16 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# test_all.sh - BlackRoad Operating System Test Orchestrator
#
# This script runs all test suites across the monorepo in a coordinated fashion.
# It supports both strict mode (fail-fast) and best-effort mode (run all, report summary).
#
# Usage:
# ./test_all.sh # Best-effort mode (run all suites, report at end)
# ./test_all.sh --strict # Strict mode (fail on first error)
# ./test_all.sh --suite backend # Run specific suite only
# ./test_all.sh --help # Show usage
#
# Available suites: backend, agents, operator, sdk-python, sdk-typescript, frontend
#
set -uo pipefail
###############################################################################
# CONFIGURATION
###############################################################################
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT"
STRICT_MODE=false
SPECIFIC_SUITE=""
VERBOSE=false
# Color codes for pretty output
if [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'
else
RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' RESET=''
fi
# Results tracking
declare -A SUITE_RESULTS
declare -A SUITE_TIMES
SUITES_RAN=0
SUITES_PASSED=0
SUITES_FAILED=0
SUITES_SKIPPED=0
###############################################################################
# HELPERS
###############################################################################
have() {
command -v "$1" >/dev/null 2>&1
}
log_header() {
echo ""
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${BOLD}$1${RESET}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
}
log_suite() {
echo ""
echo -e "${BLUE}$1${RESET}"
}
log_info() {
echo -e " ${CYAN}${RESET} $1"
}
log_success() {
echo -e " ${GREEN}${RESET} $1"
}
log_warning() {
echo -e " ${YELLOW}${RESET} $1"
}
log_error() {
echo -e " ${RED}${RESET} $1"
}
log_skip() {
echo -e " ${YELLOW}${RESET} $1"
}
record_result() {
local suite=$1
local result=$2 # PASS, FAIL, SKIP
local duration=$3
SUITE_RESULTS[$suite]=$result
SUITE_TIMES[$suite]=$duration
((SUITES_RAN++))
case $result in
PASS) ((SUITES_PASSED++)) ;;
FAIL) ((SUITES_FAILED++)) ;;
SKIP) ((SUITES_SKIPPED++)) ;;
esac
if [[ "$result" == "FAIL" && "$STRICT_MODE" == "true" ]]; then
log_error "Strict mode enabled - aborting on first failure"
print_summary
exit 1
fi
}
print_summary() {
echo ""
log_header "TEST SUMMARY"
echo ""
# Summary table
printf "${BOLD}%-25s %-10s %-15s${RESET}\n" "Suite" "Result" "Duration"
echo "─────────────────────────────────────────────────────────"
for suite in backend agents operator sdk-python sdk-typescript frontend; do
if [[ -n "${SUITE_RESULTS[$suite]:-}" ]]; then
result="${SUITE_RESULTS[$suite]}"
duration="${SUITE_TIMES[$suite]}"
case $result in
PASS)
printf "${GREEN}%-25s %-10s %-15s${RESET}\n" "$suite" "✓ PASS" "$duration"
;;
FAIL)
printf "${RED}%-25s %-10s %-15s${RESET}\n" "$suite" "✗ FAIL" "$duration"
;;
SKIP)
printf "${YELLOW}%-25s %-10s %-15s${RESET}\n" "$suite" "⊘ SKIP" "$duration"
;;
esac
fi
done
echo "─────────────────────────────────────────────────────────"
echo ""
echo -e "${BOLD}Total:${RESET} $SUITES_RAN suites | ${GREEN}$SUITES_PASSED passed${RESET} | ${RED}$SUITES_FAILED failed${RESET} | ${YELLOW}$SUITES_SKIPPED skipped${RESET}"
echo ""
if [[ $SUITES_FAILED -gt 0 ]]; then
echo -e "${RED}${BOLD}❌ TESTS FAILED${RESET}"
return 1
else
echo -e "${GREEN}${BOLD}✅ ALL TESTS PASSED${RESET}"
return 0
fi
}
###############################################################################
# TEST SUITE FUNCTIONS
###############################################################################
run_backend_tests() {
log_suite "Backend (FastAPI + pytest)"
local start_time=$(date +%s)
if [[ ! -d "$ROOT/backend" ]]; then
log_skip "backend/ directory not found"
record_result "backend" "SKIP" "0s"
return 0
fi
cd "$ROOT/backend"
# Detect Python
local PY=python3
if ! have python3; then
if have python; then
PY=python
else
log_error "Python not found"
record_result "backend" "FAIL" "0s"
return 1
fi
fi
log_info "Using Python: $($PY --version 2>&1)"
# Setup virtual environment for isolation
local VENV_DIR=".venv-tests"
if [[ ! -d "$VENV_DIR" ]]; then
log_info "Creating test virtual environment..."
$PY -m venv "$VENV_DIR" >/dev/null 2>&1
fi
# shellcheck disable=SC1091
source "$VENV_DIR/bin/activate"
# Install dependencies
log_info "Installing dependencies..."
pip install --upgrade pip >/dev/null 2>&1
if [[ -f requirements.txt ]]; then
pip install -r requirements.txt >/dev/null 2>&1
fi
# Run pytest
if have pytest && [[ -f pytest.ini || -d tests ]]; then
log_info "Running pytest..."
# Export test environment variables
export TEST_DATABASE_URL="${TEST_DATABASE_URL:-sqlite+aiosqlite:///./test.db}"
export ENVIRONMENT="testing"
export ALLOWED_ORIGINS="http://localhost:3000,http://localhost:8000"
if [[ "$VERBOSE" == "true" ]]; then
pytest -v --maxfail=1
else
pytest -q --maxfail=1
fi
local exit_code=$?
local end_time=$(date +%s)
local duration=$((end_time - start_time))
if [[ $exit_code -eq 0 ]]; then
log_success "Backend tests passed"
record_result "backend" "PASS" "${duration}s"
else
log_error "Backend tests failed (exit code: $exit_code)"
record_result "backend" "FAIL" "${duration}s"
return 1
fi
else
log_skip "pytest not available or no tests found"
record_result "backend" "SKIP" "0s"
fi
deactivate 2>/dev/null || true
cd "$ROOT"
}
run_agents_tests() {
log_suite "Agents (200+ AI agent ecosystem)"
local start_time=$(date +%s)
if [[ ! -d "$ROOT/agents/tests" ]]; then
log_skip "agents/tests/ directory not found"
record_result "agents" "SKIP" "0s"
return 0
fi
cd "$ROOT"
local PY=python3
if ! have python3; then
PY=python
fi
# Install agents dependencies
if [[ -f agents/requirements.txt ]]; then
log_info "Installing agent dependencies..."
$PY -m pip install -r agents/requirements.txt >/dev/null 2>&1
fi
if have pytest; then
log_info "Running agent tests..."
if [[ "$VERBOSE" == "true" ]]; then
pytest agents/tests/ -v
else
pytest agents/tests/ -q
fi
local exit_code=$?
local end_time=$(date +%s)
local duration=$((end_time - start_time))
if [[ $exit_code -eq 0 ]]; then
log_success "Agent tests passed"
record_result "agents" "PASS" "${duration}s"
else
log_error "Agent tests failed"
record_result "agents" "FAIL" "${duration}s"
return 1
fi
else
log_skip "pytest not available"
record_result "agents" "SKIP" "0s"
fi
cd "$ROOT"
}
run_operator_tests() {
log_suite "Operator Engine (GitHub automation)"
local start_time=$(date +%s)
if [[ ! -d "$ROOT/operator_engine" ]]; then
log_skip "operator_engine/ directory not found"
record_result "operator" "SKIP" "0s"
return 0
fi
cd "$ROOT/operator_engine"
local PY=python3
if ! have python3; then
PY=python
fi
# Install dependencies if requirements.txt exists
if [[ -f requirements.txt ]]; then
log_info "Installing operator dependencies..."
$PY -m pip install -r requirements.txt >/dev/null 2>&1
fi
if have pytest && [[ -d tests ]]; then
log_info "Running operator tests..."
if [[ "$VERBOSE" == "true" ]]; then
pytest tests/ -v
else
pytest tests/ -q
fi
local exit_code=$?
local end_time=$(date +%s)
local duration=$((end_time - start_time))
if [[ $exit_code -eq 0 ]]; then
log_success "Operator tests passed"
record_result "operator" "PASS" "${duration}s"
else
log_error "Operator tests failed"
record_result "operator" "FAIL" "${duration}s"
return 1
fi
else
log_skip "pytest not available or no tests found"
record_result "operator" "SKIP" "0s"
fi
cd "$ROOT"
}
run_sdk_python_tests() {
log_suite "SDK: Python (Official Python SDK)"
local start_time=$(date +%s)
if [[ ! -d "$ROOT/sdk/python" ]]; then
log_skip "sdk/python/ directory not found"
record_result "sdk-python" "SKIP" "0s"
return 0
fi
cd "$ROOT/sdk/python"
local PY=python3
if ! have python3; then
PY=python
fi
log_info "Installing SDK in editable mode..."
$PY -m pip install -e .[dev] >/dev/null 2>&1
if have pytest; then
log_info "Running Python SDK tests..."
if [[ "$VERBOSE" == "true" ]]; then
pytest -v
else
pytest -q
fi
local exit_code=$?
local end_time=$(date +%s)
local duration=$((end_time - start_time))
if [[ $exit_code -eq 0 ]]; then
log_success "Python SDK tests passed"
record_result "sdk-python" "PASS" "${duration}s"
else
log_error "Python SDK tests failed"
record_result "sdk-python" "FAIL" "${duration}s"
return 1
fi
else
log_skip "pytest not available"
record_result "sdk-python" "SKIP" "0s"
fi
cd "$ROOT"
}
run_sdk_typescript_tests() {
log_suite "SDK: TypeScript (Official TypeScript/JavaScript SDK)"
local start_time=$(date +%s)
if [[ ! -d "$ROOT/sdk/typescript" ]]; then
log_skip "sdk/typescript/ directory not found"
record_result "sdk-typescript" "SKIP" "0s"
return 0
fi
if ! have node; then
log_skip "Node.js not installed"
record_result "sdk-typescript" "SKIP" "0s"
return 0
fi
cd "$ROOT/sdk/typescript"
log_info "Using Node: $(node --version)"
# Install dependencies
if [[ -f package.json ]]; then
log_info "Installing npm dependencies..."
npm install >/dev/null 2>&1
fi
# Check if test script exists
if npm run -s test >/dev/null 2>&1; then
log_info "Running TypeScript SDK tests (Jest)..."
if [[ "$VERBOSE" == "true" ]]; then
npm test
else
npm test -- --silent 2>&1 | grep -E "(PASS|FAIL|Test Suites)" || true
fi
local exit_code=${PIPESTATUS[0]}
local end_time=$(date +%s)
local duration=$((end_time - start_time))
if [[ $exit_code -eq 0 ]]; then
log_success "TypeScript SDK tests passed"
record_result "sdk-typescript" "PASS" "${duration}s"
else
log_error "TypeScript SDK tests failed"
record_result "sdk-typescript" "FAIL" "${duration}s"
return 1
fi
else
log_skip "No test script found in package.json"
record_result "sdk-typescript" "SKIP" "0s"
fi
cd "$ROOT"
}
run_frontend_tests() {
log_suite "Frontend (Vanilla JavaScript validation)"
local start_time=$(date +%s)
# Note: Frontend currently has CI validation only (no unit tests)
# This suite validates that the frontend files are present and valid
if [[ ! -d "$ROOT/backend/static" ]]; then
log_skip "backend/static/ directory not found"
record_result "frontend" "SKIP" "0s"
return 0
fi
log_info "Validating frontend structure..."
local errors=0
# Check for essential files
if [[ ! -f "$ROOT/backend/static/index.html" ]]; then
log_error "Missing: backend/static/index.html"
((errors++))
else
log_success "Found: index.html"
fi
if [[ ! -d "$ROOT/backend/static/js" ]]; then
log_error "Missing: backend/static/js/ directory"
((errors++))
else
log_success "Found: js/ directory"
fi
# Basic JavaScript syntax check (if node is available)
if have node && [[ -d "$ROOT/backend/static/js" ]]; then
log_info "Running JavaScript syntax validation..."
local js_errors=0
while IFS= read -r -d '' file; do
if ! node --check "$file" 2>/dev/null; then
log_error "Syntax error in: $file"
((js_errors++))
fi
done < <(find "$ROOT/backend/static/js" -name "*.js" -print0)
if [[ $js_errors -eq 0 ]]; then
log_success "JavaScript syntax validation passed"
else
log_error "JavaScript syntax validation failed ($js_errors files)"
((errors++))
fi
fi
local end_time=$(date +%s)
local duration=$((end_time - start_time))
if [[ $errors -eq 0 ]]; then
log_success "Frontend validation passed"
record_result "frontend" "PASS" "${duration}s"
else
log_error "Frontend validation failed ($errors errors)"
record_result "frontend" "FAIL" "${duration}s"
return 1
fi
}
###############################################################################
# COMMAND-LINE PARSING
###############################################################################
show_help() {
cat << EOF
${BOLD}BlackRoad Operating System - Test Orchestrator${RESET}
${BOLD}USAGE:${RESET}
./test_all.sh [OPTIONS]
${BOLD}OPTIONS:${RESET}
--strict Fail on first test suite failure (default: best-effort)
--suite <name> Run specific test suite only
--verbose, -v Show verbose test output
--help, -h Show this help message
${BOLD}AVAILABLE SUITES:${RESET}
backend Backend FastAPI tests (pytest)
agents AI agent ecosystem tests (pytest)
operator Operator engine tests (pytest)
sdk-python Python SDK tests (pytest)
sdk-typescript TypeScript SDK tests (jest)
frontend Frontend validation (structure + syntax)
${BOLD}EXAMPLES:${RESET}
./test_all.sh # Run all suites, best-effort mode
./test_all.sh --strict # Run all suites, fail-fast mode
./test_all.sh --suite backend # Run only backend tests
./test_all.sh --suite sdk-python --verbose # Run Python SDK tests with verbose output
${BOLD}EXIT CODES:${RESET}
0 All tests passed
1 One or more test suites failed
${BOLD}NOTES:${RESET}
- In best-effort mode, all suites run even if some fail
- In strict mode, execution stops at first failure
- Suite results are summarized at the end
- Use --verbose for detailed test output
EOF
}
while [[ $# -gt 0 ]]; do
case $1 in
--strict)
STRICT_MODE=true
shift
;;
--suite)
SPECIFIC_SUITE="$2"
shift 2
;;
--verbose|-v)
VERBOSE=true
shift
;;
--help|-h)
show_help
exit 0
;;
*)
echo -e "${RED}Unknown option: $1${RESET}"
echo "Use --help for usage information"
exit 1
;;
esac
done
###############################################################################
# MAIN EXECUTION
###############################################################################
log_header "BlackRoad Operating System - Test Orchestrator"
if [[ "$STRICT_MODE" == "true" ]]; then
log_info "Mode: ${RED}${BOLD}STRICT${RESET} (fail-fast)"
else
log_info "Mode: ${GREEN}${BOLD}BEST-EFFORT${RESET} (run all suites)"
fi
if [[ -n "$SPECIFIC_SUITE" ]]; then
log_info "Running suite: ${BOLD}$SPECIFIC_SUITE${RESET}"
fi
echo ""
# Run suites
if [[ -z "$SPECIFIC_SUITE" ]]; then
# Run all suites
run_backend_tests || true
run_agents_tests || true
run_operator_tests || true
run_sdk_python_tests || true
run_sdk_typescript_tests || true
run_frontend_tests || true
else
# Run specific suite
case $SPECIFIC_SUITE in
backend)
run_backend_tests
;;
agents)
run_agents_tests
;;
operator)
run_operator_tests
;;
sdk-python)
run_sdk_python_tests
;;
sdk-typescript)
run_sdk_typescript_tests
;;
frontend)
run_frontend_tests
;;
*)
log_error "Unknown suite: $SPECIFIC_SUITE"
echo "Use --help to see available suites"
exit 1
;;
esac
fi
# Print summary and exit with appropriate code
print_summary
exit $?