test/e2e_16_push.sh
Ref: Size: 7.3 KiB History
# shellcheck shell=sh
# e2e_16_push.sh — sourced by test/e2e.sh after e2e_lib.sh. Scenarios run in
# the order they stand in; see the lib's header for what this file may assume
# and what it must register.
# `mux d upgrade HOST` — the push flow: one preflight ssh whose line count is
# the verdict, the image streamed over a second ssh into an atomic rename,
# and the freshly installed binary run as `d upgrade` over a third. The
# "remote" is this box behind the ssh shim: a HOME of its own, a bare PATH,
# and the one mux it has installed at ~/.local/bin. Every claim the flow
# makes about that box is asserted against the fake box's own files and
# /proc, never against the pusher's report.
PUSHROOT="${TMPDIR:-/tmp}/mux-e2e-push-$$"
defer_rm "$PUSHROOT"
# The box that has no mux at all, for the refusal that must invent nothing.
PUSHBARE="${TMPDIR:-/tmp}/mux-e2e-pushbare-$$"
defer_rm "$PUSHBARE"
PUSHIM_DIR="${TMPDIR:-/tmp}/mux-e2e-pushshim-$$"
defer_rm "$PUSHIM_DIR"
PUSHSOCK="$PUSHROOT/run/muxd.sock"
defer_sock "$PUSHSOCK"
PUSHINST="$PUSHROOT/.local/bin/mux"
mkdir -p "$PUSHROOT/.local/bin" "$PUSHROOT/run" "$PUSHROOT/state" \
"$PUSHBARE" "$PUSHIM_DIR/fakebin"
# The path the daemon's image is compared against, resolved ONCE and
# through the oracle, which is where "resolved" is spelled per OS: pid_exe
# answers the canonical image, so a $TMPDIR reached through a symlink would
# fail a correct push if only one side were canonical. After the mkdir,
# because real_path needs every component but the last to exist.
PUSHWANT=$(real_path "$PUSHINST")
# The shim IS ssh as far as the pusher can tell (e2e_04 explains that
# contract), and here it also IS the remote box: it swaps in the fake box's
# HOME, xdg dirs and a BARE PATH before handing the word to a shell. Bare,
# because the build's bin directory leads this suite's PATH, and a
# `command -v mux` that found the build's binary would be the fake remote
# answering with the pusher's own install. The word's appended
# `$HOME/.local/bin` is then the only place a mux can be found — which is
# exactly the claim the appended fallback makes. PUSHIM_HOME picks the box
# per scenario; PUSHIM_PATH lets the arch scenario front a lying uname.
ssh_shim_head "$PUSHIM_DIR/ssh"
cat >> "$PUSHIM_DIR/ssh" <<'SHIM'
shift
HOME="${PUSHIM_HOME:?}" \
XDG_RUNTIME_DIR="${PUSHIM_HOME:?}/run" \
XDG_STATE_HOME="${PUSHIM_HOME:?}/state" \
XDG_CONFIG_HOME="${PUSHIM_HOME:?}/config" \
XDG_CACHE_HOME="${PUSHIM_HOME:?}/cache" \
SHELL=/bin/bash \
PATH="${PUSHIM_PATH:-/usr/bin:/bin}" \
exec /bin/sh -c "$*"
SHIM
chmod +x "$PUSHIM_DIR/ssh"
PPATH="$PUSHIM_DIR:$PATH"
# The installed mux the push must replace: the real binary with a marker
# byte tail, so "replaced" and "untouched" are each one cmp/tail away and
# neither can pass by the two files having been identical all along. The
# appended tail changes no ELF header, so the marked copy still runs.
cp "$MUX_ELF" "$PUSHINST"
printf 'MUXOLDIMG' >> "$PUSHINST"
chmod 755 "$PUSHINST"
# --- refusal: a box with no mux gets nothing invented onto it ------------
if PUSHIM_HOME="$PUSHBARE" PATH="$PPATH" \
"$MUX" d upgrade pushbox > "$OUT.push_nomux" 2>&1; then
echo "e2e FAIL: push onto a box with no installed mux exited 0:"
cat "$OUT.push_nomux"
exit 1
fi
grep -q "no mux" "$OUT.push_nomux" || {
echo "e2e FAIL: the no-mux refusal never said so; the capture holds:"
cat "$OUT.push_nomux"
exit 1
}
# Refused means refused: the bare box gained no ~/.local/bin/mux.
[ ! -e "$PUSHBARE/.local/bin/mux" ] || {
echo "e2e FAIL: the no-mux refusal installed a mux anyway"
exit 1
}
ok "d upgrade HOST refuses a box with no installed mux"
# --- refusal: an arch mismatch is caught before any byte moves -----------
printf '#!/bin/sh\necho armv7l\n' > "$PUSHIM_DIR/fakebin/uname"
chmod +x "$PUSHIM_DIR/fakebin/uname"
if PUSHIM_HOME="$PUSHROOT" PUSHIM_PATH="$PUSHIM_DIR/fakebin:/usr/bin:/bin" \
PATH="$PPATH" "$MUX" d upgrade pushbox > "$OUT.push_arch" 2>&1; then
echo "e2e FAIL: push onto a mismatched arch exited 0:"
cat "$OUT.push_arch"
exit 1
fi
# Both spellings in the refusal, so the reader sees the mismatch rather
# than being told one exists.
grep -q "armv7l" "$OUT.push_arch" || {
echo "e2e FAIL: the arch refusal never named the remote's word; capture:"
cat "$OUT.push_arch"
exit 1
}
[ "$(tail -c 9 "$PUSHINST")" = "MUXOLDIMG" ] || {
echo "e2e FAIL: the arch refusal touched the installed binary"
exit 1
}
ok "d upgrade HOST refuses an arch mismatch before pushing"
# --- push with no daemon: install lands, nothing starts, exit 0 ----------
if ! PUSHIM_HOME="$PUSHROOT" PATH="$PPATH" \
"$MUX" d upgrade pushbox > "$OUT.push_idle" 2>&1; then
echo "e2e FAIL: push onto a daemonless box failed; the capture holds:"
cat "$OUT.push_idle"
exit 1
fi
grep -q "no daemon" "$OUT.push_idle" || {
echo "e2e FAIL: the daemonless push never said the daemon step was skipped:"
cat "$OUT.push_idle"
exit 1
}
# The install is byte-for-byte this build's image: marker gone, cmp clean.
cmp -s "$PUSHINST" "$MUX_ELF" || {
echo "e2e FAIL: after a daemonless push the installed mux is not this image"
exit 1
}
# A push is not a start: nothing may be listening on the fake box's socket.
[ ! -S "$PUSHSOCK" ] || {
echo "e2e FAIL: the daemonless push left a socket — something started a daemon"
exit 1
}
ok "d upgrade HOST with no remote daemon installs and starts nothing"
# --- push with a live daemon: same pid, new image, still serving ---------
# Marked again so this scenario, like the last, proves a replacement and
# not a coincidence of identical files.
printf 'MUXOLDIMG' >> "$PUSHINST"
# The remote daemon runs FROM the installed path with the fake box's own
# environment, exactly as a box that booted one would hold it. Foreground
# `d start` IS the daemon, so $! is the daemon's pid — the one the exec
# must keep.
env HOME="$PUSHROOT" XDG_RUNTIME_DIR="$PUSHROOT/run" \
XDG_STATE_HOME="$PUSHROOT/state" XDG_CONFIG_HOME="$PUSHROOT/config" \
XDG_CACHE_HOME="$PUSHROOT/cache" SHELL=/bin/bash \
"$PUSHINST" d start > "$OUT.pushd.log" 2>&1 &
PUSHDPID=$!
defer_kill "$PUSHDPID"
wait_sock "$PUSHSOCK" "$OUT.pushd.log" "push-target daemon"
if ! PUSHIM_HOME="$PUSHROOT" PATH="$PPATH" \
"$MUX" d upgrade pushbox --allow-same-version > "$OUT.push_live" 2>&1; then
echo "e2e FAIL: push onto a live daemon failed; the capture holds:"
cat "$OUT.push_live"
cat "$OUT.pushd.log"
exit 1
fi
# The remote trigger's own verdict, relayed verbatim through the ssh.
grep -q "upgraded to" "$OUT.push_live" || {
echo "e2e FAIL: the live push never reported the daemon upgrade:"
cat "$OUT.push_live"
exit 1
}
# The OS's account, not the daemon's: same pid, and the image the OS says
# it is running is the installed path with no ' (deleted)' tail — the exec
# adopted the NEW inode at that path, rather than the old one riding out a
# rename. Both sides are fully resolved (see $PUSHWANT above).
kill -0 "$PUSHDPID" 2>/dev/null || {
echo "e2e FAIL: the daemon pid died across the push upgrade"
exit 1
}
PUSHEXE=$(pid_exe "$PUSHDPID")
[ "$PUSHEXE" = "$PUSHWANT" ] || {
echo "e2e FAIL: the pushed daemon's image is $PUSHEXE, not $PUSHWANT"
exit 1
}
cmp -s "$PUSHINST" "$MUX_ELF" || {
echo "e2e FAIL: after the live push the installed mux is not this image"
exit 1
}
ok "d upgrade HOST pushes the image and the remote daemon execs it in place"