fix(e2e): headless Graph construction + keep simulator cmdloop alive
Run 10537 reached node boot but the simulator crashed again at Graph construction: lib/gui.py move_figure() unconditionally touches the Tk window manager (canvas.manager.window), which does not exist under Agg. Patch it to a no-op guard alongside the TkAgg->Agg backend swap. Also feed the simulator's interactive cmdloop from a FIFO held open until teardown — CI stdin is EOF, so the sim would otherwise exit immediately after booting the nodes.
This commit is contained in:
@@ -85,11 +85,16 @@ simulator. Nothing is forwarded when the daemon is local (unset/unix
|
||||
on the forwarders themselves.
|
||||
|
||||
Other headless adaptations the script makes automatically:
|
||||
* patches `lib/gui.py` `matplotlib.use("TkAgg")` → `"Agg"` in the pinned
|
||||
Meshtasticator checkout — the TkAgg call crashes headless runs even under
|
||||
`MPLBACKEND=Agg` (only the interactive GUI mode is lost),
|
||||
* patches `lib/gui.py` in the pinned Meshtasticator checkout: forces the Agg
|
||||
backend (the code calls `matplotlib.use("TkAgg")` at import) and makes
|
||||
`move_figure()` a no-op when no Tk window manager exists (its unconditional
|
||||
`canvas.manager.window` access crashes under Agg at Graph construction).
|
||||
Only the interactive GUI modes, never used by this harness, are lost,
|
||||
* warms the `meshtastic/meshtasticd` image (`docker pull`) before booting the
|
||||
simulator so the pull is not on the simulator's node-boot critical path.
|
||||
simulator so the pull is not on the simulator's node-boot critical path,
|
||||
* feeds the simulator's interactive `cmdloop` from a FIFO held open until
|
||||
teardown — an EOF stdin (e.g. CI) would otherwise make it exit right after
|
||||
booting the nodes.
|
||||
|
||||
Without `MESHTASTICATOR_E2E=1` the e2e module skips, so ordinary unit/CI runs
|
||||
stay fast and gateway-free.
|
||||
|
||||
@@ -109,14 +109,33 @@ else
|
||||
PROBE_HOST="$HOST"
|
||||
fi
|
||||
|
||||
# The simulator forces TkAgg at import (lib/gui.py: matplotlib.use("TkAgg")),
|
||||
# which crashes headless runs ("Tkinter is needed") even under MPLBACKEND=Agg —
|
||||
# an explicit use() overrides the env var. Neutralize it in the pinned
|
||||
# checkout; only the interactive GUI mode (never used by this harness) is lost.
|
||||
if grep -q 'matplotlib.use("TkAgg")' "$SIM_DIR/lib/gui.py"; then
|
||||
echo "== patching meshtasticator lib/gui.py: TkAgg -> Agg (headless) =="
|
||||
sed -i.bak 's/matplotlib.use("TkAgg")/matplotlib.use("Agg")/' "$SIM_DIR/lib/gui.py"
|
||||
fi
|
||||
# The simulator assumes an interactive Tk desktop: lib/gui.py forces
|
||||
# matplotlib.use("TkAgg") at import and move_figure() touches the Tk window
|
||||
# manager at Graph construction — both crash headless runs even under
|
||||
# MPLBACKEND=Agg. Neutralize them in the pinned checkout (only the interactive
|
||||
# GUI modes, never used by this harness, are lost).
|
||||
echo "== patching meshtasticator lib/gui.py for headless runs =="
|
||||
"$VENV/bin/python" - "$SIM_DIR" <<'PY'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
gui = Path(sys.argv[1]) / "lib/gui.py"
|
||||
src = gui.read_text()
|
||||
|
||||
src = src.replace('matplotlib.use("TkAgg")', 'matplotlib.use("Agg")')
|
||||
|
||||
old = 'def move_figure(fig, x, y):\n fig.canvas.manager.window.wm_geometry("+%d+%d" % (x, y))'
|
||||
new = ('def move_figure(fig, x, y):\n'
|
||||
' # Headless (Agg) backends have no Tk window manager.\n'
|
||||
' try:\n'
|
||||
' fig.canvas.manager.window.wm_geometry("+%d+%d" % (x, y))\n'
|
||||
' except AttributeError:\n'
|
||||
' pass')
|
||||
assert old in src, "move_figure source no longer matches the pinned meshtasticator"
|
||||
src = src.replace(old, new)
|
||||
gui.write_text(src)
|
||||
print("patched lib/gui.py")
|
||||
PY
|
||||
|
||||
if [ "$MODE" = docker ]; then
|
||||
# Warm the node image on the (possibly remote) daemon first: the simulator
|
||||
@@ -127,14 +146,22 @@ if [ "$MODE" = docker ]; then
|
||||
fi
|
||||
|
||||
echo "== booting Meshtasticator ($MODE) with $NODES node(s) from $SIM_DIR =="
|
||||
# The simulator ends in an interactive cmdloop reading stdin; an EOF there
|
||||
# (e.g. CI) would make it exit right after booting the nodes. Feed it from a
|
||||
# FIFO whose write end this shell keeps open until cleanup.
|
||||
SIM_FIFO="$(mktemp -u /tmp/meshtasticator-XXXXXX.fifo)"
|
||||
mkfifo "$SIM_FIFO"
|
||||
(
|
||||
cd "$SIM_DIR"
|
||||
"$SIM_PYTHON" interactiveSim.py "${SIM_ARGS[@]}" >"$SIM_LOG" 2>&1
|
||||
"$SIM_PYTHON" interactiveSim.py "${SIM_ARGS[@]}" <"$SIM_FIFO" >"$SIM_LOG" 2>&1
|
||||
) &
|
||||
SIM_PID=$!
|
||||
exec 9>"$SIM_FIFO" # hold the write end open -> sim stdin never hits EOF
|
||||
rm -f "$SIM_FIFO"
|
||||
|
||||
cleanup() {
|
||||
echo "== tearing down simulator (pid $SIM_PID) =="
|
||||
exec 9>&- 2>/dev/null || true # release the FIFO write end (sim stdin EOF)
|
||||
kill "$SIM_PID" 2>/dev/null || true
|
||||
wait "$SIM_PID" 2>/dev/null || true
|
||||
for pid in "${FWD_PIDS[@]:-}"; do
|
||||
|
||||
Reference in New Issue
Block a user