# Helpers for the netssh test environment. Sourced by run.sh, the tests, and # shell.sh inside the client container. # shellcheck disable=SC1091 . /state/env RESULTS=${RESULTS:-/tmp/netssh-results} # new_home gives each test file its own netssh config, cache, notes, and rc files. new_home() { HOME=$(mktemp -d /tmp/home.XXXXXX) export HOME XDG_CONFIG_HOME="$HOME/.config" XDG_CACHE_HOME="$HOME/.cache" SHELL=/bin/bash mkdir -p "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" unset TMUX ZELLIJ NETSSH_NETBOX_URL NETSSH_NETBOX_TOKEN cd "$HOME" || exit 1 } # base_config prints both NetBox instances followed by stdin. base_config() { cat /state/netssh.yaml -; } # config writes the netssh config: both NetBox instances plus stdin. config() { base_config >"$XDG_CONFIG_HOME/netssh.yaml"; } # config_only writes stdin as the whole config (no NetBox). config_only() { cat >"$XDG_CONFIG_HOME/netssh.yaml"; } ok() { echo "ok $CURRENT: $1" >>"$RESULTS" printf ' \033[32mok\033[0m %s\n' "$1" } bad() { echo "FAIL $CURRENT: $1" >>"$RESULTS" printf ' \033[31mFAIL\033[0m %s\n' "$1" printf '%s\n' "$2" | sed 's/^/ /' } # check NAME CMD... — CMD must succeed. check() { local name=$1 out shift if out=$("$@" 2>&1); then ok "$name"; else bad "$name" "exit $?: $out"; fi } # expect_out NAME WANT CMD... — CMD must succeed and print WANT (substring). expect_out() { local name=$1 want=$2 out rc shift 2 out=$("$@" 2>&1) && rc=0 || rc=$? if [ "$rc" = 0 ] && [[ $out == *"$want"* ]]; then ok "$name"; else bad "$name" "want: $want got (exit $rc): $out"; fi } # expect_eq NAME WANT CMD... — CMD must succeed and print exactly WANT. expect_eq() { local name=$1 want=$2 out rc shift 2 out=$("$@" 2>&1) && rc=0 || rc=$? if [ "$rc" = 0 ] && [ "$out" = "$want" ]; then ok "$name"; else bad "$name" "want: $want got (exit $rc): $out"; fi } # expect_fail NAME WANT CMD... — CMD must fail and print WANT (substring). expect_fail() { local name=$1 want=$2 out rc shift 2 out=$("$@" 2>&1) && rc=0 || rc=$? if [ "$rc" != 0 ] && [[ $out == *"$want"* ]]; then ok "$name"; else bad "$name" "want failure with: $want got (exit $rc): $out"; fi } # wait_for TIMEOUT CMD... — retry CMD every 0.2 s until it succeeds. wait_for() { local deadline=$((SECONDS + $1)) shift until "$@" >/dev/null 2>&1; do [ $SECONDS -ge $deadline ] && return 1 sleep 0.2 done } # --- rendering in a real terminal emulator (tests/16-render.sh) --- # Xvfb provides a screen, xterm renders the terminal, `import` takes a # screenshot, and tesseract reads it back, so checks see what a user sees. start_screen() { Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp >/dev/null 2>&1 & XVFB_PID=$! export DISPLAY=:99 wait_for 10 import -window root -crop 1x1+0+0 null: } stop_screen() { pkill -u "$(id -u)" xterm 2>/dev/null kill "$XVFB_PID" 2>/dev/null wait "$XVFB_PID" 2>/dev/null } # term CMD... — run CMD in a full-screen xterm (UTF-8, DejaVu Sans Mono). term() { pkill -u "$(id -u)" xterm 2>/dev/null xterm -u8 -geometry 190x52+0+0 -fa 'DejaVu Sans Mono' -fs 11 -bg '#1e1e1e' -fg '#d4d4d4' \ -T netssh -e "$@" >/dev/null 2>&1 & sleep 1 } # shot NAME — screenshot to /artifacts/NAME.png. shot() { import -window root "/artifacts/$1.png"; } # OCR preprocessing variants: no single one reads everything (dim hint lines, # red/green borders): plain luminance, stretched dark range for dim gray, and # the brightest color channel for colored text. OCR_VARIANTS=("-colorspace gray" "-colorspace gray -level 10%,40%" "-channel RGB -separate -evaluate-sequence max -level 10%,60%") # ocr_variant NAME I — OCR of screenshot NAME with preprocessing variant I. ocr_variant() { # shellcheck disable=SC2086 magick "/artifacts/$1.png" ${OCR_VARIANTS[$2]} -negate -resize 300% png:- | tesseract stdin stdout --psm 6 2>/dev/null } # screen_text NAME — the text of all variants combined. screen_text() { local i for i in "${!OCR_VARIANTS[@]}"; do ocr_variant "$1" "$i"; done } # ocr_norm maps characters OCR confuses in DejaVu Sans Mono (1/l/I/|, C/( , 0/O, # typographic dashes for "-") to one form; expected text gets the same mapping, # so comparisons stay strict about everything else. ocr_norm() { sed 's/[‐‑‒–—―−]/-/g' | tr 'lI|(O' '111C0'; } # screen_shows NAME TEXT... — take a screenshot and check that OCR finds every TEXT. screen_shows() { local name=$1 text want shift shot "$name" text=$(screen_text "$name" | ocr_norm) for want in "$@"; do want=$(printf '%s' "$want" | ocr_norm) [[ $text == *"$want"* ]] || return 1 done } # screen_count NAME TEXT — how often TEXT is on screenshot NAME: the highest # count of any OCR variant (they read the same screen, so they are not added). screen_count() { local want i n max=0 want=$(printf '%s' "$2" | ocr_norm) for i in "${!OCR_VARIANTS[@]}"; do n=$(ocr_variant "$1" "$i" | ocr_norm | grep -oF -- "$want" | wc -l) [ "$n" -gt "$max" ] && max=$n done echo "$max" }