Files
nmon/scripts/gate-m2.sh
jleung f7b394585b Maintainability is a hard rule: standard, golangci-lint config, lint in every gate
- docs/MAINTAINABILITY.md: limits and architecture rules
- .golangci.yml (pinned v2.14.0) + file-size check; make lint runs first in every gate
- web/go.mod stops the Go tool from walking web/node_modules
- layering: Target -> internal/snmptarget, TableKey -> profile.Metric.TableKey; poller no longer imports discovery

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-22 18:54:32 +00:00

106 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Gate M2: every fixture's expect.yaml passes (including v1 and v3) and the PDU
# budget holds. Procedure: docs/milestones/M2-discovery-and-profiles.md.
# 1. go test -race ./internal/fixtures: every testdata/snmprec/*.expect.yaml over IPv6 and IPv4.
# 2. `nmon profile test` against the v1 and v3 fixtures served by the simagent CLI.
# 3. With NMON_LIBRENMS_DIR set: real LibreNMS recordings must match their profile, with no type mismatch.
# Needs: go, make, awk.
set -uo pipefail
# Maintainability is a hard rule (docs/MAINTAINABILITY.md): no gate passes with lint findings.
( cd "$(git rev-parse --show-toplevel)" && make lint ) || { echo "FAIL: make lint"; exit 1; }
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
TMP="$(mktemp -d)"
FAILS=0
AGENT_PID=""
pass() { echo "PASS: $*"; }
fail() { echo "FAIL: $*"; FAILS=$((FAILS + 1)); }
die() { fail "$*"; exit 1; }
cleanup() {
[ -n "$AGENT_PID" ] && kill "$AGENT_PID" 2>/dev/null
rm -rf "$TMP"
}
trap cleanup EXIT
trap 'exit 130' INT TERM
pick_port() {
local p
while :; do
p=$((20000 + RANDOM % 30000))
ss -H -lntu 2>/dev/null | awk '{print $5}' | grep -q "[:.]$p\$" || { echo "$p"; return; }
done
}
# 1. Fixture harness, without the real-device pass (that is step 3).
echo "== fixtures (go test -race ./internal/fixtures)"
if env -u NMON_LIBRENMS_DIR go test -race -count=1 -v ./internal/fixtures/... >"$TMP/fixtures.log" 2>&1; then
pass "go test -race ./internal/fixtures"
else
grep -E "^\s*(---|===) (FAIL)|^\s+\S+_test.go:[0-9]+:|^FAIL|panic" "$TMP/fixtures.log" | head -60
fail "go test -race ./internal/fixtures"
fi
printf '%-24s %-6s %s\n' FIXTURE RESULT SECONDS
awk '
/^ --- (PASS|FAIL): TestFixtures\/[^\/ ]+ / { n = $3; sub(/^TestFixtures\//, "", n); t = $4; gsub(/[()s]/, "", t); printf "%-24s %-6s %s\n", n, $2 == "PASS:" ? "PASS" : "FAIL", t }
' "$TMP/fixtures.log" | sort
N="$(grep -cE '^ --- PASS: TestFixtures/[^/ ]+ ' "$TMP/fixtures.log")"
[ "$N" -ge 25 ] && pass "$N fixtures passed over IPv6 and IPv4" || fail "only $N fixtures passed"
for t in TestRenumberKeepsInterfaceIDs TestRMAOpensHardwareReplacedReview TestStackSwapOpensOneReviewAndMergesNothing TestV1DiscoversWithGetNext TestV3AuthPrivWorks; do
grep -q -- "--- PASS: $t " "$TMP/fixtures.log" && pass "$t" || fail "$t"
done
# 2. nmon profile test against the v1 and v3 fixtures.
echo "== nmon profile test (v1, v3)"
make build >"$TMP/build.log" 2>&1 || { tail -n 20 "$TMP/build.log"; die "make build"; }
go build -o "$TMP/simagent" ./internal/simagent/cmd/simagent || die "build simagent"
serve() { # serve <fixture> -> sets PORT, AGENT_PID
PORT="$(pick_port)"
"$TMP/simagent" -addr "[::1]:$PORT" -agent "testdata/snmprec/$1.agent.yaml" >"$TMP/simagent-$1.log" 2>&1 &
AGENT_PID=$!
for _ in $(seq 1 50); do grep -q listening "$TMP/simagent-$1.log" 2>/dev/null && break; sleep 0.1; done
grep -q listening "$TMP/simagent-$1.log" || { cat "$TMP/simagent-$1.log"; die "simagent $1 did not start"; }
}
stop() { kill "$AGENT_PID" 2>/dev/null; wait "$AGENT_PID" 2>/dev/null; AGENT_PID=""; }
serve v1
if ./nmon profile test ::1 --port "$PORT" --version v1 --community nmon-v1 >"$TMP/v1.out" 2>&1 &&
grep -q '^profile generic' "$TMP/v1.out" && grep -q 'counter_32bit_fast_link' "$TMP/v1.out" &&
grep -q ' 0 type mismatches' "$TMP/v1.out"; then
pass "v1: $(grep -m1 '^interfaces' "$TMP/v1.out" | tr -s ' '), $(grep 'total ' "$TMP/v1.out" | tr -s ' ')"
else
cat "$TMP/v1.out"; fail "nmon profile test against the v1 fixture"
fi
stop
serve v3
if ./nmon profile test ::1 --port "$PORT" --version v3 --v3-user nmon --v3-auth-proto SHA256 --v3-auth-pass authpass-123 \
--v3-priv-proto AES --v3-priv-pass privpass-123 >"$TMP/v3.out" 2>&1 &&
grep -q '^profile generic' "$TMP/v3.out" && grep -q ' 0 type mismatches' "$TMP/v3.out"; then
pass "v3 authPriv: $(grep -m1 '^interfaces' "$TMP/v3.out" | tr -s ' '), $(grep 'total ' "$TMP/v3.out" | tr -s ' ')"
else
cat "$TMP/v3.out"; fail "nmon profile test against the v3 fixture"
fi
stop
# 3. Real device recordings, when a LibreNMS checkout is available.
echo "== real-device recordings"
if [ -n "${NMON_LIBRENMS_DIR:-}" ]; then
if go test -count=1 -v -run TestLibreNMSRecordings ./internal/fixtures/ >"$TMP/real.log" 2>&1; then
pass "real recordings under $NMON_LIBRENMS_DIR"
else
grep -E "^\s+\S+_test.go:[0-9]+:|^--- FAIL|^ --- FAIL" "$TMP/real.log" | grep -v REAL | head -40
fail "real recordings"
fi
grep -h REAL "$TMP/real.log" | sed -E 's/^ *[a-z_]+_test.go:[0-9]+: //' | sort
else
echo "skipped: NMON_LIBRENMS_DIR is not set"
fi
if [ "$FAILS" -eq 0 ]; then echo "GATE M2: PASS"; else echo "GATE M2: FAIL ($FAILS)"; exit 1; fi