Files
coredesktop/smoketest/lib/scenario.sh
T
davidandClaude Opus 5.5 6524ff9efd smoketest: say once a minute that a command in the VM is still running
run_in_vm prints how long it has run and the log's latest line, so an
hour-long install.sh shows progress without tail -f.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 08:36:01 -07:00

224 lines
7.2 KiB
Bash

#!/bin/bash
#
# Purpose : Helpers and assertions for smoketest scenarios (see ../README.md).
# Sourced by the driver after it defines vm_exec, vm_put and
# vm_reboot.
SMOKETEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# The CoreDesktop repo whose commits are tested. Overridable for the tests.
SMOKETEST_REPO="${SMOKETEST_REPO:-$(cd "$SMOKETEST_DIR/.." && pwd)}"
# In the VM: the fake upstream, the scratch clone upstream_commit works in, and
# where the fingerprint and health scripts are copied to
SMOKETEST_VM_DIR="${SMOKETEST_VM_DIR:-/srv/smoketest}"
UPSTREAM="$SMOKETEST_VM_DIR/coredesktop.git"
WORK="$SMOKETEST_VM_DIR/work"
# Every URL CoreDesktop has been cloned from, so install.sh at any baseline
# clones the fake upstream instead
COREDESKTOP_URLS=(
https://gitea.federated.computer/Federated_Computer/coredesktop.git
https://gitea.young.computer/david/coredesktop.git
)
# Exit status of a scenario that skipped itself
SKIP_STATUS=3
# Output, exit status, name and log file of the last run_in_vm
OUTPUT=""
STATUS=0
LAST_RUN=""
LAST_LOG=""
RUN_COUNT=0
q() {
printf '%q' "$1"
}
fail() {
echo "FAIL: $*" >&2
exit 1
}
skip() {
echo "SKIP: $*"
exit "$SKIP_STATUS"
}
# run_in_vm NAME SCRIPT: run SCRIPT in the VM, keeping its output in $OUTPUT,
# its exit status in $STATUS, and both in NN-NAME.log. Doesn't fail on its own:
# check with assert_status. The log is written as SCRIPT runs, so a long one
# (install.sh takes an hour or more) can be followed with tail -f, and once a
# minute run_in_vm says it's still running, with the log's latest line.
run_in_vm() {
local ticker
RUN_COUNT=$((RUN_COUNT + 1))
LAST_RUN="$1"
LAST_LOG="$SMOKETEST_RESULTS/$(printf '%02d' "$RUN_COUNT")-$1.log"
echo "== $1 (to follow it: tail -f $LAST_LOG)"
: > "$LAST_LOG"
progress_ticker "$1" "$LAST_LOG" &
ticker=$!
vm_exec "$2" > "$LAST_LOG" 2>&1 && STATUS=0 || STATUS=$?
kill "$ticker" 2> /dev/null
wait "$ticker" 2> /dev/null || true
OUTPUT="$(cat "$LAST_LOG")"
echo "== $1 exited $STATUS (log: $LAST_LOG)"
}
# progress_ticker NAME LOG: every SMOKETEST_TICK seconds (60), say NAME is
# still running, for how long, and LOG's latest line. run_in_vm kills it.
# sleep gets no stdout, so a killed ticker leaves nothing holding the driver's
# pipe to tee open.
progress_ticker() {
local start=$SECONDS tick="${SMOKETEST_TICK:-60}" last
while sleep "$tick" > /dev/null 2>&1 3>&-; do
last="$(tail -c 4096 "$2" | tr '\r' '\n' | grep -v '^[[:space:]]*$' | tail -n 1 | cut -c 1-100)"
echo " still running $1 ($(( (SECONDS - start) / 60 )) min)${last:+: $last}"
done
}
# Called by the driver once the VM is up, before the scenario
smoketest_prepare() {
local urls="" url
for url in "${COREDESKTOP_URLS[@]}"; do
urls+=" $(q "$url")"
done
vm_exec "set -e
[ -d $(q "$UPSTREAM") ] || git init -q --bare --initial-branch=main $(q "$UPSTREAM")
git config --global user.name 'CoreDesktop Smoketest'
git config --global user.email smoketest@localhost
git config --global --unset-all url.$(q "$UPSTREAM").insteadOf || true
for url in $urls; do
git config --global --add url.$(q "$UPSTREAM").insteadOf \"\$url\"
done" || fail "couldn't set up the fake upstream in the VM"
if [ "${IMAGE:-}" = installed ]; then
git -C "$SMOKETEST_REPO" merge-base --is-ancestor "$IMAGE_REF" "$SMOKETEST_REF" \
|| fail "the installed image is at $IMAGE_REF, which isn't in the history of" \
"the commit under test. Rebuild it: coredesktop-dev smoketest images --installed"
upstream_set "$SMOKETEST_REF"
cd_update
assert_status 0
else
upstream_set "$SMOKETEST_REF"
fi
}
# Called by the driver to make the installed image from the base image
smoketest_install_image() {
smoketest_prepare
upstream_set "$1"
cd_install
assert_status 0
}
# upstream_set REF: point the fake upstream's main at REF, a commit in this repo
upstream_set() {
local sha
sha="$(git -C "$SMOKETEST_REPO" rev-parse --verify --quiet "$1^{commit}")" \
|| fail "no such commit: $1"
# --no-verify: a pre-push hook in the working copy (coredesktop-dev's runs
# make check) is for pushes to the real upstream, not to the VM
GIT_SSH_COMMAND="${VM_GIT_SSH:-ssh}" git -C "$SMOKETEST_REPO" push --quiet --force --no-verify \
"${VM_GIT_URL:-}$UPSTREAM" "$sha:refs/heads/main" || fail "couldn't push $1 to the VM"
}
# upstream_commit MESSAGE SCRIPT: release a new commit, made by running SCRIPT
# (in the VM) in a clone of the fake upstream's main
upstream_commit() {
run_in_vm commit "set -e
rm -rf $(q "$WORK")
git clone -q $(q "$UPSTREAM") $(q "$WORK")
cd $(q "$WORK")
$2
git add -A
git commit -qm $(q "$1")
git push -q origin HEAD:main"
assert_status 0
}
# Run install.sh the way the README's wget ... | bash does: main's install.sh,
# with no terminal to ask questions on
cd_install() {
run_in_vm install "git --git-dir=$(q "$UPSTREAM") show main:install.sh > ~/install.sh \
&& cd && TERM=dumb bash ~/install.sh"
}
# cd_update [VAR=value...]: run update, with those variables set
# Scenarios pass the variables, e.g. 44-low-disk.sh
# shellcheck disable=SC2120
cd_update() {
local vars="" var
for var in "$@"; do
vars+=" $(q "$var")"
done
run_in_vm update "TERM=dumb env$vars ~/.local/bin/update"
}
# fingerprint FILE: save the machine's fingerprint to FILE
fingerprint() {
vm_put "$SMOKETEST_DIR/fingerprint" "$SMOKETEST_VM_DIR/fingerprint" \
|| fail "couldn't copy the fingerprint script to the VM"
vm_exec "bash $(q "$SMOKETEST_VM_DIR/fingerprint")" > "$1" || fail "fingerprint failed (see $1)"
}
# Where 10-fresh-install saves the fingerprint of a fresh install of the
# commit under test
reference_fingerprint() {
echo "$SMOKETEST_SHARED/fresh-install.fingerprint"
}
# Run the health checks; fail the scenario if any fails
health() {
vm_put "$SMOKETEST_DIR/health" "$SMOKETEST_VM_DIR/health" \
|| fail "couldn't copy the health script to the VM"
run_in_vm health "bash $(q "$SMOKETEST_VM_DIR/health")"
printf '%s\n' "$OUTPUT"
[ "$STATUS" -eq 0 ] || fail "the health checks failed"
}
assert_status() {
[ "$STATUS" -eq "$1" ] || fail "$LAST_RUN exited $STATUS, expected $1 (see $LAST_LOG)"
}
assert_output() {
[[ "$OUTPUT" == *"$1"* ]] || fail "$LAST_RUN's output doesn't contain \"$1\" (see $LAST_LOG)"
}
refute_output() {
[[ "$OUTPUT" != *"$1"* ]] || fail "$LAST_RUN's output contains \"$1\" (see $LAST_LOG)"
}
# assert_vm DESCRIPTION SCRIPT: SCRIPT must succeed in the VM
assert_vm() {
local out
out="$(vm_exec "$2" 2>&1)" || { printf '%s\n' "$out"; fail "$1"; }
echo "ok: $1"
}
# refute_vm DESCRIPTION SCRIPT: SCRIPT must fail in the VM
refute_vm() {
local out
if out="$(vm_exec "$2" 2>&1)"; then
printf '%s\n' "$out"
fail "$1"
fi
echo "ok: $1"
}
# vm_value SCRIPT: print SCRIPT's output, failing the scenario if SCRIPT fails
vm_value() {
vm_exec "$1" || fail "couldn't run in the VM: $1"
}
# assert_same_fingerprint EXPECTED ACTUAL: the fingerprint files must match
assert_same_fingerprint() {
local diff="$SMOKETEST_RESULTS/fingerprint.diff"
if ! diff -u --label expected --label actual "$1" "$2" > "$diff"; then
cat "$diff"
fail "the machine differs from $1 (diff: $diff)"
fi
echo "ok: same fingerprint as $1"
}