#!/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.federated.computer/david/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" }