fix(tests): make required-stack.bats portable across nodes with different app rosters
Found live during the .5 multinode-pass run: this suite was hardcoded to .116's exact app bundle (including the mempool stack), so any node missing an app hard-failed instead of skipping — and a missing local fail() helper (present in 3 sibling bats files, absent here) masked the real error as "command not found" (exit 127). Add the same skip-if-absent idiom already used in mempool.bats per-app, and define fail() locally like the others. Verified: skips cleanly on .116 (no bitcoin-knots here), still exercises real checks for apps that are installed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
6b7af884ab
commit
f1055164d2
@ -1,7 +1,14 @@
|
|||||||
#!/usr/bin/env bats
|
#!/usr/bin/env bats
|
||||||
# tests/lifecycle/bats/required-stack.bats
|
# tests/lifecycle/bats/required-stack.bats
|
||||||
#
|
#
|
||||||
# Read-only release-gate checks for the required Bitcoin stack on .116.
|
# Read-only release-gate checks for the Bitcoin/electrum/lnd/mempool stack.
|
||||||
|
# Originally written against .116's fixed app roster; the "present"/"running"
|
||||||
|
# checks below now only require containers actually installed on THIS node
|
||||||
|
# (podman_all_names — present in `podman ps -a` even if stopped), so a node
|
||||||
|
# with a different app subset (e.g. no mempool stack) doesn't hard-fail on
|
||||||
|
# apps it was never meant to have. Per-app checks further down (mempool,
|
||||||
|
# filebrowser, ...) skip individually if that app isn't installed, matching
|
||||||
|
# the mempool_skip_if_absent idiom in mempool.bats.
|
||||||
#
|
#
|
||||||
# This suite is intentionally non-destructive and does not use RPC auth;
|
# This suite is intentionally non-destructive and does not use RPC auth;
|
||||||
# it can run anytime as a health gate during long sync/reindex windows.
|
# it can run anytime as a health gate during long sync/reindex windows.
|
||||||
@ -19,15 +26,38 @@ required_containers=(
|
|||||||
"archy-electrs-ui"
|
"archy-electrs-ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fail() { echo "$@" >&2; return 1; }
|
||||||
|
|
||||||
podman_names() {
|
podman_names() {
|
||||||
podman ps --format '{{.Names}}'
|
podman ps --format '{{.Names}}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
podman_all_names() {
|
||||||
|
podman ps -a --format '{{.Names}}'
|
||||||
|
}
|
||||||
|
|
||||||
container_running() {
|
container_running() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
podman inspect --format '{{.State.Running}}' "$name" 2>/dev/null
|
podman inspect --format '{{.State.Running}}' "$name" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
container_installed() {
|
||||||
|
local name="$1"
|
||||||
|
podman_all_names | grep -Fx "$name" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_if_not_installed() {
|
||||||
|
container_installed "$1" || skip "$1 not installed on this node"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The subset of required_containers actually installed on this node.
|
||||||
|
installed_required_containers() {
|
||||||
|
local c
|
||||||
|
for c in "${required_containers[@]}"; do
|
||||||
|
container_installed "$c" && echo "$c"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
bitcoin_rpc() {
|
bitcoin_rpc() {
|
||||||
curl -fsS --max-time 60 \
|
curl -fsS --max-time 60 \
|
||||||
--user "archipelago:$(cat /var/lib/archipelago/secrets/bitcoin-rpc-password)" \
|
--user "archipelago:$(cat /var/lib/archipelago/secrets/bitcoin-rpc-password)" \
|
||||||
@ -42,13 +72,16 @@ bitcoin_json() {
|
|||||||
|
|
||||||
@test "required containers are present" {
|
@test "required containers are present" {
|
||||||
# Under sustained 5× churn an app may still be mid-restart when this runs;
|
# Under sustained 5× churn an app may still be mid-restart when this runs;
|
||||||
# wait for the whole required set rather than single-shot.
|
# wait for the whole required set rather than single-shot. Only checks
|
||||||
|
# containers actually installed on this node (see installed_required_containers).
|
||||||
|
local targets; targets="$(installed_required_containers)"
|
||||||
|
[[ -n "$targets" ]] || skip "none of required_containers installed on this node"
|
||||||
local deadline=$(( $(date +%s) + 180 )) names missing
|
local deadline=$(( $(date +%s) + 180 )) names missing
|
||||||
while (( $(date +%s) < deadline )); do
|
while (( $(date +%s) < deadline )); do
|
||||||
names="$(podman_names)"; missing=""
|
names="$(podman_names)"; missing=""
|
||||||
for c in "${required_containers[@]}"; do
|
while IFS= read -r c; do
|
||||||
echo "$names" | grep -Fx "$c" >/dev/null || missing="$missing $c"
|
echo "$names" | grep -Fx "$c" >/dev/null || missing="$missing $c"
|
||||||
done
|
done <<< "$targets"
|
||||||
[[ -z "$missing" ]] && return 0
|
[[ -z "$missing" ]] && return 0
|
||||||
sleep 3
|
sleep 3
|
||||||
done
|
done
|
||||||
@ -56,12 +89,14 @@ bitcoin_json() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "required containers are running" {
|
@test "required containers are running" {
|
||||||
|
local targets; targets="$(installed_required_containers)"
|
||||||
|
[[ -n "$targets" ]] || skip "none of required_containers installed on this node"
|
||||||
local deadline=$(( $(date +%s) + 180 )) notrunning
|
local deadline=$(( $(date +%s) + 180 )) notrunning
|
||||||
while (( $(date +%s) < deadline )); do
|
while (( $(date +%s) < deadline )); do
|
||||||
notrunning=""
|
notrunning=""
|
||||||
for c in "${required_containers[@]}"; do
|
while IFS= read -r c; do
|
||||||
[[ "$(container_running "$c" 2>/dev/null)" == "true" ]] || notrunning="$notrunning $c"
|
[[ "$(container_running "$c" 2>/dev/null)" == "true" ]] || notrunning="$notrunning $c"
|
||||||
done
|
done <<< "$targets"
|
||||||
[[ -z "$notrunning" ]] && return 0
|
[[ -z "$notrunning" ]] && return 0
|
||||||
sleep 3
|
sleep 3
|
||||||
done
|
done
|
||||||
@ -69,12 +104,14 @@ bitcoin_json() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "bitcoin-knots RPC responds" {
|
@test "bitcoin-knots RPC responds" {
|
||||||
|
skip_if_not_installed bitcoin-knots
|
||||||
run bitcoin_rpc
|
run bitcoin_rpc
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
echo "$output" | python3 -c 'import json,sys; r=json.load(sys.stdin)["result"]; assert r["chain"] == "main" and r["blocks"] >= 0'
|
echo "$output" | python3 -c 'import json,sys; r=json.load(sys.stdin)["result"]; assert r["chain"] == "main" and r["blocks"] >= 0'
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "bitcoin backend is synced archival for electrumx/lnd gate" {
|
@test "bitcoin backend is synced archival for electrumx/lnd gate" {
|
||||||
|
skip_if_not_installed bitcoin-knots
|
||||||
run bitcoin_rpc
|
run bitcoin_rpc
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
@ -95,6 +132,7 @@ bitcoin_json() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "electrumx TCP port accepts connections" {
|
@test "electrumx TCP port accepts connections" {
|
||||||
|
skip_if_not_installed electrumx
|
||||||
run python3 - <<'PY'
|
run python3 - <<'PY'
|
||||||
import socket
|
import socket
|
||||||
s = socket.create_connection(("127.0.0.1", 50001), 3)
|
s = socket.create_connection(("127.0.0.1", 50001), 3)
|
||||||
@ -105,6 +143,7 @@ PY
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "lnd CLI getinfo succeeds" {
|
@test "lnd CLI getinfo succeeds" {
|
||||||
|
skip_if_not_installed lnd
|
||||||
# lnd RPC readiness lags the container "running" state (wallet auto-unlock on
|
# lnd RPC readiness lags the container "running" state (wallet auto-unlock on
|
||||||
# start), so retry until ready rather than single-shot. See lnd.bats note.
|
# start), so retry until ready rather than single-shot. See lnd.bats note.
|
||||||
run sh -lc 'for i in $(seq 1 30); do
|
run sh -lc 'for i in $(seq 1 30); do
|
||||||
@ -115,6 +154,7 @@ PY
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "lnd REST port accepts connections" {
|
@test "lnd REST port accepts connections" {
|
||||||
|
skip_if_not_installed lnd
|
||||||
run python3 - <<'PY'
|
run python3 - <<'PY'
|
||||||
import socket
|
import socket
|
||||||
s = socket.create_connection(("127.0.0.1", 18080), 3)
|
s = socket.create_connection(("127.0.0.1", 18080), 3)
|
||||||
@ -125,17 +165,20 @@ PY
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "mempool api endpoint responds" {
|
@test "mempool api endpoint responds" {
|
||||||
|
skip_if_not_installed mempool-api
|
||||||
# mempool-api reconnects to electrumx after a stack restart — retry ~180s.
|
# mempool-api reconnects to electrumx after a stack restart — retry ~180s.
|
||||||
run sh -lc 'for i in $(seq 1 60); do curl -fsS -m 5 -o /dev/null "http://127.0.0.1:8999/api/v1/backend-info" && exit 0; sleep 3; done; exit 1'
|
run sh -lc 'for i in $(seq 1 60); do curl -fsS -m 5 -o /dev/null "http://127.0.0.1:8999/api/v1/backend-info" && exit 0; sleep 3; done; exit 1'
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "mempool frontend responds" {
|
@test "mempool frontend responds" {
|
||||||
|
skip_if_not_installed mempool
|
||||||
run sh -lc 'for i in $(seq 1 60); do curl -fsS -m 5 -o /dev/null "http://127.0.0.1:4080/" && exit 0; sleep 3; done; exit 1'
|
run sh -lc 'for i in $(seq 1 60); do curl -fsS -m 5 -o /dev/null "http://127.0.0.1:4080/" && exit 0; sleep 3; done; exit 1'
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "bitcoin ui responds" {
|
@test "bitcoin ui responds" {
|
||||||
|
skip_if_not_installed archy-bitcoin-ui
|
||||||
# The companion (archy-bitcoin-ui) may have just been recreated by an earlier
|
# The companion (archy-bitcoin-ui) may have just been recreated by an earlier
|
||||||
# companion-survives test; its nginx takes a moment to serve. Retry ~120s
|
# companion-survives test; its nginx takes a moment to serve. Retry ~120s
|
||||||
# rather than single-shot.
|
# rather than single-shot.
|
||||||
@ -144,11 +187,13 @@ PY
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "lnd ui responds" {
|
@test "lnd ui responds" {
|
||||||
|
skip_if_not_installed archy-lnd-ui
|
||||||
run curl -fsS "http://127.0.0.1:18083/"
|
run curl -fsS "http://127.0.0.1:18083/"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "filebrowser responds" {
|
@test "filebrowser responds" {
|
||||||
|
skip_if_not_installed filebrowser
|
||||||
run curl -fsS "http://127.0.0.1:8083/"
|
run curl -fsS "http://127.0.0.1:8083/"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user