#!/usr/bin/env bash # test-lib.sh -- Shared utilities for the clinical-intelligence test suite. # Source this file from test-all.sh: source "$(dirname "$0")/test-lib.sh" set -uo pipefail PASS_COUNT=0 FAIL_COUNT=0 WARN_COUNT=0 SKIP_COUNT=0 TEST_LOG_DIR="" VERBOSE="${VERBOSE:-false}" SANDBOX_NAME="${SANDBOX_NAME:-clinical-sandbox}" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' init_test_run() { local ts ts=$(date +%Y-%m-%d-%H%M%S) local repo_root repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" TEST_LOG_DIR="${repo_root}/test-results/${ts}" mkdir -p "$TEST_LOG_DIR" ln -sfn "$TEST_LOG_DIR" "${repo_root}/test-results/latest" echo "Test logs: $TEST_LOG_DIR" } _sandbox() { ssh -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o LogLevel=ERROR \ -o ConnectTimeout=10 \ -o "ProxyCommand=openshell ssh-proxy --gateway-name openshell --name $SANDBOX_NAME" \ "sandbox@openshell-$SANDBOX_NAME" "$@" } _bridge_ip() { ip -4 addr show docker0 2>/dev/null | grep -oP 'inet \K[\d.]+' || echo "172.17.0.1" } # run_test TEST_ID NAME CMD ASSERTION HINT [EXTRA_ARGS...] # # Assertions: assert_exit_0, assert_exit_nonzero, assert_contains, # assert_not_contains, assert_equals, assert_numeric_gt, # assert_output_not_empty # # Severity: prefix assertion with "warn:" to log a warning instead of a failure. # e.g. "warn:assert_contains" -- same check, WARN on mismatch instead of FAIL. # Warnings do NOT cause a non-zero exit code. run_test() { local test_id="$1" local test_name="$2" local cmd="$3" local raw_assertion="$4" local hint="$5" shift 5 local extra_args=("$@") local warn_only=false local assertion="$raw_assertion" if [[ "$assertion" == warn:* ]]; then warn_only=true assertion="${assertion#warn:}" fi local log_file="$TEST_LOG_DIR/${test_id}.log" local start_s start_s=$(date +%s) { echo "=== Test: $test_id $test_name ===" echo "Command: $cmd" echo "Assertion: $raw_assertion ${extra_args[*]:-}" echo "---" } > "$log_file" local output="" local exit_code=0 output=$(eval "$cmd" 2>&1) || exit_code=$? { echo "Exit code: $exit_code" echo "Output:" echo "$output" } >> "$log_file" local end_s end_s=$(date +%s) local elapsed="$(( end_s - start_s ))s" local pass=false case "$assertion" in assert_exit_0) if [[ "$exit_code" -eq 0 ]]; then pass=true; fi ;; assert_exit_nonzero) if [[ "$exit_code" -ne 0 ]]; then pass=true; fi ;; assert_contains) local needle="${extra_args[0]:-}" if echo "$output" | grep -qi "$needle" 2>/dev/null; then pass=true; fi ;; assert_not_contains) local needle="${extra_args[0]:-}" if ! echo "$output" | grep -qi "$needle" 2>/dev/null; then pass=true; fi ;; assert_equals) local expected="${extra_args[0]:-}" if [[ "$(echo "$output" | tr -d '[:space:]')" == "$(echo "$expected" | tr -d '[:space:]')" ]]; then pass=true; fi ;; assert_numeric_gt) local threshold="${extra_args[0]:-0}" local val val=$(echo "$output" | tr -dc '0-9' | head -c 10) if [[ -n "$val" ]] && [[ "$val" -gt "$threshold" ]]; then pass=true; fi ;; assert_output_not_empty) if [[ -n "$(echo "$output" | tr -d '[:space:]')" ]]; then pass=true; fi ;; *) echo " Unknown assertion: $assertion" >> "$log_file" ;; esac if $pass; then PASS_COUNT=$((PASS_COUNT + 1)) printf "${GREEN}[PASS]${NC} %-6s %-45s ${CYAN}(%s)${NC}\n" "$test_id" "$test_name" "$elapsed" echo "Result: PASS" >> "$log_file" elif $warn_only; then WARN_COUNT=$((WARN_COUNT + 1)) printf "${YELLOW}[WARN]${NC} %-6s %-45s ${CYAN}(%s)${NC}\n" "$test_id" "$test_name" "$elapsed" printf " ${YELLOW}%s${NC}\n" "$hint" echo "Result: WARN" >> "$log_file" echo "Hint: $hint" >> "$log_file" else FAIL_COUNT=$((FAIL_COUNT + 1)) printf "${RED}[FAIL]${NC} %-6s %-45s ${CYAN}(%s)${NC}\n" "$test_id" "$test_name" "$elapsed" printf " ${YELLOW}Hint: %s${NC}\n" "$hint" echo "Result: FAIL" >> "$log_file" echo "Hint: $hint" >> "$log_file" fi if [[ "$VERBOSE" == "true" ]]; then echo " Output: $(echo "$output" | head -5)" [[ $(echo "$output" | wc -l) -gt 5 ]] && echo " ... (see $log_file)" fi } print_summary() { local total=$((PASS_COUNT + FAIL_COUNT + WARN_COUNT + SKIP_COUNT)) echo "" echo "==============================" if (( WARN_COUNT > 0 )); then printf "${BOLD}Results: ${GREEN}%d passed${NC}, ${YELLOW}%d warnings${NC}, ${RED}%d failed${NC}, %d total\n" \ "$PASS_COUNT" "$WARN_COUNT" "$FAIL_COUNT" "$total" else printf "${BOLD}Results: ${GREEN}%d passed${NC}, ${RED}%d failed${NC}, %d total\n" \ "$PASS_COUNT" "$FAIL_COUNT" "$total" fi echo "==============================" if [[ -n "$TEST_LOG_DIR" ]]; then { echo "Total: $total" echo "Passed: $PASS_COUNT" echo "Warnings: $WARN_COUNT" echo "Failed: $FAIL_COUNT" echo "" if (( WARN_COUNT > 0 )); then echo "Warnings (non-blocking):" for f in "$TEST_LOG_DIR"/*.log; do if grep -q "Result: WARN" "$f" 2>/dev/null; then local tid thint tid=$(basename "$f" .log) thint=$(grep "^Hint:" "$f" | head -1 | sed 's/^Hint: //') echo " $tid -- $thint" fi done echo "" fi if (( FAIL_COUNT > 0 )); then echo "Failed tests:" for f in "$TEST_LOG_DIR"/*.log; do if grep -q "Result: FAIL" "$f" 2>/dev/null; then local tid thint tid=$(basename "$f" .log) thint=$(grep "^Hint:" "$f" | head -1 | sed 's/^Hint: //') echo " $tid -- $thint" fi done fi } > "$TEST_LOG_DIR/summary.txt" echo "Logs: $TEST_LOG_DIR/" fi (( FAIL_COUNT > 0 )) && return 1 return 0 }