#!/usr/bin/env bash set -euo pipefail # Unified test runner for E2E tests of EVerest. # # Usage: run-tests.sh [OPTIONS] SUITE # # Suites: # all All tests # # integration Core, framework, and async API tests # core Core tests only # framework Framework tests only # asyncapi Async API tests only # # ocpp All OCPP tests (1.6, 2.0.1, 2.1) # ocpp16 OCPP 1.6 tests only # ocpp201 OCPP 2.0.1 tests only # ocpp21 OCPP 2.1 tests only # # Options: # -j N Parallel workers (default: nproc) # --serial Run tests serially # --everest-prefix P EVerest install prefix (default: /build/dist) # --junitxml PATH JUnit XML output (default: result.xml) # --html PATH HTML report output (default: report.html) # --no-isolation Disable network isolation # -h, --help Show this help # # Environment variables: # PYTHON_INTERPRETER Python to use (default: python3) # PARALLEL_TESTS Worker count (overridden by -j) # NETWORK_ISOLATION true/false (overridden by --no-isolation) # EVEREST_PREFIX Install prefix (overridden by --everest-prefix) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)" EVEREST_CORE_DIR="$(dirname "$SCRIPT_DIR")" PYTHON="${PYTHON_INTERPRETER:-python3}" # Defaults WORKERS="${PARALLEL_TESTS:-$(nproc)}" SERIAL=false PREFIX="${EVEREST_PREFIX:-${EVEREST_CORE_DIR}/build/dist}" JUNITXML="result.xml" HTML="report.html" ISOLATION="${NETWORK_ISOLATION:-true}" SUITE="" usage() { sed -n '3,/^$/s/^# \?//p' "$0" exit 0 } while [[ $# -gt 0 ]]; do case "$1" in -j) WORKERS="$2"; shift 2;; --serial) SERIAL=true; shift;; --everest-prefix) PREFIX="$2"; shift 2;; --junitxml) JUNITXML="$2"; shift 2;; --html) HTML="$2"; shift 2;; --no-isolation) ISOLATION=false; shift;; -h|--help) usage;; -*) echo "Unknown option: $1" >&2; exit 1;; *) SUITE="$1"; shift;; esac done if [[ -z "$SUITE" ]]; then echo "Error: no suite specified." >&2 echo "Run '$(basename "$0") --help' for usage." >&2 exit 1 fi echo "Suite: $SUITE" echo "Python: $PYTHON" echo "Prefix: $PREFIX" # Network isolation ISOLATION_FLAG="" if [[ "$ISOLATION" == "true" && "$SERIAL" == "false" ]]; then SETUP_SCRIPT="$SCRIPT_DIR/setup-network-isolation.sh" if ip link show ev_test0 >/dev/null 2>&1; then # Veth pairs already exist (e.g. leftover from a previous run or created by CI). echo "Network isolation: detected pre-existing veth pairs." ISOLATION_FLAG="--network-isolation" elif [[ -x "$SETUP_SCRIPT" ]]; then echo "Setting up network isolation (veth pairs)..." # Try without sudo first (for CI). # CTRL+C at the sudo prompt cancels it and falls back to sequential execution. if "$SETUP_SCRIPT" setup "$WORKERS" 2>/dev/null; then ISOLATION_FLAG="--network-isolation" elif sudo "$SETUP_SCRIPT" setup "$WORKERS"; then ISOLATION_FLAG="--network-isolation" else echo "Warning: Could not set up network isolation (no sudo / CAP_NET_ADMIN)." echo " ISO 15118 tests will run sequentially via xdist_group markers." fi fi if [[ -n "$ISOLATION_FLAG" ]]; then # Try without sudo (CI/root), then sudo -n (local NOPASSWD), then warn. teardown_network_isolation() { echo "Tearing down network isolation..." "$SETUP_SCRIPT" teardown "$WORKERS" 2>/dev/null \ || sudo -n "$SETUP_SCRIPT" teardown "$WORKERS" 2>/dev/null \ || echo "Warning: Could not tear down network isolation. Run manually: sudo $SETUP_SCRIPT teardown" } trap teardown_network_isolation EXIT fi fi # Common pytest arguments PYTEST_ARGS=( -rA --self-contained-html --max-worker-restart=0 --timeout=300 --everest-prefix "$PREFIX" --config-file "$SCRIPT_DIR/pytest.ini" ) if [[ "$SERIAL" == "false" ]]; then echo "Workers: $WORKERS" PYTEST_ARGS+=(-n "$WORKERS" --dist=loadgroup) else echo "Workers: serial" fi [[ -n "$ISOLATION_FLAG" ]] && PYTEST_ARGS+=("$ISOLATION_FLAG") # OCPP setup (certs + configs) setup_ocpp() { local aux_dir="$SCRIPT_DIR/ocpp_tests/test_sets/everest-aux" if [[ -d "$aux_dir" ]]; then echo "Installing OCPP test certs and configs..." (cd "$aux_dir" && ./install_certs.sh "$PREFIX" && ./install_configs.sh "$PREFIX") fi } # Run case "$SUITE" in all) echo "Running all test suites" cd "$SCRIPT_DIR" setup_ocpp "$PYTHON" -m pytest "${PYTEST_ARGS[@]}" \ --junitxml="$JUNITXML" --html="$HTML" \ core_tests/*.py \ framework_tests/*.py \ async_api_tests/*.py \ ocpp_tests/test_sets/ocpp16/*.py \ ocpp_tests/test_sets/ocpp201/*.py \ ocpp_tests/test_sets/ocpp21/*.py ;; integration) cd "$SCRIPT_DIR" "$PYTHON" -m pytest "${PYTEST_ARGS[@]}" \ --junitxml="$JUNITXML" --html="$HTML" \ core_tests/*.py \ framework_tests/*.py \ async_api_tests/*.py ;; core) cd "$SCRIPT_DIR" "$PYTHON" -m pytest "${PYTEST_ARGS[@]}" \ --junitxml="$JUNITXML" --html="$HTML" \ core_tests/*.py ;; framework) cd "$SCRIPT_DIR" "$PYTHON" -m pytest "${PYTEST_ARGS[@]}" \ --junitxml="$JUNITXML" --html="$HTML" \ framework_tests/*.py ;; asyncapi) cd "$SCRIPT_DIR" "$PYTHON" -m pytest "${PYTEST_ARGS[@]}" \ --junitxml="$JUNITXML" --html="$HTML" \ async_api_tests/*.py ;; ocpp) setup_ocpp cd "$SCRIPT_DIR" "$PYTHON" -m pytest "${PYTEST_ARGS[@]}" \ --junitxml="$JUNITXML" --html="$HTML" \ ocpp_tests/test_sets/ocpp16/*.py \ ocpp_tests/test_sets/ocpp201/*.py \ ocpp_tests/test_sets/ocpp21/*.py ;; ocpp16) setup_ocpp cd "$SCRIPT_DIR" "$PYTHON" -m pytest "${PYTEST_ARGS[@]}" \ --junitxml="$JUNITXML" --html="$HTML" \ ocpp_tests/test_sets/ocpp16/*.py ;; ocpp201) setup_ocpp cd "$SCRIPT_DIR" "$PYTHON" -m pytest "${PYTEST_ARGS[@]}" \ --junitxml="$JUNITXML" --html="$HTML" \ ocpp_tests/test_sets/ocpp201/*.py ;; ocpp21) setup_ocpp cd "$SCRIPT_DIR" "$PYTHON" -m pytest "${PYTEST_ARGS[@]}" \ --junitxml="$JUNITXML" --html="$HTML" \ ocpp_tests/test_sets/ocpp21/*.py ;; *) echo "Unknown suite: $SUITE" >&2 echo "Valid suites: all, integration, core, framework, asyncapi, ocpp, ocpp16, ocpp201, ocpp21" >&2 exit 1 ;; esac