Fix node-facts.sh bugs and add stub-curl integration tests
- Fix grep -qE calls to handle variables starting with '-' (add -- separator) - Remove local declarations at top level by wrapping main body in main() function - Replace bounded-worker scheduler with simpler temp file + wait -n pattern - Fix per-node fan-out to capture function output instead of re-calling - Add stub-curl integration tests that catch all three original issues - Add fixture compose files for avalanche, op-node+beacon, geth testing - Update test path-blacklist.txt to include entry starting with - Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
client:
|
||||||
|
image: ava-labs/avalanchego:v1.10.0
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.avalanche-mainnet-go.rule=PathPrefix(`/avalanche-mainnet-go`)"
|
||||||
|
- "traefik.http.middlewares.avalanche-mainnet-go.stripprefix.prefixes=/avalanche-mainnet-go"
|
||||||
|
x-upstreams:
|
||||||
|
network: avalanche
|
||||||
|
chain: avalanche-mainnet
|
||||||
|
chain: avalanche-mainnet
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
client:
|
||||||
|
image: sigp/lighthouse:v5.3.0
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.ethereum-mainnet-lighthouse.rule=PathPrefix(`/ethereum-mainnet-lighthouse`)"
|
||||||
|
- "traefik.http.middlewares.ethereum-mainnet-lighthouse.stripprefix.prefixes=/ethereum-mainnet-lighthouse"
|
||||||
|
x-upstreams:
|
||||||
|
network: ethereum
|
||||||
|
chain: ethereum-mainnet
|
||||||
|
chain: ethereum-mainnet
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
node:
|
||||||
|
image: us-docker.pkg.dev/oplabs/op-node:v1.19.0
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.op-base-mainnet.rule=PathPrefix(`/op-base-mainnet`)"
|
||||||
|
- "traefik.http.middlewares.op-base-mainnet.stripprefix.prefixes=/op-base-mainnet"
|
||||||
|
x-upstreams:
|
||||||
|
network: base
|
||||||
|
chain: op-base-mainnet
|
||||||
|
chain: op-base-mainnet
|
||||||
@@ -4,3 +4,4 @@ prysm
|
|||||||
beacon
|
beacon
|
||||||
nimbus
|
nimbus
|
||||||
ws
|
ws
|
||||||
|
-node
|
||||||
|
|||||||
Executable
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simple stub curl for node-facts.sh testing
|
||||||
|
# Returns minimal valid JSON responses
|
||||||
|
|
||||||
|
# Extract the URL (last http/https argument)
|
||||||
|
URL=""
|
||||||
|
METHOD=""
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [[ "$arg" == http* || "$arg" == https* ]]; then
|
||||||
|
URL="$arg"
|
||||||
|
fi
|
||||||
|
# Look for method in JSON data arguments
|
||||||
|
if echo "$arg" | grep -q "method"; then
|
||||||
|
METHOD=$(echo "$arg" | sed -n 's/.*"method":"\([^"]*\)".*/\1/p' | head -1)
|
||||||
|
[ -n "$METHOD" ] && break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# If method not found in data args, try to extract from the URL
|
||||||
|
if [ -z "$METHOD" ]; then
|
||||||
|
# Extract method from URL path for REST endpoints
|
||||||
|
case "$URL" in
|
||||||
|
*eth/v1/node/version*) METHOD="eth/v1/node/version" ;;
|
||||||
|
*eth/v1/node/peers*) METHOD="eth/v1/node/peers" ;;
|
||||||
|
*eth/v1/config/spec*) METHOD="eth/v1/config/spec" ;;
|
||||||
|
*eth/v1/node/syncing*) METHOD="eth/v1/node/syncing" ;;
|
||||||
|
*eth/v1/node/head*) METHOD="eth/v1/node/head" ;;
|
||||||
|
*optimism_version*) METHOD="optimism_version" ;;
|
||||||
|
*opp2p_peers*) METHOD="opp2p_peers" ;;
|
||||||
|
*optimism_rollupConfig*) METHOD="optimism_rollupConfig" ;;
|
||||||
|
*optimism_syncStatus*) METHOD="optimism_syncStatus" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Respond based on URL and method
|
||||||
|
case "$URL" in
|
||||||
|
*avalanche-mainnet-go*)
|
||||||
|
case "$METHOD" in
|
||||||
|
info.getNodeVersion)
|
||||||
|
echo '{"jsonrpc":"2.0","result":{"version":"avalanche/1.10.0","databaseVersion":"1.0.0"},"id":1}'
|
||||||
|
;;
|
||||||
|
info.peers)
|
||||||
|
echo '{"jsonrpc":"2.0","result":[{"version":"avalanche/1.10.0"},{"version":"avalanche/1.9.0"}],"id":1}'
|
||||||
|
;;
|
||||||
|
info.upgrades)
|
||||||
|
echo '{"jsonrpc":"2.0","result":{"apricotPhase1Time":4200000},"id":1}'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# For /ext/info without method, return version info
|
||||||
|
if [[ "$URL" == *"/ext/info"* ]]; then
|
||||||
|
echo '{"jsonrpc":"2.0","result":{"version":"avalanche/1.10.0","databaseVersion":"1.0.0"},"id":1}'
|
||||||
|
else
|
||||||
|
echo '{"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":1}'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*ethereum-mainnet-lighthouse* | *beacon* | *lighthouse*)
|
||||||
|
case "$METHOD" in
|
||||||
|
eth/v1/node/version*)
|
||||||
|
echo '{"version":"Lighthouse/v5.3.0"}'
|
||||||
|
;;
|
||||||
|
eth/v1/node/peers*)
|
||||||
|
echo '{"data":[]}'
|
||||||
|
;;
|
||||||
|
eth/v1/config/spec*)
|
||||||
|
echo '{"data":{}}'
|
||||||
|
;;
|
||||||
|
eth/v1/node/syncing*)
|
||||||
|
echo '{"data":{"head_slot":"100","syncing":false}}'
|
||||||
|
;;
|
||||||
|
eth/v1/node/head*)
|
||||||
|
echo '{"data":{"slot":"100","timestamp":"2026-10-06T12:00:00Z"}}'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo '{"message":"Not found"}'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*op-base-mainnet* | *op-node* | *kona*)
|
||||||
|
case "$METHOD" in
|
||||||
|
optimism_version* | */node*)
|
||||||
|
echo '{"result":"v1.19.0"}'
|
||||||
|
;;
|
||||||
|
opp2p_peers*)
|
||||||
|
echo '{"result":[]}'
|
||||||
|
;;
|
||||||
|
optimism_rollupConfig*)
|
||||||
|
echo '{"result":{}}'
|
||||||
|
;;
|
||||||
|
optimism_syncStatus*)
|
||||||
|
echo '{"result":{"current_block_number":"100","syncing":false}}'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo '{"message":"Not found"}'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*ethereum-mainnet-geth* | *geth*)
|
||||||
|
case "$METHOD" in
|
||||||
|
web3_clientVersion)
|
||||||
|
echo '{"jsonrpc":"2.0","result":"Geth/v1.13.0-stable","id":1}'
|
||||||
|
;;
|
||||||
|
admin_peers)
|
||||||
|
echo '{"jsonrpc":"2.0","result":[{"clientId":"Geth/v1.13.0"},{"clientId":"Geth/v1.12.0"}],"id":1}'
|
||||||
|
;;
|
||||||
|
eth_blockNumber)
|
||||||
|
echo '{"jsonrpc":"2.0","result":"0x64","id":1}'
|
||||||
|
;;
|
||||||
|
eth_getBlockByNumber)
|
||||||
|
echo '{"jsonrpc":"2.0","result":{"timestamp":"0x65a6b3d4","number":"0x64"},"id":1}'
|
||||||
|
;;
|
||||||
|
eth_syncing)
|
||||||
|
echo '{"jsonrpc":"2.0","result":false,"id":1}'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo '{"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":1}'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo '{"jsonrpc":"2.0","error":{"code":-32601,"message":"Unknown endpoint"},"id":1}'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
+53
-61
@@ -71,7 +71,7 @@ fi
|
|||||||
should_include() {
|
should_include() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
for word in "${blacklist[@]}"; do
|
for word in "${blacklist[@]}"; do
|
||||||
if echo "$path" | grep -qE "$word"; then
|
if echo "$path" | grep -qE -- "$word"; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -86,7 +86,7 @@ get_node_compose_files() {
|
|||||||
# Remove leading ./ and trailing .yml if present
|
# Remove leading ./ and trailing .yml if present
|
||||||
part="$(echo "$part" | sed 's|^\./||; s|\.yml$||')"
|
part="$(echo "$part" | sed 's|^\./||; s|\.yml$||')"
|
||||||
# Only include if it's a real compose file (contains hyphens, not base/rpc/etc)
|
# Only include if it's a real compose file (contains hyphens, not base/rpc/etc)
|
||||||
if echo "$part" | grep -q "-" && should_include "$part"; then
|
if echo "$part" | grep -q -- "-" && should_include "$part"; then
|
||||||
echo "$part"
|
echo "$part"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -133,7 +133,7 @@ get_client_info() {
|
|||||||
|
|
||||||
if [ -n "$services" ]; then
|
if [ -n "$services" ]; then
|
||||||
# Try to find image for this service
|
# Try to find image for this service
|
||||||
local image_line=$(grep -A 20 "^\s+${services}:" "$compose_file" | grep -oP 'image:\s*\K[^\s]+' | head -1)
|
local image_line=$(grep -A 20 -- "^\s+${services}:" "$compose_file" | grep -oP -- 'image:\s*\K[^\s]+' | head -1)
|
||||||
if [ -n "$image_line" ]; then
|
if [ -n "$image_line" ]; then
|
||||||
# Parse image:tag
|
# Parse image:tag
|
||||||
client_image="$(echo "$image_line" | cut -d':' -f1)"
|
client_image="$(echo "$image_line" | cut -d':' -f1)"
|
||||||
@@ -698,52 +698,51 @@ get_node_facts() {
|
|||||||
echo "${sig_result}|${elapsed}"
|
echo "${sig_result}|${elapsed}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run all data collection functions in parallel
|
# Run all data collection functions in parallel and capture output
|
||||||
local version_pid
|
local version_pid
|
||||||
local peers_pid
|
local peers_pid
|
||||||
local schedule_pid
|
local schedule_pid
|
||||||
local head_pid
|
local head_pid
|
||||||
local signals_pid
|
local signals_pid
|
||||||
|
local version_result
|
||||||
|
local peers_result
|
||||||
|
local schedule_result
|
||||||
|
local head_result
|
||||||
|
local signals_result
|
||||||
|
|
||||||
get_version &
|
version_result=$(get_version) &
|
||||||
version_pid=$!
|
version_pid=$!
|
||||||
|
|
||||||
get_peers &
|
peers_result=$(get_peers) &
|
||||||
peers_pid=$!
|
peers_pid=$!
|
||||||
|
|
||||||
get_schedule &
|
schedule_result=$(get_schedule) &
|
||||||
schedule_pid=$!
|
schedule_pid=$!
|
||||||
|
|
||||||
get_head &
|
head_result=$(get_head) &
|
||||||
head_pid=$!
|
head_pid=$!
|
||||||
|
|
||||||
get_signals &
|
signals_result=$(get_signals) &
|
||||||
signals_pid=$!
|
signals_pid=$!
|
||||||
|
|
||||||
# Wait for all to complete
|
# Wait for all to complete
|
||||||
local results=()
|
|
||||||
for pid in $version_pid $peers_pid $schedule_pid $head_pid $signals_pid; do
|
for pid in $version_pid $peers_pid $schedule_pid $head_pid $signals_pid; do
|
||||||
wait "$pid"
|
wait "$pid"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Collect results
|
# Collect results from captured output
|
||||||
version_result=$(get_version)
|
|
||||||
version_json=$(echo "$version_result" | cut -d'|' -f1)
|
version_json=$(echo "$version_result" | cut -d'|' -f1)
|
||||||
version_time=$(echo "$version_result" | cut -d'|' -f2)
|
version_time=$(echo "$version_result" | cut -d'|' -f2)
|
||||||
|
|
||||||
peers_result=$(get_peers)
|
|
||||||
peers_json=$(echo "$peers_result" | cut -d'|' -f1)
|
peers_json=$(echo "$peers_result" | cut -d'|' -f1)
|
||||||
peers_time=$(echo "$peers_result" | cut -d'|' -f2)
|
peers_time=$(echo "$peers_result" | cut -d'|' -f2)
|
||||||
|
|
||||||
schedule_result=$(get_schedule)
|
|
||||||
schedule_json=$(echo "$schedule_result" | cut -d'|' -f1)
|
schedule_json=$(echo "$schedule_result" | cut -d'|' -f1)
|
||||||
schedule_time=$(echo "$schedule_result" | cut -d'|' -f2)
|
schedule_time=$(echo "$schedule_result" | cut -d'|' -f2)
|
||||||
|
|
||||||
head_result=$(get_head)
|
|
||||||
head_json=$(echo "$head_result" | cut -d'|' -f1)
|
head_json=$(echo "$head_result" | cut -d'|' -f1)
|
||||||
head_time=$(echo "$head_result" | cut -d'|' -f2)
|
head_time=$(echo "$head_result" | cut -d'|' -f2)
|
||||||
|
|
||||||
signals_result=$(get_signals)
|
|
||||||
signals_json=$(echo "$signals_result" | cut -d'|' -f1)
|
signals_json=$(echo "$signals_result" | cut -d'|' -f1)
|
||||||
signals_time=$(echo "$signals_result" | cut -d'|' -f2)
|
signals_time=$(echo "$signals_result" | cut -d'|' -f2)
|
||||||
|
|
||||||
@@ -829,8 +828,9 @@ get_node_facts() {
|
|||||||
echo "$doc"
|
echo "$doc"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main execution
|
# Main execution function
|
||||||
if [ -n "$node_path" ]; then
|
main() {
|
||||||
|
if [ -n "$node_path" ]; then
|
||||||
# Single node mode
|
# Single node mode
|
||||||
if should_include "$node_path"; then
|
if should_include "$node_path"; then
|
||||||
get_node_facts "$node_path" "$PRETTY"
|
get_node_facts "$node_path" "$PRETTY"
|
||||||
@@ -838,67 +838,59 @@ if [ -n "$node_path" ]; then
|
|||||||
echo "error: node_path '$node_path' is blacklisted" >&2
|
echo "error: node_path '$node_path' is blacklisted" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# All nodes mode - iterate over all node compose files
|
# All nodes mode - iterate over all node compose files
|
||||||
local node_files=()
|
node_files=()
|
||||||
while IFS= read -r node_file; do
|
while IFS= read -r node_file; do
|
||||||
[ -z "$node_file" ] && continue
|
[ -z "$node_file" ] && continue
|
||||||
node_files+=("$node_file")
|
node_files+=("$node_file")
|
||||||
done < <(get_node_compose_files)
|
done < <(get_node_compose_files)
|
||||||
|
|
||||||
# Parallel execution with bounded workers
|
# Create temp directory for output files
|
||||||
local max_workers=${MAX_WORKERS:-8}
|
temp_dir=$(mktemp -d)
|
||||||
local pids=()
|
|
||||||
local results=()
|
# Track pids and temp files
|
||||||
local current_workers=0
|
pids=()
|
||||||
local index=0
|
temp_files=()
|
||||||
|
max_workers=${MAX_WORKERS:-8}
|
||||||
|
|
||||||
for node_file in "${node_files[@]}"; do
|
for node_file in "${node_files[@]}"; do
|
||||||
# Wait if we have too many workers
|
temp_file="$temp_dir/$(basename "$node_file" .yml).json"
|
||||||
while [ $current_workers -ge $max_workers ]; do
|
( get_node_facts "$node_file" "$PRETTY" > "$temp_file" ) &
|
||||||
|
pids+=($!)
|
||||||
|
temp_files+=("$temp_file")
|
||||||
|
|
||||||
|
# Wait if we reached max workers
|
||||||
|
if [ ${#pids[@]} -ge $max_workers ]; then
|
||||||
|
wait -n
|
||||||
|
# Remove the completed pid and corresponding temp file
|
||||||
|
completed_pid="$?"
|
||||||
for i in "${!pids[@]}"; do
|
for i in "${!pids[@]}"; do
|
||||||
if ! kill -0 "${pids[$i]}" 2>/dev/null; then
|
if [ "${pids[$i]}" = "$completed_pid" ]; then
|
||||||
# Process completed
|
|
||||||
local result=$(wait "${pids[$i]}" 2>/dev/null && echo "" || echo "")
|
|
||||||
results+=("$result")
|
|
||||||
unset pids[$i]
|
unset pids[$i]
|
||||||
pids=("${pids[@]}")
|
unset temp_files[$i]
|
||||||
current_workers=$((current_workers - 1))
|
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# If still at max, sleep briefly
|
pids=("${pids[@]}")
|
||||||
if [ $current_workers -ge $max_workers ]; then
|
temp_files=("${temp_files[@]}")
|
||||||
sleep 0.1
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Start new worker
|
# Wait for all remaining workers
|
||||||
{
|
wait
|
||||||
local result=$(get_node_facts "$node_file" "$PRETTY")
|
|
||||||
echo "${result}"
|
# Output results in order
|
||||||
} &
|
for temp_file in "${temp_files[@]}"; do
|
||||||
pids+=($!)
|
cat "$temp_file"
|
||||||
current_workers=$((current_workers + 1))
|
|
||||||
index=$((index + 1))
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# Wait for remaining workers
|
# Clean up temp directory
|
||||||
for pid in "${pids[@]}"; do
|
rm -rf "$temp_dir"
|
||||||
wait "$pid" 2>/dev/null
|
fi
|
||||||
done
|
}
|
||||||
|
|
||||||
# Since we're outputting JSON lines, we already printed results in the workers
|
# Call main function
|
||||||
# But to maintain order, let's do a simpler sequential approach for JSON lines
|
main
|
||||||
# Actually, let's redo this properly for streaming JSON lines
|
|
||||||
|
|
||||||
# Clear the partial parallel results and do sequential for JSON lines
|
|
||||||
echo "" # Separator
|
|
||||||
|
|
||||||
# Sequential approach for JSON lines (to maintain order)
|
|
||||||
for node_file in "${node_files[@]}"; do
|
|
||||||
get_node_facts "$node_file" "$PRETTY"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
@@ -67,6 +67,137 @@ fi
|
|||||||
|
|
||||||
rm -rf "$TMP_DIR"
|
rm -rf "$TMP_DIR"
|
||||||
|
|
||||||
|
echo "=== Testing stub-curl integration ==="
|
||||||
|
|
||||||
|
# Stub-curl test: test with fixture compose directory and stub curl
|
||||||
|
STUB_TEST_DIR="$BASEPATH/node-facts-test"
|
||||||
|
if [ ! -d "$STUB_TEST_DIR" ]; then
|
||||||
|
echo "SKIP: Stub test directory not found"
|
||||||
|
else
|
||||||
|
# Test 6: Test with stub curl - single node mode
|
||||||
|
TMP_DIR=$(mktemp -d)
|
||||||
|
cd "$TMP_DIR" || exit 1
|
||||||
|
|
||||||
|
# Copy test files
|
||||||
|
cp -r "$STUB_TEST_DIR"/* .
|
||||||
|
|
||||||
|
# Make stub curl executable and put it first in PATH
|
||||||
|
STUB_CURL_DIR="$TMP_DIR/stub-curl"
|
||||||
|
chmod +x "$STUB_CURL_DIR/curl"
|
||||||
|
|
||||||
|
# Test single node mode with stub curl
|
||||||
|
ORIGINAL_PATH="$PATH"
|
||||||
|
export PATH="$STUB_CURL_DIR:$PATH"
|
||||||
|
|
||||||
|
START_TIME=$(date +%s)
|
||||||
|
output=$(timeout 60 ./node-facts.sh "avalanche-mainnet-go" 2>&1)
|
||||||
|
END_TIME=$(date +%s)
|
||||||
|
exit_code=$?
|
||||||
|
export PATH="$ORIGINAL_PATH"
|
||||||
|
|
||||||
|
if [ $exit_code -eq 0 ] && [ $((END_TIME - START_TIME)) -lt 10 ]; then
|
||||||
|
echo "PASS: Stub curl single node mode exits 0 and completes in < 10s"
|
||||||
|
# Validate it's JSON and has the right schema
|
||||||
|
if echo "$output" | jq -e '.schema, .node_path, .network, .client' >/dev/null 2>&1; then
|
||||||
|
echo "PASS: Single node output is valid JSON with required schema fields"
|
||||||
|
else
|
||||||
|
echo "FAIL: Single node output is not valid JSON schema"
|
||||||
|
echo "Output: $output"
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "FAIL: Stub curl single node mode failed with exit code $exit_code, took $((END_TIME - START_TIME))s"
|
||||||
|
echo "Output: $output"
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 7: Test with stub curl - all nodes mode
|
||||||
|
START_TIME=$(date +%s)
|
||||||
|
output=$(timeout 60 ./node-facts.sh 2>&1)
|
||||||
|
END_TIME=$(date +%s)
|
||||||
|
exit_code=$?
|
||||||
|
|
||||||
|
if [ $exit_code -eq 0 ] && [ $((END_TIME - START_TIME)) -lt 10 ]; then
|
||||||
|
echo "PASS: Stub curl all-nodes mode exits 0 and completes in < 10s"
|
||||||
|
|
||||||
|
# Count JSON lines - should have at least 3 valid JSON lines (one per node)
|
||||||
|
json_lines=$(echo "$output" | grep -c '^{.*}$' || echo "0")
|
||||||
|
if [ "$json_lines" -ge 3 ]; then
|
||||||
|
echo "PASS: All-nodes mode produced $json_lines JSON lines (>= 3)"
|
||||||
|
else
|
||||||
|
echo "FAIL: All-nodes mode produced only $json_lines JSON lines, expected >= 3"
|
||||||
|
echo "Output: $output"
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate each JSON line against schema
|
||||||
|
valid_count=0
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if echo "$line" | jq -e '.schema, .at, .host, .node_path, .network, .client, .version, .peers, .schedule, .signals, .head, .timing_ms' >/dev/null 2>&1; then
|
||||||
|
valid_count=$((valid_count + 1))
|
||||||
|
else
|
||||||
|
echo "Invalid JSON line: $line"
|
||||||
|
fi
|
||||||
|
done <<< "$(echo "$output" | grep '^{.*}$')"
|
||||||
|
|
||||||
|
if [ $valid_count -ge 3 ]; then
|
||||||
|
echo "PASS: All $valid_count JSON lines have valid schema v1 fields"
|
||||||
|
else
|
||||||
|
echo "FAIL: Only $valid_count valid JSON lines out of $json_lines total"
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for no stderr noise (grep/sed/jq errors)
|
||||||
|
if echo "$output" | grep -q "grep: invalid argument\|local: can only be used\|sed:\|jq:"; then
|
||||||
|
echo "FAIL: Found stderr noise in output: $(echo "$output" | grep "grep: invalid argument\|local: can only be used\|sed:\|jq:")"
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "PASS: No stderr noise (grep/sed/jq errors) in output"
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "FAIL: Stub curl all-nodes mode failed with exit code $exit_code, took $((END_TIME - START_TIME))s"
|
||||||
|
echo "Output: $output"
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 8: Test that path-blacklist with - prefix works
|
||||||
|
# Add a compose file that should be excluded by the -node blacklist entry
|
||||||
|
echo "version: '3'" > test-node-blacklist.yml
|
||||||
|
echo "services:" >> test-node-blacklist.yml
|
||||||
|
echo " client:" >> test-node-blacklist.yml
|
||||||
|
echo " image: test/node-client:v1.0.0" >> test-node-blacklist.yml
|
||||||
|
echo "chain: test-node-blacklist" >> test-node-blacklist.yml
|
||||||
|
|
||||||
|
# Update COMPOSE_FILE to include the blacklisted file
|
||||||
|
export COMPOSE_FILE="avalanche-mainnet-go.yml:test-node-blacklist.yml"
|
||||||
|
output=$(timeout 60 ./node-facts.sh 2>&1)
|
||||||
|
exit_code=$?
|
||||||
|
|
||||||
|
if [ $exit_code -eq 0 ]; then
|
||||||
|
# Should not include test-node-blacklist
|
||||||
|
if echo "$output" | grep -q "test-node-blacklist"; then
|
||||||
|
echo "FAIL: Blacklisted node was included in output"
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "PASS: Blacklist with - prefix works correctly"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "FAIL: Blacklist test failed with exit code $exit_code"
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "=== Testing JSON schema validation ==="
|
echo "=== Testing JSON schema validation ==="
|
||||||
|
|
||||||
# Function to validate schema
|
# Function to validate schema
|
||||||
|
|||||||
Reference in New Issue
Block a user