mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-26 11:00:38 +00:00
* fix(zeroclaw): remove broken zeroclaw agent (repo 404) The zeroclaw-labs/zeroclaw GitHub repository returns 404 — all installs fail. Remove zeroclaw entirely from the matrix: agent definition, setup code, shell scripts, e2e tests, packer config, skill files, and documentation. Fixes #3102 Agent: code-health Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(zeroclaw): remove stale zeroclaw reference from discovery.md ARM agents list Addresses security review on PR #3107 — the last remaining zeroclaw reference in .claude/rules/discovery.md is now removed. Agent: issue-fixer Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(zeroclaw): remove remaining stale zeroclaw references from CI/packer Remove zeroclaw from: - .github/workflows/agent-tarballs.yml ARM build matrix - .github/workflows/docker.yml agent matrix - packer/digitalocean.pkr.hcl comment - sh/e2e/e2e.sh comment Addresses all 5 stale references flagged in security review of PR #3107. Agent: issue-fixer Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
521 lines
19 KiB
Bash
521 lines
19 KiB
Bash
#!/bin/bash
|
|
# sh/test/e2e-lib.sh — Unit tests for E2E library functions (common.sh, verify.sh, provision.sh)
|
|
#
|
|
# Tests pure functions without requiring cloud credentials or remote instances.
|
|
# Bash 3.2 compatible (no set -u, no echo -e, no (( ++ ))).
|
|
#
|
|
# Usage:
|
|
# bash sh/test/e2e-lib.sh
|
|
set -eo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test harness
|
|
# ---------------------------------------------------------------------------
|
|
_TESTS_RUN=0
|
|
_TESTS_PASSED=0
|
|
_TESTS_FAILED=0
|
|
_FAIL_DETAILS=""
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
assert_eq() {
|
|
local label="$1"
|
|
local expected="$2"
|
|
local actual="$3"
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
if [ "${expected}" = "${actual}" ]; then
|
|
_TESTS_PASSED=$((_TESTS_PASSED + 1))
|
|
else
|
|
_TESTS_FAILED=$((_TESTS_FAILED + 1))
|
|
_FAIL_DETAILS="${_FAIL_DETAILS}\n FAIL: ${label}\n expected: '${expected}'\n actual: '${actual}'"
|
|
fi
|
|
}
|
|
|
|
assert_match() {
|
|
local label="$1"
|
|
local pattern="$2"
|
|
local actual="$3"
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
if printf '%s' "${actual}" | grep -qE "${pattern}"; then
|
|
_TESTS_PASSED=$((_TESTS_PASSED + 1))
|
|
else
|
|
_TESTS_FAILED=$((_TESTS_FAILED + 1))
|
|
_FAIL_DETAILS="${_FAIL_DETAILS}\n FAIL: ${label}\n pattern: '${pattern}'\n actual: '${actual}'"
|
|
fi
|
|
}
|
|
|
|
assert_exit() {
|
|
local label="$1"
|
|
local expected_exit="$2"
|
|
shift 2
|
|
local actual_exit=0
|
|
"$@" >/dev/null 2>&1 || actual_exit=$?
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
if [ "${expected_exit}" -eq "${actual_exit}" ]; then
|
|
_TESTS_PASSED=$((_TESTS_PASSED + 1))
|
|
else
|
|
_TESTS_FAILED=$((_TESTS_FAILED + 1))
|
|
_FAIL_DETAILS="${_FAIL_DETAILS}\n FAIL: ${label}\n expected exit: ${expected_exit}\n actual exit: ${actual_exit}"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Source the libraries under test
|
|
# We need to suppress set -e in common.sh since it validates env on source
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Stub out commands that common.sh checks for (we don't need real ones for unit tests)
|
|
export OPENROUTER_API_KEY="test-key-for-unit-tests"
|
|
|
|
# Source common.sh (provides helpers, constants, logging)
|
|
source "${REPO_ROOT}/sh/e2e/lib/common.sh"
|
|
|
|
# Source verify.sh (provides _validate_timeout, _validate_base64, etc.)
|
|
source "${REPO_ROOT}/sh/e2e/lib/verify.sh"
|
|
|
|
# ===================================================================
|
|
# common.sh tests
|
|
# ===================================================================
|
|
|
|
# --- format_duration ---
|
|
printf '%b\n' "${BOLD}Testing: format_duration${NC}"
|
|
|
|
assert_eq "format_duration 0" "0m 0s" "$(format_duration 0)"
|
|
assert_eq "format_duration 59" "0m 59s" "$(format_duration 59)"
|
|
assert_eq "format_duration 60" "1m 0s" "$(format_duration 60)"
|
|
assert_eq "format_duration 61" "1m 1s" "$(format_duration 61)"
|
|
assert_eq "format_duration 3661" "61m 1s" "$(format_duration 3661)"
|
|
assert_eq "format_duration 120" "2m 0s" "$(format_duration 120)"
|
|
|
|
# --- make_app_name ---
|
|
printf '%b\n' "${BOLD}Testing: make_app_name${NC}"
|
|
|
|
# Without ACTIVE_CLOUD
|
|
ACTIVE_CLOUD=""
|
|
result=$(make_app_name "claude")
|
|
assert_match "make_app_name claude (no cloud)" '^e2e-claude-[0-9]+$' "${result}"
|
|
|
|
# With ACTIVE_CLOUD
|
|
ACTIVE_CLOUD="aws"
|
|
result=$(make_app_name "openclaw")
|
|
assert_match "make_app_name openclaw (aws)" '^e2e-aws-openclaw-[0-9]+$' "${result}"
|
|
|
|
ACTIVE_CLOUD="sprite"
|
|
result=$(make_app_name "codex")
|
|
assert_match "make_app_name codex (sprite)" '^e2e-sprite-codex-[0-9]+$' "${result}"
|
|
|
|
# Reset
|
|
ACTIVE_CLOUD=""
|
|
|
|
# --- track_app / untrack_app ---
|
|
printf '%b\n' "${BOLD}Testing: track_app / untrack_app${NC}"
|
|
|
|
_TRACKED_APPS=""
|
|
track_app "app-1"
|
|
assert_eq "track_app first" "app-1" "${_TRACKED_APPS}"
|
|
|
|
track_app "app-2"
|
|
assert_eq "track_app second" "app-1 app-2" "${_TRACKED_APPS}"
|
|
|
|
track_app "app-3"
|
|
assert_eq "track_app third" "app-1 app-2 app-3" "${_TRACKED_APPS}"
|
|
|
|
untrack_app "app-2"
|
|
assert_eq "untrack_app middle" "app-1 app-3" "${_TRACKED_APPS}"
|
|
|
|
untrack_app "app-1"
|
|
assert_eq "untrack_app first" "app-3" "${_TRACKED_APPS}"
|
|
|
|
untrack_app "app-3"
|
|
assert_eq "untrack_app last" "" "${_TRACKED_APPS}"
|
|
|
|
# Untrack non-existent (should be no-op)
|
|
_TRACKED_APPS="x y z"
|
|
untrack_app "w"
|
|
assert_eq "untrack_app non-existent" "x y z" "${_TRACKED_APPS}"
|
|
|
|
_TRACKED_APPS=""
|
|
|
|
# --- get_provision_timeout ---
|
|
printf '%b\n' "${BOLD}Testing: get_provision_timeout${NC}"
|
|
|
|
# Default agent (no override)
|
|
result=$(get_provision_timeout "claude")
|
|
assert_eq "get_provision_timeout claude (default)" "${PROVISION_TIMEOUT}" "${result}"
|
|
|
|
# Junie has a built-in override
|
|
result=$(get_provision_timeout "junie")
|
|
assert_eq "get_provision_timeout junie (built-in)" "1200" "${result}"
|
|
|
|
# Env var override takes precedence
|
|
export PROVISION_TIMEOUT_codex=999
|
|
result=$(get_provision_timeout "codex")
|
|
assert_eq "get_provision_timeout codex (env override)" "999" "${result}"
|
|
unset PROVISION_TIMEOUT_codex
|
|
|
|
# Non-numeric env var override is ignored
|
|
export PROVISION_TIMEOUT_codex="abc"
|
|
result=$(get_provision_timeout "codex")
|
|
assert_eq "get_provision_timeout codex (non-numeric env ignored)" "${PROVISION_TIMEOUT}" "${result}"
|
|
unset PROVISION_TIMEOUT_codex
|
|
|
|
# Agent name sanitization (special chars → underscore)
|
|
result=$(get_provision_timeout "my-agent")
|
|
assert_eq "get_provision_timeout my-agent (sanitized)" "${PROVISION_TIMEOUT}" "${result}"
|
|
|
|
# --- get_agent_timeout ---
|
|
printf '%b\n' "${BOLD}Testing: get_agent_timeout${NC}"
|
|
|
|
# Default agent
|
|
result=$(get_agent_timeout "claude")
|
|
assert_eq "get_agent_timeout claude (default)" "${AGENT_TIMEOUT}" "${result}"
|
|
|
|
# Junie has a built-in override
|
|
result=$(get_agent_timeout "junie")
|
|
assert_eq "get_agent_timeout junie (built-in)" "2400" "${result}"
|
|
|
|
# Env var override
|
|
export AGENT_TIMEOUT_hermes=500
|
|
result=$(get_agent_timeout "hermes")
|
|
assert_eq "get_agent_timeout hermes (env override)" "500" "${result}"
|
|
unset AGENT_TIMEOUT_hermes
|
|
|
|
# Non-numeric env var ignored — falls through to built-in hermes default (3600), not global
|
|
export AGENT_TIMEOUT_hermes="not-a-number"
|
|
result=$(get_agent_timeout "hermes")
|
|
assert_eq "get_agent_timeout hermes (non-numeric ignored)" "3600" "${result}"
|
|
unset AGENT_TIMEOUT_hermes
|
|
|
|
# --- Numeric validation (constants) ---
|
|
printf '%b\n' "${BOLD}Testing: numeric validation${NC}"
|
|
|
|
# The constants should be numeric after common.sh's validation
|
|
assert_match "PROVISION_TIMEOUT is numeric" '^[0-9]+$' "${PROVISION_TIMEOUT}"
|
|
assert_match "INSTALL_WAIT is numeric" '^[0-9]+$' "${INSTALL_WAIT}"
|
|
assert_match "INPUT_TEST_TIMEOUT is numeric" '^[0-9]+$' "${INPUT_TEST_TIMEOUT}"
|
|
assert_match "AGENT_TIMEOUT is numeric" '^[0-9]+$' "${AGENT_TIMEOUT}"
|
|
|
|
# Verify defaults
|
|
assert_eq "PROVISION_TIMEOUT default" "720" "${PROVISION_TIMEOUT}"
|
|
assert_eq "INSTALL_WAIT default" "600" "${INSTALL_WAIT}"
|
|
assert_eq "INPUT_TEST_TIMEOUT default" "120" "${INPUT_TEST_TIMEOUT}"
|
|
assert_eq "AGENT_TIMEOUT default" "1800" "${AGENT_TIMEOUT}"
|
|
|
|
# Test that non-numeric values get reset to defaults (spawn a subshell)
|
|
result=$(INPUT_TEST_TIMEOUT="DROP TABLE;" bash -c 'source "'"${REPO_ROOT}"'/sh/e2e/lib/common.sh" && printf "%s" "${INPUT_TEST_TIMEOUT}"' 2>/dev/null)
|
|
assert_eq "INPUT_TEST_TIMEOUT injection reset" "120" "${result}"
|
|
|
|
result=$(PROVISION_TIMEOUT='$(whoami)' bash -c 'source "'"${REPO_ROOT}"'/sh/e2e/lib/common.sh" && printf "%s" "${PROVISION_TIMEOUT}"' 2>/dev/null)
|
|
assert_eq "PROVISION_TIMEOUT injection reset" "720" "${result}"
|
|
|
|
result=$(AGENT_TIMEOUT="" bash -c 'source "'"${REPO_ROOT}"'/sh/e2e/lib/common.sh" && printf "%s" "${AGENT_TIMEOUT}"' 2>/dev/null)
|
|
assert_eq "AGENT_TIMEOUT empty reset" "1800" "${result}"
|
|
|
|
# --- OpenRouter API key fallback ---
|
|
printf '%b\n' "${BOLD}Testing: OpenRouter API key fallback${NC}"
|
|
|
|
# Test: ANTHROPIC_AUTH_TOKEN with openrouter base URL should set OPENROUTER_API_KEY
|
|
result=$(
|
|
unset OPENROUTER_API_KEY
|
|
ANTHROPIC_AUTH_TOKEN="sk-or-test-123" \
|
|
ANTHROPIC_BASE_URL="https://openrouter.ai/api" \
|
|
bash -c 'source "'"${REPO_ROOT}"'/sh/e2e/lib/common.sh" && printf "%s" "${OPENROUTER_API_KEY:-}"' 2>/dev/null
|
|
)
|
|
assert_eq "API key fallback (openrouter URL)" "sk-or-test-123" "${result}"
|
|
|
|
# Test: non-openrouter base URL should NOT set OPENROUTER_API_KEY
|
|
result=$(
|
|
unset OPENROUTER_API_KEY
|
|
ANTHROPIC_AUTH_TOKEN="sk-ant-test-456" \
|
|
ANTHROPIC_BASE_URL="https://api.anthropic.com" \
|
|
bash -c 'source "'"${REPO_ROOT}"'/sh/e2e/lib/common.sh" && printf "%s" "${OPENROUTER_API_KEY:-}"' 2>/dev/null
|
|
)
|
|
assert_eq "API key fallback (non-openrouter URL)" "" "${result}"
|
|
|
|
# Test: existing OPENROUTER_API_KEY should NOT be overwritten
|
|
result=$(
|
|
OPENROUTER_API_KEY="existing-key" \
|
|
ANTHROPIC_AUTH_TOKEN="sk-or-new-key" \
|
|
ANTHROPIC_BASE_URL="https://openrouter.ai/api" \
|
|
bash -c 'source "'"${REPO_ROOT}"'/sh/e2e/lib/common.sh" && printf "%s" "${OPENROUTER_API_KEY}"' 2>/dev/null
|
|
)
|
|
assert_eq "API key fallback (existing key preserved)" "existing-key" "${result}"
|
|
|
|
# --- cloud_max_parallel / cloud_install_wait defaults ---
|
|
printf '%b\n' "${BOLD}Testing: cloud_max_parallel / cloud_install_wait defaults${NC}"
|
|
|
|
# When no cloud-specific function exists, should return defaults
|
|
ACTIVE_CLOUD="nonexistent"
|
|
result=$(cloud_max_parallel 2>/dev/null)
|
|
assert_eq "cloud_max_parallel default" "99" "${result}"
|
|
|
|
result=$(cloud_install_wait 2>/dev/null)
|
|
assert_eq "cloud_install_wait default" "${INSTALL_WAIT}" "${result}"
|
|
|
|
|
|
# ===================================================================
|
|
# verify.sh tests
|
|
# ===================================================================
|
|
|
|
# --- _validate_timeout ---
|
|
printf '%b\n' "${BOLD}Testing: _validate_timeout${NC}"
|
|
|
|
INPUT_TEST_TIMEOUT=120
|
|
assert_exit "_validate_timeout valid (120)" 0 _validate_timeout
|
|
|
|
INPUT_TEST_TIMEOUT=0
|
|
assert_exit "_validate_timeout valid (0)" 0 _validate_timeout
|
|
|
|
INPUT_TEST_TIMEOUT=99999
|
|
assert_exit "_validate_timeout valid (99999)" 0 _validate_timeout
|
|
|
|
INPUT_TEST_TIMEOUT="abc"
|
|
assert_exit "_validate_timeout invalid (abc)" 1 _validate_timeout
|
|
|
|
INPUT_TEST_TIMEOUT='$(whoami)'
|
|
assert_exit "_validate_timeout invalid (injection)" 1 _validate_timeout
|
|
|
|
INPUT_TEST_TIMEOUT=""
|
|
assert_exit "_validate_timeout invalid (empty)" 1 _validate_timeout
|
|
|
|
INPUT_TEST_TIMEOUT="12 34"
|
|
assert_exit "_validate_timeout invalid (space)" 1 _validate_timeout
|
|
|
|
INPUT_TEST_TIMEOUT="120;rm -rf /"
|
|
assert_exit "_validate_timeout invalid (semicolon injection)" 1 _validate_timeout
|
|
|
|
# Reset to valid
|
|
INPUT_TEST_TIMEOUT=120
|
|
|
|
# --- _validate_base64 ---
|
|
printf '%b\n' "${BOLD}Testing: _validate_base64${NC}"
|
|
|
|
assert_exit "_validate_base64 valid" 0 _validate_base64 "SGVsbG8gV29ybGQ="
|
|
assert_exit "_validate_base64 valid (no padding)" 0 _validate_base64 "SGVsbG8"
|
|
assert_exit "_validate_base64 valid (with +/)" 0 _validate_base64 "abc+def/ghi="
|
|
assert_exit "_validate_base64 empty" 1 _validate_base64 ""
|
|
assert_exit "_validate_base64 invalid (spaces)" 1 _validate_base64 "SGVs bG8="
|
|
assert_exit "_validate_base64 invalid (shell metachar)" 1 _validate_base64 'SGVsbG8;rm -rf /'
|
|
assert_exit "_validate_base64 invalid (backtick)" 1 _validate_base64 'SGVsbG8`whoami`'
|
|
assert_exit "_validate_base64 invalid (dollar)" 1 _validate_base64 'SGVsbG8$(id)'
|
|
# NOTE: _validate_base64 uses grep which matches per-line, so a string with
|
|
# newlines passes if each line is individually valid. This is a known limitation
|
|
# but low risk — the base64 encoding step always strips newlines (tr -d '\n'),
|
|
# and the data is piped via stdin, never interpolated into commands.
|
|
assert_exit "_validate_base64 newline (known: passes per-line)" 0 _validate_base64 "$(printf 'SGVs\nbG8=')"
|
|
|
|
# --- run_input_test dispatch ---
|
|
printf '%b\n' "${BOLD}Testing: run_input_test dispatch${NC}"
|
|
|
|
# Unknown agent should fail
|
|
assert_exit "run_input_test unknown agent" 1 run_input_test "nonexistent-agent" "fake-app"
|
|
|
|
# SKIP_INPUT_TEST=1 should succeed for any agent
|
|
SKIP_INPUT_TEST=1
|
|
assert_exit "run_input_test skipped" 0 run_input_test "claude" "fake-app"
|
|
SKIP_INPUT_TEST=0
|
|
|
|
# TUI-only agents should pass (they return 0 with a skip message)
|
|
# These don't need cloud_exec since they skip early
|
|
assert_exit "run_input_test opencode (TUI skip)" 0 run_input_test "opencode" "fake-app"
|
|
assert_exit "run_input_test kilocode (TUI skip)" 0 run_input_test "kilocode" "fake-app"
|
|
assert_exit "run_input_test hermes (TUI skip)" 0 run_input_test "hermes" "fake-app"
|
|
assert_exit "run_input_test junie (not implemented skip)" 0 run_input_test "junie" "fake-app"
|
|
|
|
|
|
# ===================================================================
|
|
# provision.sh — app_name validation
|
|
# ===================================================================
|
|
printf '%b\n' "${BOLD}Testing: provision_agent app_name validation${NC}"
|
|
|
|
# Source provision.sh
|
|
source "${REPO_ROOT}/sh/e2e/lib/provision.sh"
|
|
|
|
_tmp_log=$(mktemp -d "${TMPDIR:-/tmp}/e2e-test-XXXXXX")
|
|
|
|
# Valid names should pass validation (will fail later on missing CLI, that's fine)
|
|
# We test that invalid names fail BEFORE any CLI interaction
|
|
|
|
# Empty name
|
|
assert_exit "provision_agent empty name" 1 provision_agent "claude" "" "${_tmp_log}"
|
|
|
|
# Name with shell metacharacters
|
|
assert_exit "provision_agent semicolon injection" 1 provision_agent "claude" "app;rm -rf /" "${_tmp_log}"
|
|
assert_exit "provision_agent backtick injection" 1 provision_agent "claude" 'app`whoami`' "${_tmp_log}"
|
|
assert_exit "provision_agent dollar injection" 1 provision_agent "claude" 'app$(id)' "${_tmp_log}"
|
|
assert_exit "provision_agent space in name" 1 provision_agent "claude" "app name" "${_tmp_log}"
|
|
assert_exit "provision_agent pipe in name" 1 provision_agent "claude" "app|cat" "${_tmp_log}"
|
|
|
|
rm -rf "${_tmp_log}"
|
|
|
|
|
|
# ===================================================================
|
|
# Integration: e2e.sh argument parsing (via --help, invalid args)
|
|
# ===================================================================
|
|
printf '%b\n' "${BOLD}Testing: e2e.sh argument parsing${NC}"
|
|
|
|
E2E_SCRIPT="${REPO_ROOT}/sh/e2e/e2e.sh"
|
|
|
|
# --help should exit 0
|
|
assert_exit "e2e.sh --help" 0 bash "${E2E_SCRIPT}" --help
|
|
|
|
# No --cloud should exit 1
|
|
assert_exit "e2e.sh no args" 1 bash "${E2E_SCRIPT}"
|
|
|
|
# Unknown cloud should exit 1
|
|
assert_exit "e2e.sh unknown cloud" 1 bash "${E2E_SCRIPT}" --cloud fakecloudxyz
|
|
|
|
# Unknown agent should exit 1
|
|
assert_exit "e2e.sh unknown agent" 1 bash "${E2E_SCRIPT}" --cloud aws fakeagentxyz
|
|
|
|
# Unknown option should exit 1
|
|
assert_exit "e2e.sh unknown option" 1 bash "${E2E_SCRIPT}" --cloud aws --bogus
|
|
|
|
# --parallel without number should exit 1
|
|
assert_exit "e2e.sh --parallel no arg" 1 bash "${E2E_SCRIPT}" --cloud aws --parallel
|
|
|
|
# --parallel 0 should exit 1
|
|
assert_exit "e2e.sh --parallel 0" 1 bash "${E2E_SCRIPT}" --cloud aws --parallel 0
|
|
|
|
# --parallel 999 should exit 1 (> 50)
|
|
assert_exit "e2e.sh --parallel 999" 1 bash "${E2E_SCRIPT}" --cloud aws --parallel 999
|
|
|
|
# --parallel abc should exit 1
|
|
assert_exit "e2e.sh --parallel abc" 1 bash "${E2E_SCRIPT}" --cloud aws --parallel abc
|
|
|
|
|
|
# ===================================================================
|
|
# ALL_AGENTS constant completeness
|
|
# ===================================================================
|
|
printf '%b\n' "${BOLD}Testing: ALL_AGENTS completeness${NC}"
|
|
|
|
# Every agent in ALL_AGENTS should have a verify_* and input_test_* function
|
|
for agent in ${ALL_AGENTS}; do
|
|
# Check verify function exists
|
|
if type "verify_${agent}" >/dev/null 2>&1; then
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
_TESTS_PASSED=$((_TESTS_PASSED + 1))
|
|
else
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
_TESTS_FAILED=$((_TESTS_FAILED + 1))
|
|
_FAIL_DETAILS="${_FAIL_DETAILS}\n FAIL: verify_${agent} function missing"
|
|
fi
|
|
|
|
# Check input_test function exists
|
|
if type "input_test_${agent}" >/dev/null 2>&1; then
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
_TESTS_PASSED=$((_TESTS_PASSED + 1))
|
|
else
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
_TESTS_FAILED=$((_TESTS_FAILED + 1))
|
|
_FAIL_DETAILS="${_FAIL_DETAILS}\n FAIL: input_test_${agent} function missing"
|
|
fi
|
|
done
|
|
|
|
|
|
# ===================================================================
|
|
# Cloud driver interface compliance
|
|
# ===================================================================
|
|
printf '%b\n' "${BOLD}Testing: cloud driver interface compliance${NC}"
|
|
|
|
REQUIRED_FUNCTIONS="validate_env headless_env provision_verify exec teardown"
|
|
|
|
for driver_file in "${REPO_ROOT}"/sh/e2e/lib/clouds/*.sh; do
|
|
driver_name=$(basename "${driver_file}" .sh)
|
|
|
|
# Source the driver
|
|
source "${driver_file}"
|
|
|
|
for fn in ${REQUIRED_FUNCTIONS}; do
|
|
full_fn="_${driver_name}_${fn}"
|
|
if type "${full_fn}" >/dev/null 2>&1; then
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
_TESTS_PASSED=$((_TESTS_PASSED + 1))
|
|
else
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
_TESTS_FAILED=$((_TESTS_FAILED + 1))
|
|
_FAIL_DETAILS="${_FAIL_DETAILS}\n FAIL: ${driver_name} driver missing ${full_fn}()"
|
|
fi
|
|
done
|
|
done
|
|
|
|
|
|
# ===================================================================
|
|
# Bash syntax check on all E2E scripts
|
|
# ===================================================================
|
|
printf '%b\n' "${BOLD}Testing: bash -n syntax check on E2E scripts${NC}"
|
|
|
|
for script in \
|
|
"${REPO_ROOT}/sh/e2e/e2e.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/common.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/provision.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/verify.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/teardown.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/soak.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/interactive.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/ai-review.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/clouds/aws.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/clouds/digitalocean.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/clouds/gcp.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/clouds/hetzner.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/clouds/sprite.sh"; do
|
|
|
|
script_name=$(basename "${script}")
|
|
if bash -n "${script}" 2>/dev/null; then
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
_TESTS_PASSED=$((_TESTS_PASSED + 1))
|
|
else
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
_TESTS_FAILED=$((_TESTS_FAILED + 1))
|
|
_FAIL_DETAILS="${_FAIL_DETAILS}\n FAIL: bash -n ${script_name}"
|
|
fi
|
|
done
|
|
|
|
|
|
# ===================================================================
|
|
# macOS compat linter on E2E scripts
|
|
# ===================================================================
|
|
printf '%b\n' "${BOLD}Testing: macOS compat linter on E2E scripts${NC}"
|
|
|
|
compat_script="${REPO_ROOT}/sh/test/macos-compat.sh"
|
|
if [ -f "${compat_script}" ]; then
|
|
for script in \
|
|
"${REPO_ROOT}/sh/e2e/lib/common.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/provision.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/verify.sh" \
|
|
"${REPO_ROOT}/sh/e2e/lib/teardown.sh"; do
|
|
|
|
script_name=$(basename "${script}")
|
|
if bash "${compat_script}" "${script}" >/dev/null 2>&1; then
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
_TESTS_PASSED=$((_TESTS_PASSED + 1))
|
|
else
|
|
_TESTS_RUN=$((_TESTS_RUN + 1))
|
|
_TESTS_FAILED=$((_TESTS_FAILED + 1))
|
|
_FAIL_DETAILS="${_FAIL_DETAILS}\n FAIL: macOS compat ${script_name}"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
|
|
# ===================================================================
|
|
# Results
|
|
# ===================================================================
|
|
printf '\n%b================================%b\n' "${BOLD}" "${NC}"
|
|
if [ "${_TESTS_FAILED}" -eq 0 ]; then
|
|
printf '%b%d/%d tests passed%b\n' "${GREEN}" "${_TESTS_PASSED}" "${_TESTS_RUN}" "${NC}"
|
|
else
|
|
printf '%b%d/%d tests passed, %d failed%b\n' "${RED}" "${_TESTS_PASSED}" "${_TESTS_RUN}" "${_TESTS_FAILED}" "${NC}"
|
|
printf '%b%b%b\n' "${RED}" "${_FAIL_DETAILS}" "${NC}"
|
|
fi
|
|
printf '%b================================%b\n' "${BOLD}" "${NC}"
|
|
|
|
if [ "${_TESTS_FAILED}" -gt 0 ]; then
|
|
exit 1
|
|
fi
|
|
exit 0
|