From cc6eb46621398748170d8b04d99d3f67866f3d95 Mon Sep 17 00:00:00 2001 From: Jonathan Baldie Date: Wed, 7 Oct 2026 07:09:34 +0100 Subject: [PATCH] test: centralize Varnish runtime diagnostics in E2E harness Add a shared Varnish container lookup, expose assert_no_vcl_errors to scenario assertions, and include VSL records when Compose fixtures fail. The unsafe-location scenario uses the no-pattern assertion because its VSL contains no other VCL_Error records. Closes #128 --- test/e2e/assert-compose-fixture-lifecycle.sh | 20 +++++++ test/e2e/assert-hostile-unsafe-location.sh | 20 +------ test/e2e/compose-fixture.sh | 59 +++++++++++++++----- test/e2e/http-cache-assert.sh | 50 +++++++++++++++-- test/e2e/varnish-container.sh | 10 ++++ 5 files changed, 121 insertions(+), 38 deletions(-) create mode 100644 test/e2e/varnish-container.sh diff --git a/test/e2e/assert-compose-fixture-lifecycle.sh b/test/e2e/assert-compose-fixture-lifecycle.sh index b2a39de..7e36a01 100755 --- a/test/e2e/assert-compose-fixture-lifecycle.sh +++ b/test/e2e/assert-compose-fixture-lifecycle.sh @@ -99,6 +99,16 @@ grep -Fq 'Restoring fixture service: fixture-web' "$tmpdir/assertion-failure.log cat "$tmpdir/assertion-failure.log" >&2 exit 1 } +grep -Fq -- '--- Varnish runtime diagnostics (VCL_Error, FetchError, Backend_health) ---' "$tmpdir/assertion-failure.log" || { + echo "FAIL: assertion failure should include a Varnish runtime diagnostics section" >&2 + cat "$tmpdir/assertion-failure.log" >&2 + exit 1 +} +grep -Fq "No Varnish container found for Compose fixture $failure_project; skipping runtime log dump." "$tmpdir/assertion-failure.log" || { + echo "FAIL: assertion failure without Varnish should explain why runtime logs were skipped" >&2 + cat "$tmpdir/assertion-failure.log" >&2 + exit 1 +} assert_project_is_clean "$failure_project" timeout_project="${project_prefix}-readiness-timeout" @@ -122,6 +132,16 @@ grep -Fq 'GET /not-ready HTTP/1.1' "$tmpdir/readiness-timeout.log" || { cat "$tmpdir/readiness-timeout.log" >&2 exit 1 } +grep -Fq -- '--- Varnish runtime diagnostics (VCL_Error, FetchError, Backend_health) ---' "$tmpdir/readiness-timeout.log" || { + echo "FAIL: readiness failure should include a Varnish runtime diagnostics section" >&2 + cat "$tmpdir/readiness-timeout.log" >&2 + exit 1 +} +grep -Fq "No Varnish container found for Compose fixture $timeout_project; skipping runtime log dump." "$tmpdir/readiness-timeout.log" || { + echo "FAIL: readiness failure without Varnish should explain why runtime logs were skipped" >&2 + cat "$tmpdir/readiness-timeout.log" >&2 + exit 1 +} assert_project_is_clean "$timeout_project" echo "OK: Compose fixture lifecycle contract passed" diff --git a/test/e2e/assert-hostile-unsafe-location.sh b/test/e2e/assert-hostile-unsafe-location.sh index d728a06..1292389 100755 --- a/test/e2e/assert-hostile-unsafe-location.sh +++ b/test/e2e/assert-hostile-unsafe-location.sh @@ -44,7 +44,7 @@ run_referenced_invalidation_case() { assert_header_absent "${prefix}-mutation" X-Varnish-Cache-Ref-Host "${method} ${prefix} mutation" assert_header_absent "${prefix}-mutation" X-Varnish-Cache-Ref-URL "${method} ${prefix} mutation" if [[ "$expected_ref" == *" "* || "$expected_ref" == *$'\t'* ]]; then - assert_no_reference_ban_syntax_errors + assert_no_vcl_errors "${method} ${ref_header} reference containing unencoded whitespace" fi http_request "${prefix}-after" "${base_url}${target_path}" @@ -190,24 +190,6 @@ run_variant_port_invalidation_case() { echo "OK: ${variant_host} Location invalidated the bare-host target" } -assert_no_reference_ban_syntax_errors() { - local varnish_id - local varnish_log - - varnish_id="$(docker ps \ - --filter "label=com.docker.compose.project=${TEST_PROJECT:?TEST_PROJECT must be set}" \ - --filter "label=com.docker.compose.service=varnish" \ - --format '{{.ID}}' \ - | head -n 1)" - varnish_log="$(docker exec "$varnish_id" varnishlog -d -g raw -i VCL_Error)" - - if grep -Fq 'ban(): Expected && between conditions' <<<"$varnish_log"; then - echo "FAIL: Varnish logged a ban syntax error for an unencoded-whitespace reference" - printf '%s\n' "$varnish_log" - exit 1 - fi -} - case_id="$(openssl rand -hex 4)" tab="$(printf '\t')" diff --git a/test/e2e/compose-fixture.sh b/test/e2e/compose-fixture.sh index 60ef26b..dbdd6e8 100755 --- a/test/e2e/compose-fixture.sh +++ b/test/e2e/compose-fixture.sh @@ -2,6 +2,7 @@ set -euo pipefail script_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" +source "$(dirname "$script_path")/varnish-container.sh" fixture_project="" fixture_compose_files=() fixture_restore_services=() @@ -44,6 +45,30 @@ fixture_compose() { docker compose "${args[@]}" "$@" } +fixture_dump_vsl() { + local varnish_container + echo "--- Varnish runtime diagnostics (VCL_Error, FetchError, Backend_health) ---" >&2 + + if ! varnish_container="$(find_varnish_container "${COMPOSE_FIXTURE_PROJECT:?}")"; then + echo "FAIL: could not find a Varnish container for Compose fixture ${COMPOSE_FIXTURE_PROJECT}" >&2 + return 0 + fi + if [ -z "$varnish_container" ]; then + echo "No Varnish container found for Compose fixture ${COMPOSE_FIXTURE_PROJECT}; skipping runtime log dump." >&2 + return 0 + fi + + if ! docker exec "$varnish_container" varnishlog -d -g raw -i VCL_Error,FetchError,Backend_health >&2; then + echo "FAIL: could not read Varnish runtime logs from container ${varnish_container}" >&2 + fi +} + +fixture_dump_failure_diagnostics() { + echo "--- Compose service logs ---" >&2 + fixture_compose logs >&2 || true + fixture_dump_vsl +} + fixture_cleanup() { local original_status=$? local cleanup_status=0 @@ -171,23 +196,26 @@ compose_fixture_run() { local compose_file_list compose_file_list="$(IFS=:; echo "${fixture_compose_files[*]}")" export COMPOSE_FILE="$compose_file_list" + if [ "${#env_assignments[@]}" -gt 0 ]; then + for assignment in "${env_assignments[@]}"; do + export "$assignment" + done + fi if [ -n "$project" ]; then fixture_project="$project" - export COMPOSE_PROJECT_NAME="$project" + elif [ -n "${COMPOSE_PROJECT_NAME:-}" ]; then + fixture_project="$COMPOSE_PROJECT_NAME" else - fixture_project="" + fixture_project="$(fixture_compose config --format json | python3 -c 'import json,sys; print(json.load(sys.stdin).get("name", ""))')" fi + [ -n "$fixture_project" ] || fail "could not determine Compose fixture project name" + export COMPOSE_PROJECT_NAME="$fixture_project" export COMPOSE_FIXTURE_ACTIVE=true - export COMPOSE_FIXTURE_PROJECT="${project:-${COMPOSE_PROJECT_NAME:-}}" + export COMPOSE_FIXTURE_PROJECT="$fixture_project" export COMPOSE_FIXTURE_SCRIPT="$script_path" export COMPOSE_FIXTURE_READINESS_URL="$readiness_url" export COMPOSE_FIXTURE_TIMEOUT="$timeout" export COMPOSE_FIXTURE_CURL_TIMEOUT="$curl_timeout" - if [ "${#env_assignments[@]}" -gt 0 ]; then - for assignment in "${env_assignments[@]}"; do - export "$assignment" - done - fi if [ "${#restore_services[@]}" -gt 0 ]; then fixture_restore_services=("${restore_services[@]}") @@ -198,12 +226,12 @@ compose_fixture_run() { if [ "${#services[@]}" -gt 0 ]; then if ! fixture_compose up -d --build "${services[@]}"; then echo "FAIL: Compose fixture failed to start" >&2 - fixture_compose logs >&2 || true + fixture_dump_failure_diagnostics return 1 fi elif ! fixture_compose up -d --build; then echo "FAIL: Compose fixture failed to start" >&2 - fixture_compose logs >&2 || true + fixture_dump_failure_diagnostics return 1 fi @@ -219,8 +247,13 @@ compose_fixture_run() { fi if curl -sf --max-time "$request_timeout" "$readiness_url" >/dev/null 2>&1; then echo "$ready_message" - "${assertion_command[@]}" - return $? + if "${assertion_command[@]}"; then + return 0 + else + local assertion_status=$? + fixture_dump_failure_diagnostics + return "$assertion_status" + fi fi remaining=$((deadline - SECONDS)) [ "$remaining" -gt 0 ] || break @@ -232,7 +265,7 @@ compose_fixture_run() { done echo "FAIL: Services did not become ready within ${timeout}s" >&2 - fixture_compose logs >&2 || true + fixture_dump_failure_diagnostics return 1 } diff --git a/test/e2e/http-cache-assert.sh b/test/e2e/http-cache-assert.sh index e43146e..f12ad30 100755 --- a/test/e2e/http-cache-assert.sh +++ b/test/e2e/http-cache-assert.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/varnish-container.sh" + response_headers_path() { printf '%s/%s.headers\n' "${TEST_TMPDIR:?TEST_TMPDIR must be set}" "$1" } @@ -68,12 +70,7 @@ http_request() { local host_header local loopback_url - varnish_container="$( - docker ps \ - --filter "label=com.docker.compose.project=${TEST_PROJECT:?TEST_PROJECT must be set}" \ - --format '{{.ID}} {{.Label "com.docker.compose.service"}}' \ - | awk '$2 ~ /^varnish/ { print $1; exit }' - )" + varnish_container="$(find_varnish_container "${TEST_PROJECT:?TEST_PROJECT must be set}")" if [ -z "$varnish_container" ]; then echo "FAIL: Could not find Varnish container for project $TEST_PROJECT" @@ -137,6 +134,47 @@ assert_http_status() { fi } +assert_no_vcl_errors() { + if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then + echo "FAIL: assert_no_vcl_errors requires a context and optional fixed-string pattern" >&2 + exit 2 + fi + + local context="$1" + local project="${TEST_PROJECT:?TEST_PROJECT must be set}" + local pattern="${2-}" + local varnish_container + local varnish_log + local matching_logs + + varnish_container="$(find_varnish_container "$project")" + if [ -z "$varnish_container" ]; then + echo "FAIL: ${context}: could not find Varnish container for project ${project}" >&2 + exit 1 + fi + + if ! varnish_log="$(docker exec "$varnish_container" varnishlog -d -g raw -i VCL_Error)"; then + echo "FAIL: ${context}: could not read Varnish VCL_Error log for project ${project}" >&2 + exit 1 + fi + + if [ "$#" -eq 2 ]; then + matching_logs="$(printf '%s\n' "$varnish_log" | grep -F -- "$pattern" || true)" + else + matching_logs="$varnish_log" + fi + + if [ -n "$matching_logs" ]; then + if [ "$#" -eq 2 ]; then + echo "FAIL: ${context}: unexpected Varnish VCL_Error records matching '${pattern}'" >&2 + else + echo "FAIL: ${context}: unexpected Varnish VCL_Error records" >&2 + fi + printf '%s\n' "$matching_logs" >&2 + exit 1 + fi +} + assert_cache_state() { local name="$1" local expected_state="$2" diff --git a/test/e2e/varnish-container.sh b/test/e2e/varnish-container.sh new file mode 100644 index 0000000..f4dc4f0 --- /dev/null +++ b/test/e2e/varnish-container.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +find_varnish_container() { + local project="$1" + + docker ps \ + --filter "label=com.docker.compose.project=${project}" \ + --format '{{.ID}} {{.Label "com.docker.compose.service"}}' \ + | awk '$2 ~ /^varnish/ { print $1; exit }' +}