#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" BASE_COMPOSE="$ROOT_DIR/integration/docker-compose.runner.yml" SUITES_DIR="$ROOT_DIR/integration/suites" if [[ ! -f "$BASE_COMPOSE" ]]; then echo "missing base compose file: $BASE_COMPOSE" >&2 exit 1 fi if [[ ! -d "$SUITES_DIR" ]]; then echo "missing integration suites directory: $SUITES_DIR" >&2 exit 1 fi sanitize_project_name() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' } collect_suite_dirs() { if [[ "$#" -gt 0 ]]; then local suite for suite in "$@"; do printf '%s\n' "$SUITES_DIR/$suite" done return fi find "$SUITES_DIR" -mindepth 1 -maxdepth 1 -type d | sort } run_suite() { local suite_dir="$1" local suite_name suite_name="$(basename "$suite_dir")" local manifest="$suite_dir/suite.env" if [[ ! -f "$manifest" ]]; then echo "suite $suite_name is missing manifest: $manifest" >&2 return 1 fi local compose_args=() compose_args+=(--project-directory "$ROOT_DIR" -p "picoclaw-int-$(sanitize_project_name "$suite_name")") compose_args+=(-f "$BASE_COMPOSE") local compose_files=() while IFS= read -r compose_file; do compose_files+=("$compose_file") compose_args+=(-f "$compose_file") done < <(find "$suite_dir" -maxdepth 1 -type f \( -name 'docker-compose.yml' -o -name 'docker-compose.*.yml' \) | sort) if [[ "${#compose_files[@]}" -eq 0 ]]; then echo "suite $suite_name has no docker-compose file" >&2 return 1 fi ( set -a # shellcheck disable=SC1090 source "$manifest" set +a export INTEGRATION_REPO_ROOT="$ROOT_DIR" : "${TEST_COMMAND:?suite $suite_name must define TEST_COMMAND in $manifest}" runner_service="${RUNNER_SERVICE:-integration-runner}" cleanup() { docker compose "${compose_args[@]}" down -v --remove-orphans >/dev/null 2>&1 || true } trap cleanup EXIT echo "==> [$suite_name] resolving services" local services=() while IFS= read -r service; do services+=("$service") done < <(docker compose "${compose_args[@]}" config --services) local dependency_services=() for service in "${services[@]}"; do if [[ "$service" != "$runner_service" ]]; then dependency_services+=("$service") fi done if [[ "${#dependency_services[@]}" -gt 0 ]]; then echo "==> [$suite_name] starting docker services: ${dependency_services[*]}" docker compose "${compose_args[@]}" up -d --build --wait "${dependency_services[@]}" fi echo "==> [$suite_name] running: $TEST_COMMAND" # integration-runner already uses `bash -c` as its entrypoint, so pass the # suite command as a single argument for Bash to execute directly. docker compose "${compose_args[@]}" run --rm "$runner_service" "$TEST_COMMAND" ) } main() { local suite_dirs=() while IFS= read -r suite_dir; do suite_dirs+=("$suite_dir") done < <(collect_suite_dirs "$@") if [[ "${#suite_dirs[@]}" -eq 0 ]]; then echo "no integration suites found" >&2 exit 1 fi local suite_dir for suite_dir in "${suite_dirs[@]}"; do if [[ ! -d "$suite_dir" ]]; then echo "unknown integration suite: $suite_dir" >&2 exit 1 fi run_suite "$suite_dir" done } main "$@"