ci(e2e): on-demand Meshtasticator e2e job for the docker-enabled runner
Adds .gitea/workflows/meshtasticator-e2e.yaml (workflow_dispatch until validated green, pull_request trigger commented out): builds the pinned Hermes contract env like release.yaml, installs the plugin with deps, checks out Meshtasticator at a pinned commit, installs the simulator's own (conflicting-version) requirements in an isolated venv, then runs scripts/meshtasticator_e2e/run_e2e.sh --mode docker. Supporting changes: - run_e2e.sh: SIM_VENV support; automatic localhost->daemon TCP forwards (scripts/meshtasticator_e2e/tcp_forward.py) when DOCKER_HOST is a tcp:// daemon, because Meshtasticator hardcodes localhost for its node control connections; node-boot settle wait; fixed docker cleanup (container 'Meshtastic', volume 'Meshtasticator'). - e2e tests: connect via a module-long event loop in a background thread (adapter requires a running loop for watchdog + inbound dispatch, so per-call asyncio.run was wrong) with retry while the node TCP API boots. - README: sim-venv guidance, remote-daemon forwarding, CI section. Docker access follows the deployment examples in eric/actions-docker (docker is available to runner jobs).
This commit is contained in:
@@ -15,6 +15,12 @@
|
||||
# select 'native') and pass --program <dir containing binary 'program'>.
|
||||
# * uv + python 3.13 for the contract venv (plugin/.venv-contract).
|
||||
#
|
||||
# Docker on a remote daemon: if DOCKER_HOST points at a tcp:// daemon, the
|
||||
# simulator's own control connections assume nodes listen on localhost, so
|
||||
# this script starts lightweight localhost->daemon TCP forwarders for the node
|
||||
# ports first (scripts/meshtasticator_e2e/tcp_forward.py). Nothing is
|
||||
# forwarded when the daemon is local (unset/unix DOCKER_HOST).
|
||||
#
|
||||
# Usage
|
||||
# MESHTASTICATOR_DIR=/path/to/meshtasticator scripts/meshtasticator_e2e/run_e2e.sh
|
||||
# # docker mode (default): ... run_e2e.sh --mode docker
|
||||
@@ -24,9 +30,11 @@
|
||||
# Environment
|
||||
# MESHTASTICATOR_DIR path to a clone of github.com/meshtastic/meshtasticator
|
||||
# E2E_VENV python env with hermes-agent + plugin deps installed
|
||||
# (default: plugin/.venv-contract — set it up with
|
||||
# `uv pip install -e plugin/.hermes-src` equivalents via
|
||||
# scripts/contract-test.sh, then `uv pip install -e plugin`)
|
||||
# (default: plugin/.venv-contract — see scripts/contract-test.sh,
|
||||
# then `uv pip install --python plugin/.venv-contract/bin/python -e plugin`)
|
||||
# SIM_VENV optional python env holding the simulator's deps
|
||||
# (meshtasticator/requirements.txt + `pip install docker`);
|
||||
# defaults to plain `python3`.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -37,6 +45,11 @@ PROGRAM=""
|
||||
CHANNEL_INDEX=0
|
||||
HOST=127.0.0.1
|
||||
VENV="${E2E_VENV:-$REPO_ROOT/plugin/.venv-contract}"
|
||||
if [ -n "${SIM_VENV:-}" ]; then
|
||||
SIM_PYTHON="$SIM_VENV/bin/python"
|
||||
else
|
||||
SIM_PYTHON="${SIM_PYTHON:-python3}"
|
||||
fi
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
@@ -55,6 +68,30 @@ command -v uv >/dev/null 2>&1 || { echo "uv required" >&2; exit 1; }
|
||||
|
||||
export MPLBACKEND=Agg # headless matplotlib (no display needed)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remote-daemon port forwarding: Meshtasticator talks to nodes on localhost.
|
||||
# ---------------------------------------------------------------------------
|
||||
DOCKER_TARGET=""
|
||||
if [[ "${DOCKER_HOST:-}" == tcp://* ]]; then
|
||||
DOCKER_TARGET="${DOCKER_HOST#tcp://}"
|
||||
DOCKER_TARGET="${DOCKER_TARGET%%:*}"
|
||||
if [ "$DOCKER_TARGET" = "127.0.0.1" ] || [ "$DOCKER_TARGET" = "localhost" ]; then
|
||||
DOCKER_TARGET=""
|
||||
fi
|
||||
fi
|
||||
FWD_PIDS=()
|
||||
PORT_BASE=4404 # matches meshtasticator lib/interactive.py TCP_PORT_OFFSET
|
||||
|
||||
start_forwards() {
|
||||
for ((i = 0; i < NODES; i++)); do
|
||||
port=$((PORT_BASE + i))
|
||||
"$VENV/bin/python" "$REPO_ROOT/scripts/meshtasticator_e2e/tcp_forward.py" \
|
||||
--listen "127.0.0.1:$port" --target "$DOCKER_TARGET:$port" >/dev/null 2>&1 &
|
||||
FWD_PIDS+=("$!")
|
||||
done
|
||||
sleep 1
|
||||
}
|
||||
|
||||
SIM_LOG="$REPO_ROOT/meshtasticator-sim.log"
|
||||
SIM_ARGS=("$NODES")
|
||||
if [ "$MODE" = native ]; then
|
||||
@@ -64,28 +101,36 @@ else
|
||||
SIM_ARGS+=(-d)
|
||||
fi
|
||||
|
||||
if [ -n "$DOCKER_TARGET" ]; then
|
||||
echo "== remote docker daemon detected ($DOCKER_TARGET): starting localhost port forwards =="
|
||||
start_forwards
|
||||
fi
|
||||
|
||||
echo "== booting Meshtasticator ($MODE) with $NODES node(s) from $SIM_DIR =="
|
||||
(
|
||||
cd "$SIM_DIR"
|
||||
python3 interactiveSim.py "${SIM_ARGS[@]}" >"$SIM_LOG" 2>&1
|
||||
"$SIM_PYTHON" interactiveSim.py "${SIM_ARGS[@]}" >"$SIM_LOG" 2>&1
|
||||
) &
|
||||
SIM_PID=$!
|
||||
|
||||
PORTS=""
|
||||
cleanup() {
|
||||
echo "== tearing down simulator (pid $SIM_PID) =="
|
||||
kill "$SIM_PID" 2>/dev/null || true
|
||||
wait "$SIM_PID" 2>/dev/null || true
|
||||
for pid in "${FWD_PIDS[@]:-}"; do
|
||||
kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
if [ "$MODE" = docker ]; then
|
||||
# The sim container may outlive the process on abrupt kills.
|
||||
docker ps -q --filter "ancestor=meshtastic-meshtasticd" 2>/dev/null | xargs -r docker rm -f >/dev/null 2>&1 || true
|
||||
# The sim names its container/volume (auto_remove only fires on stop).
|
||||
docker rm -f Meshtastic >/dev/null 2>&1 || true
|
||||
docker volume rm Meshtasticator >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "== waiting for $NODES node TCP API port(s) to accept connections =="
|
||||
READY=""
|
||||
for _ in $(seq 1 180); do # up to ~5 min: docker builds firmware on first run
|
||||
for _ in $(seq 1 180); do # up to ~5 min: docker pulls meshtastic/meshtasticd on first run
|
||||
READY="$("$VENV/bin/python" - "$HOST" "$NODES" <<'PY' || true
|
||||
import socket, sys
|
||||
host, count = sys.argv[1], int(sys.argv[2])
|
||||
@@ -114,6 +159,11 @@ if [ -z "$READY" ]; then
|
||||
fi
|
||||
echo " node ports: $READY"
|
||||
|
||||
# Ports accept before the node firmware finished booting inside the container;
|
||||
# give meshtasticd a moment to open its TCP API for real.
|
||||
echo "== settling (node firmware boot) =="
|
||||
sleep 15
|
||||
|
||||
echo "== running radio-level e2e tests =="
|
||||
(
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
Reference in New Issue
Block a user