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>
224 lines
7.2 KiB
Bash
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"
|
|
}
|