diff --git a/.github/workflows/scripts/run-and-measure.sh b/.github/workflows/scripts/run-and-measure.sh index 741ddcf3..97d407d9 100755 --- a/.github/workflows/scripts/run-and-measure.sh +++ b/.github/workflows/scripts/run-and-measure.sh @@ -37,16 +37,11 @@ COMPOSE=(docker compose -f docker-compose.yml -f docker-compose.coverage.yml) "${COMPOSE[@]}" up -d --build -# Both 200 and 401 are success signals. -for i in $(seq 1 120); do - code=$(curl -sS -o /dev/null -w '%{http_code}' \ - "http://127.0.0.1:${RESTHEART_APP_PORT}/" 2>/dev/null || echo "") - if [ "$code" = "200" ] || [ "$code" = "401" ]; then break; fi - sleep 2 -done - -if [ "$code" != "200" ] && [ "$code" != "401" ]; then - echo "::error::restheart did not bind on port ${RESTHEART_APP_PORT} within 240s (last code: ${code:-empty})" +# flow.sh bootstrap waits for RESTHeart to answer for as long as its +# container runs (no count of tries: a slow runner is waited for), and +# fails at once when the container has exited. +if ! bash flow.sh bootstrap; then + echo "::error::flow.sh bootstrap failed (above): RESTHeart did not come up on port ${RESTHEART_APP_PORT}, or seeding it failed" echo "----- restheart container logs -----" docker logs "${RESTHEART_APP_CONTAINER}" --tail 200 2>&1 || true echo "----- mongo container logs -----" @@ -54,8 +49,6 @@ if [ "$code" != "200" ] && [ "$code" != "401" ]; then "${COMPOSE[@]}" down -v --remove-orphans || true exit 1 fi - -bash flow.sh bootstrap 240 bash flow.sh record-traffic # JaCoCo TCP-dump + report (no JVM stop needed). diff --git a/restheart-mongo/README.md b/restheart-mongo/README.md index ba929c4e..58a3c34b 100644 --- a/restheart-mongo/README.md +++ b/restheart-mongo/README.md @@ -34,7 +34,7 @@ restheart-mongo/ The sample is keploy-independent: `docker compose up && bash flow.sh bootstrap && bash flow.sh record-traffic` runs end-to-end against bare RESTHeart. Lane scripts wrap that exact same path inside `keploy record` / `keploy test`. -* `bootstrap` — wait for RESTHeart to start serving and PUT the seed collections (`items`, `people`, `places`, `halpeople`, `relpeople`, `gql-apps`, `acl`, `_schemas`, `avatars.files`, `range_files.files`, `imported_csv`) so subsequent record-traffic calls have something to find. +* `bootstrap` — wait for RESTHeart to answer (for as long as its container runs: no deadline) and PUT the seed collections (`items`, `people`, `places`, `halpeople`, `relpeople`, `gql-apps`, `acl`, `_schemas`, `avatars.files`, `range_files.files`, `imported_csv`) so subsequent record-traffic calls have something to find. * `record-traffic` — drive the full RESTHeart REST surface listed above. Every call is fault-tolerant (`|| true`) so a single transient 4xx never aborts the run. keploy is the assertion layer. * `coverage` — emits real Java line coverage via JaCoCo when the `docker-compose.coverage.yml` overlay is applied; otherwise a no-op (the base image is uninstrumented so this prints an info message and exits 0). @@ -44,7 +44,7 @@ The sample is keploy-independent: `docker compose up && bash flow.sh bootstrap & ```sh docker compose up -d -bash flow.sh bootstrap 240 +bash flow.sh bootstrap bash flow.sh record-traffic docker compose down -v ``` @@ -58,7 +58,7 @@ The base image is uninstrumented. Apply the coverage overlay to attach the JaCoC ```sh mkdir -p coverage docker compose -f docker-compose.yml -f docker-compose.coverage.yml up -d --build -bash flow.sh bootstrap 240 +bash flow.sh bootstrap bash flow.sh record-traffic bash flow.sh coverage docker compose -f docker-compose.yml -f docker-compose.coverage.yml down -v diff --git a/restheart-mongo/flow.sh b/restheart-mongo/flow.sh index 63555487..8687ca7c 100644 --- a/restheart-mongo/flow.sh +++ b/restheart-mongo/flow.sh @@ -44,26 +44,56 @@ log_fired() { printf '%s %s\n' "$1" "$2" >>"$RESTHEART_FIRED_ROUTES_FILE" } +# restheart_answers: RESTHeart answers HTTP on its root: 200, or 401 (it +# is up and asks for credentials). A published port is not that: docker's +# proxy listens on it as soon as the container starts. +restheart_answers() { + local code + code=$(curl -sS -m 5 -o /dev/null -w '%{http_code}' "${base}/" 2>/dev/null || true) + [ "$code" = "200" ] || [ "$code" = "401" ] +} + +# restheart_wait_for_app: waits until RESTHeart answers, for as long as its +# container ($RESTHEART_APP_CONTAINER) is created, running or restarting. No +# deadline: how long a JVM takes to start depends on the host, and a deadline +# that a loaded runner outlasts fails a healthy app (60 s here did, under +# keploy record on a busy CI host). A container that has exited, or is not +# there, ends the wait at once and fails it, with its last log lines; a docker +# that does not answer is no verdict either way; one that runs and never +# answers is left to the caller's own bound (a CI step's timeout). A lane +# that waits for the app itself first finds it answering at the first probe. restheart_wait_for_app() { - local timeout=${1:-180} - local start_ts code - start_ts=$(date +%s) - while true; do - code=$(curl -sS -o /dev/null -w '%{http_code}' "${base}/" 2>/dev/null || echo "") - # 401 (auth required on root) is a SUCCESS signal — it - # means RESTHeart is up and responding to HTTP. - if [ "$code" = "200" ] || [ "$code" = "401" ]; then return 0; fi - if [ $(( $(date +%s) - start_ts )) -ge "$timeout" ]; then - echo "restheart_wait_for_app: timed out (last code: ${code:-})" >&2 - return 1 - fi - sleep 2 + local state rc said="" + while ! restheart_answers; do + rc=0 + state=$(docker inspect -f '{{.State.Status}}' "$RESTHEART_APP_CONTAINER" 2>&1) || rc=$? + case "$rc:$state" in + # No docker to ask (not installed, not executable): no wait can end. + 126:* | 127:*) + echo "restheart_wait_for_app: cannot run docker (exit $rc): $state" >&2 + return 1 ;; + # Not answering yet, but on its way: created, starting, restarting. + 0:created | 0:running | 0:restarting) ;; + # Gone: it exited, died, or is not there. + 0:* | *"No such"*) + # It may have answered, and gone, since the probe. + restheart_answers && return 0 + [ "$rc" = 0 ] || state="not there" + echo "restheart_wait_for_app: RESTHeart does not answer on ${base}/ and its container ${RESTHEART_APP_CONTAINER} is ${state}" >&2 + docker logs --tail 50 "$RESTHEART_APP_CONTAINER" >&2 2>&1 || true + return 1 ;; + # docker did not answer (a daemon slow under load, a socket it + # may not open): not a verdict, said once. + *) + [ -n "$said" ] || echo "restheart_wait_for_app: docker inspect failed (exit $rc), still waiting: $state" >&2 + said=1 ;; + esac + sleep 1 done } restheart_bootstrap() { - local timeout=${1:-180} - restheart_wait_for_app "$timeout" + restheart_wait_for_app # Seed the collections record-traffic depends on. Each PUT is # idempotent (201 first time, 200 on subsequent runs) and @@ -78,8 +108,7 @@ restheart_bootstrap() { } restheart_record_traffic() { - restheart_wait_for_app 60 - sleep 5 + restheart_wait_for_app local encoded_doc_keys='%7B%22_id%22:1,%22name%22:1,%22age%22:1%7D' local encoded_filter='%7B%22_id%22:%22jane%22%7D' @@ -1315,7 +1344,9 @@ print(miss, cov) } case "${1:-}" in - bootstrap) restheart_bootstrap "${2:-180}" ;; + # A second argument (the seconds a caller once gave the wait) is + # accepted and not used: the wait is on the app, not on a clock. + bootstrap) restheart_bootstrap ;; record-traffic) restheart_record_traffic ;; coverage) restheart_report_coverage ;; *)