CI / Shell-Lint (bash -n, source-check, Validierungs-Tests) (pull_request) Successful in 3s
205 lines
10 KiB
Bash
205 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
# Tests for install/runner-cleanup.sh, the runner's daily cleaner. Runs without
|
|
# Docker and without root: docker and stat are stand-ins on PATH that answer
|
|
# from tables, and act-runner's private /tmp is a directory under a temp root.
|
|
# stat is stood in because a ctime cannot be set back, and the cleaner goes by
|
|
# ctime; find and rm are real, under the temp root only. The cleaner's own
|
|
# list file (mktemp) goes to a directory of the test too, not to /tmp.
|
|
# Aufruf: bash tests/test_runner_cleanup.sh
|
|
set -u
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
CLEANUP="$REPO_ROOT/install/runner-cleanup.sh"
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
ok() { PASS=$((PASS + 1)); echo "ok - $1"; }
|
|
nok() { FAIL=$((FAIL + 1)); echo "NOT OK - $1"; }
|
|
|
|
# Setup must not half-work: a stand-in that is not executable would let the
|
|
# real docker on PATH answer.
|
|
die() { echo "setup failed: $1" >&2; exit 1; }
|
|
T="$(mktemp -d)" || die mktemp
|
|
trap 'rm -rf "$T"' EXIT
|
|
mkdir -p "$T/conf" "$T/bin" "$T/own" || die mkdir
|
|
|
|
# patchmgr's file, as runner-install.sh lays it down.
|
|
cat >"$T/conf/patchmgr.conf" <<'CONF_EOF' || die conf
|
|
# l.kirchner/patchmgr (D64)
|
|
PROJECT=patchmgr
|
|
CONTAINER_NAME_PATTERN='^patchmgr-testdb-'
|
|
TMP_PATTERN='patchmgr-counter-check-*'
|
|
CONF_EOF
|
|
|
|
# docker: by the label org.luki-net.project=patchmgr it knows four containers
|
|
# -- "old" (created and started two days ago), "new" (one hour), "stray"
|
|
# (two days, labelled but named like something else) and "restarted"
|
|
# (created two days ago, started again an hour ago). An unlabelled container
|
|
# never comes back from a filter by label. rm of an id listed in
|
|
# $T/rm-fails fails.
|
|
OLD="$(date -u -d '2 days ago' +%FT%TZ)"
|
|
NEW="$(date -u -d '1 hour ago' +%FT%TZ)"
|
|
cat >"$T/bin/docker" <<DOCKER_EOF || die docker
|
|
#!/usr/bin/env bash
|
|
echo "\$*" >> "$T/calls"
|
|
case "\$1" in
|
|
ps) [ "\$4" = "label=org.luki-net.project=patchmgr" ] && printf 'old\nnew\nstray\nrestarted\n' ;;
|
|
inspect)
|
|
case "\$4" in
|
|
old) echo "/patchmgr-testdb-1-x $OLD $OLD" ;;
|
|
new) echo "/patchmgr-testdb-2-x $NEW $NEW" ;;
|
|
stray) echo "/other-db $OLD $OLD" ;;
|
|
restarted) echo "/patchmgr-testdb-3-x $OLD $NEW" ;;
|
|
esac ;;
|
|
rm) grep -qx "\$4" "$T/rm-fails" 2>/dev/null && exit 1 ;;
|
|
esac
|
|
exit 0
|
|
DOCKER_EOF
|
|
# stat -c %Z: the ctime from $T/ctimes by base name, "now" for anything else.
|
|
cat >"$T/bin/stat" <<STAT_EOF || die stat
|
|
#!/usr/bin/env bash
|
|
[ "\$1 \$2" = "-c %Z" ] || exit 2
|
|
b=\$(basename "\$4")
|
|
t=\$(awk -v b="\$b" '\$1 == b { print \$2 }' "$T/ctimes")
|
|
echo "\${t:-\$(date +%s)}"
|
|
STAT_EOF
|
|
chmod +x "$T/bin/docker" "$T/bin/stat" || die chmod
|
|
[ -x "$T/bin/docker" ] && [ -x "$T/bin/stat" ] || die "stand-ins not executable"
|
|
TWO_DAYS_AGO=$(($(date +%s) - 2 * 86400))
|
|
|
|
# act-runner's PrivateTmp: patchmgr's old and new copy, a copy with an old
|
|
# mtime but a fresh ctime (cp -a of an old tree), and old entries that are
|
|
# not patchmgr's.
|
|
fresh_tmp() {
|
|
rm -rf "$T/root"
|
|
R="$T/root/systemd-private-x-act-runner.service-y/tmp"
|
|
mkdir -p "$R/patchmgr-counter-check-old" "$R/patchmgr-counter-check-new" "$R/patchmgr-counter-check-copied" \
|
|
"$R/counter-check-old" "$R/go-build-old" "$R/patchmgrx-old" || die tmp
|
|
touch -d '2 days ago' "$R/patchmgr-counter-check-copied" || die touch
|
|
printf '%s %s\n' patchmgr-counter-check-old "$TWO_DAYS_AGO" counter-check-old "$TWO_DAYS_AGO" \
|
|
go-build-old "$TWO_DAYS_AGO" patchmgrx-old "$TWO_DAYS_AGO" >"$T/ctimes"
|
|
: >"$T/calls"
|
|
rm -f "$T/rm-fails"
|
|
}
|
|
|
|
cleanup() { PATH="$T/bin:$PATH" CONF_DIR="$T/conf" TMP_ROOT="$T/root" TMPDIR="$T/own" bash "$CLEANUP"; }
|
|
removed_any() { grep -q '^rm' "$T/calls"; }
|
|
untouched() { [ -d "$R/counter-check-old" ] && [ -d "$R/go-build-old" ] && [ -d "$R/patchmgrx-old" ] && [ -d "$R/patchmgr-counter-check-old" ]; }
|
|
|
|
# ── DRY_RUN: empty or 0 removes, anything else only lists ─────────────────────
|
|
for v in 1 true yes; do
|
|
fresh_tmp
|
|
out="$(DRY_RUN=$v cleanup 2>&1)"
|
|
if removed_any || [ ! -d "$R/patchmgr-counter-check-old" ]; then
|
|
nok "DRY_RUN=$v removed something"
|
|
else
|
|
ok "DRY_RUN=$v removes nothing"
|
|
fi
|
|
done
|
|
if grep -q 'container patchmgr-testdb-1-x' <<<"$out"; then ok "a dry run lists what it would remove"; else nok "a dry run did not list: $out"; fi
|
|
if grep -q 'removed for patchmgr: none (DRY_RUN' <<<"$out"; then ok "a dry run says it removed nothing"; else nok "a dry run's last line: $out"; fi
|
|
for v in 0 ''; do
|
|
fresh_tmp
|
|
DRY_RUN=$v cleanup >/dev/null 2>&1
|
|
if removed_any; then ok "DRY_RUN='$v' removes"; else nok "DRY_RUN='$v' removed nothing"; fi
|
|
done
|
|
|
|
# ── The real run: only patchmgr's, by label and name, older than 4 h ─────────
|
|
fresh_tmp
|
|
if cleanup >/dev/null 2>&1; then ok "a clean run exits 0"; else nok "a clean run exited non-zero"; fi
|
|
if grep -qx 'rm -f -v old' "$T/calls"; then ok "the old container goes, with its volumes (rm -f -v)"; else nok "the old container was not removed with -v: $(grep '^rm' "$T/calls")"; fi
|
|
if grep -q '^rm .*new' "$T/calls"; then nok "a container younger than 4 h was removed"; else ok "a container younger than 4 h stays"; fi
|
|
if grep -q '^rm .*restarted' "$T/calls"; then nok "a container started again an hour ago was removed"; else ok "a container counts from its last start, not its creation"; fi
|
|
if grep -q '^rm .*stray' "$T/calls"; then nok "a labelled container with another name was removed"; else ok "the label alone is not enough: the name must match too"; fi
|
|
if grep -q -- '--filter label=org.luki-net.project=patchmgr' "$T/calls"; then ok "containers are found by the project label"; else nok "docker was not asked by label: $(cat "$T/calls")"; fi
|
|
if grep -q -- '--filter name=' "$T/calls"; then nok "docker was asked by name, not by label"; else ok "no filter by name alone"; fi
|
|
if [ ! -e "$R/patchmgr-counter-check-old" ]; then ok "patchmgr's old copy goes"; else nok "patchmgr's old copy is still there"; fi
|
|
if [ -d "$R/patchmgr-counter-check-new" ]; then ok "a copy younger than 4 h stays"; else nok "the new copy was removed"; fi
|
|
if [ -d "$R/patchmgr-counter-check-copied" ]; then ok "a fresh copy with an old mtime stays: the age is the ctime"; else nok "a copy with an old mtime but a fresh ctime was removed"; fi
|
|
if [ -d "$R/counter-check-old" ] && [ -d "$R/go-build-old" ] && [ -d "$R/patchmgrx-old" ]; then ok "entries without patchmgr's prefix stay"; else nok "an entry without the prefix was removed"; fi
|
|
if grep -q -E '^(system|volume|image|container) prune' "$T/calls"; then nok "docker was asked to prune"; else ok "no docker prune"; fi
|
|
|
|
# ── A failure says so in the exit code, and the other projects still run ─────
|
|
fresh_tmp
|
|
echo old >"$T/rm-fails"
|
|
printf "PROJECT=other\nTMP_PATTERN='other-*'\n" >"$T/conf/zz-other.conf"
|
|
mkdir -p "$R/other-old"
|
|
echo "other-old $TWO_DAYS_AGO" >>"$T/ctimes"
|
|
cleanup >"$T/out" 2>&1
|
|
rc=$?
|
|
rm -f "$T/conf/zz-other.conf"
|
|
if [ "$rc" -eq 1 ] && grep -q 'docker rm patchmgr-testdb-1-x failed' "$T/out"; then ok "a failed docker rm makes the run exit 1"; else nok "a failed docker rm: exit $rc: $(cat "$T/out")"; fi
|
|
if [ ! -e "$R/other-old" ] && [ ! -e "$R/patchmgr-counter-check-old" ]; then ok "the rest still runs after one failure"; else nok "a failure stopped the rest"; fi
|
|
|
|
# ── A file cannot claim another project's things, or everything ──────────────
|
|
refused() { # $1: file content, $2: name for the test
|
|
fresh_tmp
|
|
mv "$T/conf/patchmgr.conf" "$T/patchmgr.conf.keep"
|
|
printf '%s\n' "$1" >"$T/conf/bad.conf"
|
|
out="$(cleanup 2>&1)"
|
|
rc=$?
|
|
rm -f "$T/conf/bad.conf"
|
|
mv "$T/patchmgr.conf.keep" "$T/conf/patchmgr.conf"
|
|
if removed_any || ! untouched; then
|
|
nok "$2 removed something"
|
|
elif [ "$rc" -ne 0 ] && grep -q 'skipped' <<<"$out"; then
|
|
ok "$2 is refused"
|
|
else
|
|
nok "$2 was not refused (exit $rc): $out"
|
|
fi
|
|
}
|
|
refused "PROJECT=patchmgr
|
|
CONTAINER_NAME_PATTERN='^.*'" "CONTAINER_NAME_PATTERN='^.*'"
|
|
refused "PROJECT=patchmgr
|
|
CONTAINER_NAME_PATTERN='^other-'" "CONTAINER_NAME_PATTERN='^other-'"
|
|
refused "PROJECT=patchmgr
|
|
CONTAINER_NAME_PATTERN='^patchmgr-testdb-|.*'" "an alternation after the prefix"
|
|
refused "PROJECT=patchmgr
|
|
CONTAINER_NAME_PATTERN='^patchmgr-{0}.*'" "a quantifier that makes the hyphen optional"
|
|
refused "PROJECT=patchmgr
|
|
CONTAINER_NAME_PATTERN='^patchmgr-testdb-['" "a pattern that is not a regex"
|
|
refused "PROJECT=patchmgr
|
|
TMP_PATTERN='patchmgr-\$(id)'" "a quoted \$(...) in TMP_PATTERN"
|
|
refused "PROJECT=patchmgr
|
|
TMP_PATTERN='patchmgr-[a-z]*'" "brackets in TMP_PATTERN"
|
|
refused "PROJECT=patchmgr
|
|
TMP_PATTERN='?*'" "TMP_PATTERN='?*'"
|
|
refused "PROJECT=patchmgr
|
|
TMP_PATTERN='*'" "TMP_PATTERN='*'"
|
|
refused "PROJECT=patchmgr
|
|
TMP_PATTERN='other-*'" "TMP_PATTERN='other-*'"
|
|
refused "PROJECT=patchmgr
|
|
TMP_PATTERN='patchmgr-x/y'" "TMP_PATTERN with a directory"
|
|
refused "PROJECT='Patch Mgr'
|
|
TMP_PATTERN='*'" "a PROJECT that is not a name"
|
|
refused "PROJECT=patchmgr-old
|
|
TMP_PATTERN='patchmgr-old-*'" "a PROJECT with a hyphen (its prefix would overlap patchmgr's)"
|
|
refused "PROJECT=patchmgr
|
|
MAX_AGE_HOURS=1
|
|
TMP_PATTERN='patchmgr-*'" "a file that sets MAX_AGE_HOURS"
|
|
refused "PROJECT=patchmgr\$(touch $T/ran)
|
|
TMP_PATTERN='patchmgr-*'" "a file with a command in it"
|
|
if [ -e "$T/ran" ]; then nok "a command in a file was run"; else ok "a file is read, not run"; fi
|
|
|
|
# ── A project claimed by two files is claimed by neither ─────────────────────
|
|
fresh_tmp
|
|
printf "PROJECT=patchmgr\nTMP_PATTERN='patchmgr-*'\n" >"$T/conf/copy.conf"
|
|
out="$(cleanup 2>&1)"
|
|
rc=$?
|
|
rm -f "$T/conf/copy.conf"
|
|
if [ "$rc" -eq 1 ] && ! removed_any && untouched && [ "$(grep -c 'claimed by 2 files' <<<"$out")" -eq 2 ]; then ok "a project in two files is refused in both"; else nok "a project in two files (exit $rc): $out"; fi
|
|
|
|
# ── The age cannot go below the job timeout of 3 h ───────────────────────────
|
|
for age in 0 3 abc 04 1000 4611686018427387904; do
|
|
if MAX_AGE_HOURS="$age" cleanup >/dev/null 2>&1; then nok "MAX_AGE_HOURS=$age was accepted"; else ok "MAX_AGE_HOURS=$age is refused"; fi
|
|
done
|
|
fresh_tmp
|
|
if MAX_AGE_HOURS=4 cleanup >/dev/null 2>&1; then ok "MAX_AGE_HOURS=4 is accepted"; else nok "MAX_AGE_HOURS=4 was refused"; fi
|
|
|
|
# ── The cleaner's list files stay in the test and are removed ────────────────
|
|
if [ -z "$(ls -A "$T/own")" ]; then ok "no list file is left behind"; else nok "list files left behind: $(ls -A "$T/own")"; fi
|
|
|
|
echo
|
|
echo "pass: $PASS, fail: $FAIL"
|
|
[ "$FAIL" -eq 0 ]
|