Wren Linux

wren-linux / tools / test-help.sh

#!/bin/bash
# Checks --help (lib.sh, script_help in lib/core.sh): every script that loads lib.sh answers
# --help (or -h) with what its comment at the top says, changes nothing, and explains each
# option it handles itself somewhere other than a bare usage line. Every extra's --remove and
# --manual are explained for it. Each script runs with no_new_privs, so sudo can't run in
# it whatever it does. Run by test.sh, or:  bash tools/test-help.sh
# shellcheck disable=SC2016  # the inner shells expand their own variables
HERE=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
T=$(mktemp -d) || { echo "  FAIL  couldn't make a temp folder (is /tmp full?)"; exit 1; }
trap 'rm -rf "$T"' EXIT
fails=0
check() { # DESCRIPTION CONDITION-COMMAND...
    local what=$1
    shift
    if "$@" >/dev/null 2>&1; then printf '  ok    %s\n' "$what"; else printf '  FAIL  %s\n' "$what"; fails=$((fails + 1)); fi
}
not() { ! "$@"; }

mkdir -p "$T/home"
out=$(cd "$HERE" && HOME="$T/home" python3 -I - <<'PY'
import re, subprocess
files = subprocess.run(["git", "ls-files", "*.sh"], capture_output=True, text=True).stdout.split()
# options a script handles itself: case labels at the start of a line, and tests of $1
own = re.compile(r'^\s*((?:--[a-z][a-z0-9-]+\s*\|\s*)*--[a-z][a-z0-9-]+)\s*\)|\[ "\$\{?1(?::-)?\}?" = (--[a-z][a-z0-9-]+) \]', re.M)
# options of a function inside a script, not of the script (each with why)
NOT_THE_SCRIPTS = {
    "extras/whonix.sh": {"--edit", "--exec"},   # image_put's, which copies files into a VM image
}
def usage_line(l):
    s = l.strip()
    return s.startswith("Usage:") or re.fullmatch(r'(bash \S+ )?(\[[^\]]*\]\s*)+', s) is not None
n = 0
for f in files:
    if f.startswith(("tools/test-", "lib/")) or f == "lib.sh":
        continue
    src = open(f, errors="replace").read()
    if not re.search(r'^\. "\$\(dirname[^\n]*lib\.sh"', src, re.M):
        continue
    n += 1
    for flag in ("--help", "-h"):
        # no_new_privs: sudo refuses to run, so a script that ignored --help couldn't change
        # anything as root (with the old lib.sh, some installed themselves: tried, by mistake)
        r = subprocess.run(["setpriv", "--no-new-privs", "bash", f, flag], capture_output=True, text=True, timeout=20)
        if r.returncode != 0 or len(r.stdout.strip().splitlines()) < 2:
            print("NOHELP %s %s exit %d" % (f, flag, r.returncode))
    text = "\n".join(l for l in r.stdout.splitlines() if not usage_line(l))
    opts = set()
    for m in own.finditer(src):
        if m.group(1):
            opts.update(re.findall(r'--[a-z][a-z0-9-]+', m.group(1)))
        if m.group(2):
            opts.add(m.group(2))
    opts -= {"--help"} | NOT_THE_SCRIPTS.get(f, set())
    for o in sorted(opts):
        if not re.search(re.escape(o) + r'(?![\w-])', text):
            print("UNEXPLAINED %s %s" % (f, o))
print("SCRIPTS %d" % n)
PY
)
n=$(sed -n 's/^SCRIPTS //p' <<<"$out")
check "every script that loads lib.sh answers --help and -h with its description ($n scripts)" \
    bash -c '[ "${1:-0}" -gt 100 ] && ! grep -q "^NOHELP" <<<"$2"' _ "$n" "$out"
grep '^NOHELP' <<<"$out" | head -5 | sed 's/^/        /'
check "...and explains each option it takes, not only in a usage line" not grep -q '^UNEXPLAINED' <<<"$out"
grep '^UNEXPLAINED' <<<"$out" | head -10 | sed 's/^/        /'
check "...changing nothing (its home stays empty)" test -z "$(ls -A "$T/home")"
check "an extra's --remove and --manual are explained for it" \
    bash -c 'o=$(bash "$1" --help); grep -q "^  --remove   undo what it set up" <<<"$o" && grep -q "^  --manual   print the steps" <<<"$o"' _ "$HERE/extras/grim-slurp.sh"
check "...also when it handles them in a case" bash -c 'bash "$1" --help | grep -q "^  --remove   undo"' _ "$HERE/extras/security-key.sh"
check "make-install-iso.sh --help: each of its options, --public too" \
    bash -c 'o=$(bash "$1" --help); for x in user keymap locale timezone no-erase noencrypt autologin simplesway scripts out public; do grep -qE "^  --$x " <<<"$o" || exit 1; done' _ "$HERE/prep/make-install-iso.sh"
check "--help is answered before the script does anything (right after the core helpers load)" \
    bash -c 'grep -A2 "lib/core.sh\"$" "$1" | grep -qF "case \${1:-} in -h | --help) script_help \"\$0\"; exit 0 ;; esac"' _ "$HERE/lib.sh"

[ "$fails" -eq 0 ]