* asdf * Implement SphereOfInfluenceModule for traffic management and eviction tracking * Implement Sphere of Influence module for dynamic hop limit adjustment and role-based floor * Update SAMPLING_DENOMINATOR to improve mesh size estimation accuracy * Add debug logging for scale factor estimation and per-hop node counts in SphereOfInfluenceModule * Enable variable hop limits and role-based hop floors in Sphere of Influence module * Respond to copilot review * Disable variable hop limits and role-based hop floors in Sphere of Influence module * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Implement adaptive sampling for unique node ID tracking in Sphere of Influence module * Add state persistence for Sphere of Influence module * Enhance Sphere of Influence module with state management and adaptive sampling adjustments * Refactor hop scaling functionality: remove SphereOfInfluenceModule and introduce HopScalingModule - Deleted SphereOfInfluenceModule.h, consolidating its responsibilities into the new HopScalingModule. - Added HopScalingModule.h and HopScalingModule.cpp to manage hop scaling logic, including eviction tracking and sampling-based mesh size estimation. - Implemented methods for recording evictions and packet senders, estimating scale factors, and computing required hops based on node activity. - Introduced state persistence for hop scaling parameters to maintain continuity across reboots. - Enhanced thread safety and modularity by utilizing concurrency features. * Guard out STM32. Sowwy. * Refactor HopScalingModule: enhance sampling logic and improve state management * Add unit tests for HopScalingModule: implement mock database and various test scenarios * Refactor test output in HopScalingModule tests: replace printf with TEST_MESSAGE for better integration with Unity * Refactor HopScalingModule logging: replace lastStatusMode with descriptive mode names for improved readability * Refactor test_main.cpp: change NodeNum variable to static and improve comments for clarity * Remove unnecessary delay in setup function for improved test performance * Add missing include for MeshTypes in test_main.cpp * Refactor HopScalingModule tests: enhance mesh topology scenarios and improve test clarity * Update HopScalingModule tests: flesh out node scenarios and improve clarity for dense and sparse mesh cases * Fix politeness factor calculations in HopScalingModule and update related test scenarios for clarity. Remove outdated design doc. * Enhance HopScalingModule: add sampled estimate for scaling decisions and refactor initial run state management * Add sample traffic injection for HopScaling tests to enhance sampledEst visibility * Enhance HopScalingModule: adjust windowFraction calculation for early triggers and improve test output formatting * Enhance HopScalingModule: add jitter functionality to sampling denominator and update tests for consistent behavior * Enhance HopScalingModule: implement adaptive sampling denominator adjustment and add reset functionality for tests * Enhance HopScalingTestShim: add test-only clock and window helpers, update injectSampleTraffic for adaptive sampling, and improve scenario summary output * Enhance HopScalingModule: add detailed documentation for functions, improve clarity of jitter and sampling logic, and reset functionality in tests * Enhance HopScalingModule: add evictionEstimate parameter to estimateScaleFactor and update related logging for improved mesh size estimation * Enhance HopScalingModule: adjust effective rolls calculation for improved accuracy, add eviction estimate logic, responding to all copilot review points * Implement CompactHistogram for parallel hop scaling sampling - Added CompactHistogram class to track node hop distances with bitwise sampling. - Integrated CompactHistogram into HopScalingModule for independent packet sampling. - Updated NodeDB to feed both the hop scaling module and the new histogram sampler. - Enhanced HopScalingModule with methods to sample packets for the histogram and retrieve hop distribution statistics. - Implemented tests for CompactHistogram functionality, including sampling, window rolling, and adaptive denominator scaling. - Updated existing tests to validate the integration of the new histogram sampling mechanism. * Enhance CompactHistogram and HopScalingModule: add per-hop distribution functionality, improve time handling for unit tests, and refine test setup for deterministic behavior * CompactHistogram: add mesh size estimation, improve entry replacement logic, and update logging for per-hop distribution * Refactor HopScalingModule and CompactHistogram integration - Removed the suggestedHopFromCompactHistogram function to streamline hop suggestion logic. - Updated HopScalingModule to directly utilize CompactHistogram's internal methods for hop suggestions and sampling. - Enhanced logging in HopScalingModule to provide detailed histogram statistics. - Modified test cases to ensure comprehensive coverage of new histogram behaviors and sampling logic. - Improved node ID distribution in tests to better exercise sampling mechanisms. - Ensured that filtering denominators are held for 12 hours before dropping, enhancing stability in sampling. * Refactor CompactHistogram to support 13-hour activity tracking and introduce politeness regimes - Updated the bitfield structure to accommodate 13-hour seen tracking. - Changed the logic in rollHour() to analyze activity over the last 0-2 hours vs. 1-3 hours for politeness factor calculation. - Introduced three politeness levels: GENEROUS, DEFAULT, and STRICT based on recent activity ratios. - Adjusted filtering and sampling logic to reflect the new 13-hour tracking period. - Updated unit tests to validate new behavior and ensure proper functionality of politeness regimes. * Enhance CompactHistogram and HopScalingModule for improved sampling and decision-making - Introduced a session-specific hash seed in CompactHistogram to reduce bias in node ID sampling. - Updated sampling logic to use hashed node IDs instead of raw IDs for filtering and entry management. - Added histogram rollover tracking in HopScalingModule to ensure proper decision-making after initial data collection. - Adjusted logging to reflect the active state of the histogram and its comparison with NodeDB advisory hops. - Enhanced unit tests to validate new sampling logic and memory layout changes. * Expose hashNodeId for testing in CompactHistogram * Add mesh trend statistics to CompactHistogram for enhanced activity tracking * Implement histogram state persistence in CompactHistogram with save and load functions * Refactor CompactHistogram to improve entry management and enhance rollHour logging * feat: add HopScalingModule for adaptive hop limit recommendations Introduces HopScalingModule, a sampled hop-distance histogram that recommends the minimum hop limit needed to reach ~40 nodes, and automatically reducing the hops as the mesh grows. Key design: - 512-byte packed histogram (128 × 4-byte Record entries) embedded in a new HopScalingModule. - Each Record: 16-bit node hash, 3-bit hop distance, 13-bit seen bitmap - Sampling filter: only nodes where (hash & (denom-1)) == 0 are kept; denominator doubles on overflow and halves when utilisation is low - Hourly rollHour(): tallies per-hop counts, walks scaled buckets to find the minimum hop satisfying TARGET_AFFECTED_NODES (40), applies a politeness extension based on recent/older activity ratio, shifts all seen bitmaps, and persists state to /prefs/hopScalingState.bin - Hop recommendation gated by bootstrap (requires >=1 rollHour before overriding HOP_MAX) - NodeDB calls samplePacketForHistogram() on every non-MQTT rx packet - Module also estimates total mesh size and logs useful information about mesh characteristics. Changes: - src/modules/HopScalingModule.h/.cpp: new module - src/mesh/NodeDB.cpp: wire up samplePacketForHistogram - src/mesh/Router.cpp: consume getLastRequiredHop() - test/test_hop_scaling/: 12-test suite covering all mesh topologies and anticipated operational requirements * test: increase run iterations in sparse to dense transition test * feat: refactor HopScalingModule to use RUNS_PER_HOUR constant and improve logging * feat: enhance HopScalingModule with filtering denominator management and add tests for state transitions * refactor: remove CompactHistogram module and related files * address copilot review comments * Tweak: packet sampling only lora * ove role-based hop floor logic and related definitions into the module - keep it in one place. * Refactor MockNodeDB to use nodeInfoLiteSetBit for MQTT flag setting * Refactor hop scaling parameters and logic to integer maths and put default values in defaults.h. Small flash size reduction, no functional impact. * Update unit test preprocessor directives to PIO_UNIT_TESTING for consistency * refactor: improve test output organization and clarity in hop scaling tests --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
275 lines
11 KiB
Bash
Executable File
275 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# mcp-server hardware test runner.
|
|
#
|
|
# Auto-detects connected Meshtastic devices, maps each to its PlatformIO env
|
|
# via the same role table the pytest fixtures use, exports the right
|
|
# MESHTASTIC_MCP_ENV_* env vars, and invokes pytest.
|
|
#
|
|
# Usage:
|
|
# ./run-tests.sh # full suite, default pytest args
|
|
# ./run-tests.sh tests/mesh # subset (any pytest args pass through)
|
|
# ./run-tests.sh --force-bake # override one default with another
|
|
# MESHTASTIC_MCP_ENV_NRF52=foo ./run-tests.sh # override env per role
|
|
# MESHTASTIC_MCP_SEED=ci-run-42 ./run-tests.sh # override PSK seed
|
|
#
|
|
# If zero supported devices are detected, only the unit tier runs.
|
|
#
|
|
# Also restores `userPrefs.jsonc` from the session-backup sidecar if a prior
|
|
# run exited abnormally (belt to conftest.py's atexit suspenders).
|
|
|
|
set -euo pipefail
|
|
|
|
# cd to the script's directory so relative paths resolve consistently no
|
|
# matter where the user invoked from.
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
cd "$SCRIPT_DIR"
|
|
|
|
VENV_PY="$SCRIPT_DIR/.venv/bin/python"
|
|
if [[ ! -x $VENV_PY ]]; then
|
|
echo "error: $VENV_PY not found or not executable." >&2
|
|
echo " Bootstrap the venv first:" >&2
|
|
echo " cd $SCRIPT_DIR && python3 -m venv .venv && .venv/bin/pip install -e '.[test]'" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Resolve firmware root the same way conftest.py does (this script sits in
|
|
# mcp-server/, firmware repo root is one level up).
|
|
FIRMWARE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
USERPREFS_PATH="$FIRMWARE_ROOT/userPrefs.jsonc"
|
|
USERPREFS_SIDECAR="$USERPREFS_PATH.mcp-session-bak"
|
|
|
|
# ---------- Pre-flight: recover stale userPrefs.jsonc from prior crash ----
|
|
# If conftest.py's atexit hook didn't fire (SIGKILL, kernel panic, OS
|
|
# restart), the sidecar is the ground truth. Self-heal before running so we
|
|
# don't bake the previous run's dirty state into this run's firmware.
|
|
if [[ -f $USERPREFS_SIDECAR ]]; then
|
|
echo "[pre-flight] found $USERPREFS_SIDECAR from a prior abnormal exit;" >&2
|
|
echo " restoring userPrefs.jsonc before starting." >&2
|
|
cp "$USERPREFS_SIDECAR" "$USERPREFS_PATH"
|
|
rm -f "$USERPREFS_SIDECAR"
|
|
fi
|
|
|
|
# If userPrefs.jsonc has uncommitted changes BEFORE the run starts, that's
|
|
# worth warning about — tests will snapshot this dirty state and restore to
|
|
# it at the end, which may not be what the operator wants.
|
|
if command -v git >/dev/null 2>&1; then
|
|
cd "$FIRMWARE_ROOT"
|
|
# Capture the git status into a local first — SC2312 flags command
|
|
# substitution inside `[[ -n ... ]]` because the exit code of `git
|
|
# status` is masked. A two-step assignment makes the failure path
|
|
# explicit (non-git, missing file) and keeps the bracket test clean.
|
|
_git_status_porcelain="$(git status --porcelain userPrefs.jsonc 2>/dev/null || true)"
|
|
if [[ -n $_git_status_porcelain ]]; then
|
|
echo "[pre-flight] warning: userPrefs.jsonc has uncommitted changes." >&2
|
|
echo " Tests will snapshot THIS state and restore to it" >&2
|
|
echo " at teardown. If that's not intended, run:" >&2
|
|
echo " git checkout userPrefs.jsonc" >&2
|
|
echo " and re-invoke." >&2
|
|
fi
|
|
cd "$SCRIPT_DIR"
|
|
fi
|
|
|
|
# ---------- Seed default --------------------------------------------------
|
|
# Per-machine default so repeated runs from the same operator land on the
|
|
# same PSK (makes --assume-baked valid across invocations). Operator can
|
|
# override with an explicit env var if they want isolation (e.g. CI).
|
|
if [[ -z ${MESHTASTIC_MCP_SEED-} ]]; then
|
|
WHO="$(whoami 2>/dev/null || echo anon)"
|
|
HOST="$(hostname -s 2>/dev/null || echo host)"
|
|
export MESHTASTIC_MCP_SEED="mcp-${WHO}-${HOST}"
|
|
fi
|
|
|
|
# ---------- Flash progress log --------------------------------------------
|
|
# pio.py / hw_tools.py tee subprocess output (pio run -t upload, esptool,
|
|
# nrfutil, picotool) to this file line-by-line as it arrives when this env
|
|
# var is set. The TUI tails it so the operator sees live flash progress
|
|
# instead of 3 minutes of silence during `test_00_bake.py`. Plain CLI users
|
|
# also benefit — the log is a post-run diagnostic even without the TUI.
|
|
# Truncate at session start so each run gets a clean log.
|
|
export MESHTASTIC_MCP_FLASH_LOG="$SCRIPT_DIR/tests/flash.log"
|
|
: >"$MESHTASTIC_MCP_FLASH_LOG"
|
|
|
|
# ---------- Detect connected hardware -------------------------------------
|
|
# In-process call to the same Python API the test fixtures use, so the
|
|
# script never drifts from what pytest sees. Returns a JSON object
|
|
# {role: port, ...}.
|
|
ROLES_JSON="$(
|
|
"$VENV_PY" - <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
sys.path.insert(0, "src")
|
|
from meshtastic_mcp import devices
|
|
|
|
# Role → canonical VID map. Kept in sync with
|
|
# `tests/conftest.py::hub_profile` defaults; if that changes, this must too.
|
|
ROLE_BY_VID = {
|
|
0x239A: "nrf52", # Adafruit / RAK nRF52 native USB (app + DFU)
|
|
0x303A: "esp32s3", # Espressif native USB (ESP32-S3)
|
|
0x10C4: "esp32s3", # CP2102 USB-UART (common on Heltec/LilyGO ESP32 boards)
|
|
}
|
|
|
|
out: dict[str, str] = {}
|
|
for dev in devices.list_devices(include_unknown=True):
|
|
vid_raw = dev.get("vid") or ""
|
|
try:
|
|
if isinstance(vid_raw, str) and vid_raw.startswith("0x"):
|
|
vid = int(vid_raw, 16)
|
|
else:
|
|
vid = int(vid_raw)
|
|
except (TypeError, ValueError):
|
|
continue
|
|
role = ROLE_BY_VID.get(vid)
|
|
# First port wins per role — matches hub_devices fixture semantics.
|
|
if role and role not in out:
|
|
out[role] = dev["port"]
|
|
|
|
json.dump(out, sys.stdout)
|
|
PY
|
|
)"
|
|
|
|
# ---------- Map role → pio env --------------------------------------------
|
|
# Honor MESHTASTIC_MCP_ENV_<ROLE> operator overrides; fall back to the
|
|
# same defaults hardcoded in tests/conftest.py::_DEFAULT_ROLE_ENVS.
|
|
resolve_env() {
|
|
local role="$1"
|
|
local default="$2"
|
|
local upper
|
|
upper="$(echo "$role" | tr '[:lower:]' '[:upper:]')"
|
|
local var="MESHTASTIC_MCP_ENV_${upper}"
|
|
eval "local override=\${$var:-}"
|
|
if [[ -n $override ]]; then
|
|
echo "$override"
|
|
else
|
|
echo "$default"
|
|
fi
|
|
}
|
|
|
|
NRF52_PORT="$(echo "$ROLES_JSON" | "$VENV_PY" -c 'import json,sys; print(json.loads(sys.stdin.read()).get("nrf52", ""))')"
|
|
ESP32S3_PORT="$(echo "$ROLES_JSON" | "$VENV_PY" -c 'import json,sys; print(json.loads(sys.stdin.read()).get("esp32s3", ""))')"
|
|
|
|
DETECTED=""
|
|
if [[ -n $NRF52_PORT ]]; then
|
|
NRF52_ENV="$(resolve_env nrf52 rak4631)"
|
|
export MESHTASTIC_MCP_ENV_NRF52="$NRF52_ENV"
|
|
DETECTED="${DETECTED} nrf52 @ ${NRF52_PORT} -> env=${NRF52_ENV}\n"
|
|
fi
|
|
if [[ -n $ESP32S3_PORT ]]; then
|
|
ESP32S3_ENV="$(resolve_env esp32s3 heltec-v3)"
|
|
export MESHTASTIC_MCP_ENV_ESP32S3="$ESP32S3_ENV"
|
|
DETECTED="${DETECTED} esp32s3 @ ${ESP32S3_PORT} -> env=${ESP32S3_ENV}\n"
|
|
fi
|
|
|
|
# ---------- Pre-flight summary --------------------------------------------
|
|
# Surface what pytest is about to do with respect to the bake phase: the
|
|
# operator should see "will verify + bake if needed" by default, so a
|
|
# 3-minute flash appearing mid-run isn't a surprise. Detection of the
|
|
# explicit overrides is best-effort — we just scan $@ for the known flags.
|
|
_bake_mode="auto (verify + bake if needed)"
|
|
for _arg in "$@"; do
|
|
case "$_arg" in
|
|
--assume-baked) _bake_mode="skip (--assume-baked)" ;;
|
|
--force-bake) _bake_mode="force (--force-bake)" ;;
|
|
*) ;; # any other arg: pass-through; bake mode unchanged
|
|
esac
|
|
done
|
|
|
|
echo "mcp-server test runner"
|
|
echo " firmware root : $FIRMWARE_ROOT"
|
|
echo " seed : $MESHTASTIC_MCP_SEED"
|
|
echo " bake : $_bake_mode"
|
|
if [[ -n $DETECTED ]]; then
|
|
echo " detected hub :"
|
|
printf "%b" "$DETECTED"
|
|
else
|
|
echo " detected hub : (none)"
|
|
fi
|
|
echo
|
|
|
|
# ---------- Invoke pytest -------------------------------------------------
|
|
# If no devices detected, only the unit tier would produce meaningful
|
|
# PASS/FAIL — every hardware test would SKIP with "role not present". We
|
|
# narrow to tests/unit explicitly so the summary reads as "no hardware,
|
|
# unit suite only" instead of "big skip count looks suspicious".
|
|
# Keep terminal output condensed (`-q -r fE`) so skip-heavy runs do not print
|
|
# each skipped test in full; skip counts still appear in pytest's summary.
|
|
if [[ -z $DETECTED && $# -eq 0 ]]; then
|
|
echo "[pre-flight] no supported devices detected; running unit tier only."
|
|
echo
|
|
exec "$VENV_PY" -m pytest tests/unit -q -r fE --report-log=tests/reportlog.jsonl
|
|
fi
|
|
|
|
# Default pytest args when the user passed none. Power users can invoke
|
|
# `./run-tests.sh tests/mesh -v --tb=long` and skip all of these defaults.
|
|
#
|
|
# NOTE: `--assume-baked` is DELIBERATELY omitted here. `tests/test_00_bake.py`
|
|
# has an internal skip-if-already-baked check (`_bake_role`: query device_info,
|
|
# compare region + primary_channel to the session profile, skip on match).
|
|
# So the fast path is ~8-10 s of verification overhead when the devices are
|
|
# already baked — negligible next to the 2-6 min suite runtime. Letting
|
|
# test_00_bake.py run means a fresh device, a re-seeded session, or a post-
|
|
# factory-reset device gets flashed automatically instead of silently
|
|
# skipping half the hardware tests with "not baked with session profile"
|
|
# errors. Power users who know their hardware is current and want to shave
|
|
# those seconds can pass `--assume-baked` explicitly.
|
|
# Defaults also use condensed reporting (`-q -r fE`) to avoid listing every
|
|
# skipped test verbatim while still surfacing failures/errors and summary data.
|
|
if [[ $# -eq 0 ]]; then
|
|
set -- tests/ \
|
|
--html=tests/report.html --self-contained-html \
|
|
--junitxml=tests/junit.xml \
|
|
-q -r fE --tb=short
|
|
fi
|
|
|
|
# UI tier requires opencv-python-headless (and ideally easyocr). If it's
|
|
# not installed, auto-deselect tests/ui so operators without the [ui]
|
|
# extra still get a green run. Printed in yellow; silent when cv2 is
|
|
# present.
|
|
_cv2_ok=0
|
|
if "$VENV_PY" -c "import cv2" >/dev/null 2>&1; then
|
|
_cv2_ok=1
|
|
fi
|
|
_running_ui=0
|
|
for _arg in "$@"; do
|
|
case "$_arg" in
|
|
*tests/ui* | tests/) _running_ui=1 ;;
|
|
*) ;;
|
|
esac
|
|
done
|
|
if [[ $_running_ui -eq 1 && $_cv2_ok -eq 0 ]]; then
|
|
printf '\033[33m[pre-flight] tests/ui tier detected, but opencv-python-headless is not installed — deselecting.\033[0m\n'
|
|
printf ' install with: .venv/bin/pip install -e "mcp-server/.[ui]"\n'
|
|
echo
|
|
set -- "$@" --ignore=tests/ui
|
|
fi
|
|
|
|
# Recovery tier needs `uhubctl` on PATH — it power-cycles devices via USB
|
|
# hub PPPS. The tier's conftest already skips cleanly, so this is just a
|
|
# friendly heads-up before the skip happens. `baked_single`'s auto-
|
|
# recovery hook also benefits from having uhubctl available across the
|
|
# whole suite.
|
|
if ! command -v uhubctl >/dev/null 2>&1; then
|
|
printf "\033[33m[pre-flight] uhubctl not found on PATH — recovery tier will skip, and\n"
|
|
printf " wedged-device auto-recovery is disabled.\033[0m\n"
|
|
printf " install with: brew install uhubctl (macOS) or apt install uhubctl (Debian/Ubuntu).\n"
|
|
echo
|
|
fi
|
|
|
|
# Always emit `tests/reportlog.jsonl` (unless the operator explicitly passed
|
|
# their own `--report-log=...`). Consumers — notably the
|
|
# `meshtastic-mcp-test-tui` TUI — tail the reportlog for live per-test state.
|
|
# Appending here means power-user invocations like `./run-tests.sh tests/mesh`
|
|
# also produce it, not just the all-defaults invocation.
|
|
_has_report_log=0
|
|
for _arg in "$@"; do
|
|
case "$_arg" in
|
|
--report-log | --report-log=*) _has_report_log=1 ;;
|
|
*) ;; # any other arg: no-op; loop continues
|
|
esac
|
|
done
|
|
if [[ $_has_report_log -eq 0 ]]; then
|
|
set -- "$@" --report-log=tests/reportlog.jsonl
|
|
fi
|
|
|
|
exec "$VENV_PY" -m pytest "$@"
|