Bounded waits and validated arguments in run.sh and ui-drive.sh, a seeded settings directory and root-only staged rootfs permissions with their own tests, qmp.py and imgtools.py hardening, the fetch scripts checking what they download, and ASCII typography throughout. Each fix carries its test under qemu/tests or tests/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N3G6m9Aw5RyVY4ZowtKzEj
305 lines
13 KiB
Bash
Executable File
305 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Regression and coverage tests for build/fetch-vendor.sh.
|
|
#
|
|
# The script had no test of any kind before this: not the --check/--fetch
|
|
# state machine (MISSING/OK/DRIFTED, the lvgl/luckfox-pico path mapping, the
|
|
# "locally modified" annotation), not --help, not the clone stall guard. Two
|
|
# concrete regressions motivate the first two cases:
|
|
#
|
|
# - --help sliced its own source with a hardcoded line range that stopped
|
|
# one line too late, so it printed "set -uo pipefail" -- the first line
|
|
# of code -- as the last line of help text.
|
|
# - git clone ran with no bound on a stalled transfer: a dead peer or a
|
|
# wedged proxy mid-clone (the luckfox-pico tree alone is ~21 GB) hung the
|
|
# script forever with no way for a caller to tell "still working" from
|
|
# "wedged". The fix sets GIT_HTTP_LOW_SPEED_LIMIT/TIME so a stalled
|
|
# transfer aborts while a merely slow one is left alone.
|
|
#
|
|
# --help runs the shipping script directly. The --fetch/--check cases run a
|
|
# copy of it in a private fixture dir (its manifest lookup is relative to
|
|
# itself, so a copy is how its own HERE-relative resolution can be pointed at
|
|
# a manifest we control) against local, throwaway origin repos -- no network,
|
|
# and the real git binary does the work throughout (a thin logging wrapper
|
|
# only intercepts "clone" to record the env it saw, then execs straight
|
|
# through), so these exercise the shipping script's actual git calls.
|
|
set -uo pipefail
|
|
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
SCRIPT="$HERE/../../build/fetch-vendor.sh"
|
|
[ -f "$SCRIPT" ] || { echo "FAIL: script not found at $SCRIPT"; exit 1; }
|
|
|
|
FAIL=0
|
|
ok() { printf '[PASS] %s\n' "$1"; }
|
|
bad() { printf '[FAIL] %s\n' "$1"; FAIL=1; }
|
|
|
|
WORK="$(mktemp -d)"
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
|
|
GITC() { git -c user.email=t@t.invalid -c user.name=t "$@"; }
|
|
|
|
# mkrepo1 <dir> -> one commit on main, prints its hash
|
|
mkrepo1() {
|
|
local dir="$1"
|
|
git init -q -b main "$dir"
|
|
printf 'v1\n' > "$dir/file.txt"
|
|
git -C "$dir" add file.txt
|
|
GITC -C "$dir" commit -q -m v1
|
|
git -C "$dir" rev-parse HEAD
|
|
}
|
|
|
|
# mkrepo2 <dir> -> two commits on main, prints "C1 C2" (C1 older)
|
|
mkrepo2() {
|
|
local dir="$1" c1 c2
|
|
git init -q -b main "$dir"
|
|
printf 'v1\n' > "$dir/file.txt"
|
|
git -C "$dir" add file.txt
|
|
GITC -C "$dir" commit -q -m v1
|
|
c1="$(git -C "$dir" rev-parse HEAD)"
|
|
printf 'v2\n' > "$dir/file.txt"
|
|
git -C "$dir" add file.txt
|
|
GITC -C "$dir" commit -q -m v2
|
|
c2="$(git -C "$dir" rev-parse HEAD)"
|
|
printf '%s %s\n' "$c1" "$c2"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Regression: --help must not spill into the script's own code.
|
|
# ---------------------------------------------------------------------------
|
|
HELP_OUT="$(bash "$SCRIPT" --help)"
|
|
if printf '%s\n' "$HELP_OUT" | grep -q 'set -uo pipefail'; then
|
|
bad "--help does not print the script's own code"
|
|
else
|
|
ok "--help does not print the script's own code"
|
|
fi
|
|
LAST_LINE="$(printf '%s\n' "$HELP_OUT" | tail -1)"
|
|
if [ "$LAST_LINE" = "# someone's debugging session, not something to silently throw away." ]; then
|
|
ok "--help ends on the last comment line, not past it"
|
|
else
|
|
bad "--help ends on the last comment line, not past it (got: $LAST_LINE)"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Regression: the clone stall guard reaches git clone's environment.
|
|
# ---------------------------------------------------------------------------
|
|
REALGIT="$(command -v git)"
|
|
FAKEBIN="$WORK/fakebin"
|
|
mkdir -p "$FAKEBIN"
|
|
CAPTURE="$WORK/clone-env.txt"
|
|
cat > "$FAKEBIN/git" <<FAKEGIT
|
|
#!/bin/sh
|
|
if [ "\$1" = "clone" ]; then
|
|
printf '%s %s\n' "\$GIT_HTTP_LOW_SPEED_LIMIT" "\$GIT_HTTP_LOW_SPEED_TIME" >> "$CAPTURE"
|
|
fi
|
|
exec "$REALGIT" "\$@"
|
|
FAKEGIT
|
|
chmod +x "$FAKEBIN/git"
|
|
|
|
ORIGIN_GUARD="$WORK/origin-guard"
|
|
GUARD_C="$(mkrepo1 "$ORIGIN_GUARD")"
|
|
|
|
MANIFEST_GUARD="$WORK/manifest-guard"
|
|
printf 'widget\t%s\t%s\tguard test tree\n' "$ORIGIN_GUARD" "$GUARD_C" > "$MANIFEST_GUARD"
|
|
|
|
# fetch-vendor.sh finds its manifest next to itself, so give it a private
|
|
# fixture dir carrying a copy of the real script alongside our manifest.
|
|
GUARD_FIXTURE="$WORK/guard-fixture"
|
|
mkdir -p "$GUARD_FIXTURE"
|
|
cp "$SCRIPT" "$GUARD_FIXTURE/fetch-vendor.sh"
|
|
cp "$MANIFEST_GUARD" "$GUARD_FIXTURE/vendor.manifest"
|
|
|
|
: > "$CAPTURE"
|
|
PATH="$FAKEBIN:$PATH" bash "$GUARD_FIXTURE/fetch-vendor.sh" --fetch "$WORK/vendor-default" \
|
|
> "$WORK/guard-default.log" 2>&1
|
|
if [ "$(cat "$CAPTURE")" = "1000 60" ]; then
|
|
ok "clone runs with the default low-speed guard (1000 bytes/sec, 60s)"
|
|
else
|
|
bad "clone runs with the default low-speed guard (got: $(cat "$CAPTURE" 2>/dev/null))"
|
|
fi
|
|
|
|
: > "$CAPTURE"
|
|
PATH="$FAKEBIN:$PATH" WARDEN_VENDOR_LOW_SPEED_LIMIT=5 WARDEN_VENDOR_LOW_SPEED_TIME=9 \
|
|
bash "$GUARD_FIXTURE/fetch-vendor.sh" --fetch "$WORK/vendor-override" \
|
|
> "$WORK/guard-override.log" 2>&1
|
|
if [ "$(cat "$CAPTURE")" = "5 9" ]; then
|
|
ok "the low-speed guard is overridable"
|
|
else
|
|
bad "the low-speed guard is overridable (got: $(cat "$CAPTURE" 2>/dev/null))"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Coverage: the --check/--fetch state machine and the name-to-path mapping.
|
|
# ---------------------------------------------------------------------------
|
|
ORIGIN_WIDGET="$WORK/origin-widget"
|
|
read -r WIDGET_C1 WIDGET_C2 <<< "$(mkrepo2 "$ORIGIN_WIDGET")"
|
|
ORIGIN_LVGL="$WORK/origin-lvgl"
|
|
LVGL_C="$(mkrepo1 "$ORIGIN_LVGL")"
|
|
ORIGIN_SDK="$WORK/origin-sdk"
|
|
SDK_C="$(mkrepo1 "$ORIGIN_SDK")"
|
|
|
|
MANIFEST="$WORK/vendor.manifest"
|
|
write_manifest() { # write_manifest <widget-commit>
|
|
{
|
|
printf 'widget\t%s\t%s\tgeneric tree, default path mapping\n' "$ORIGIN_WIDGET" "$1"
|
|
printf 'lvgl\t%s\t%s\tlvgl name maps under ui/lvgl\n' "$ORIGIN_LVGL" "$LVGL_C"
|
|
printf 'luckfox-pico\t%s\t%s\tluckfox-pico name maps under sdk\n' "$ORIGIN_SDK" "$SDK_C"
|
|
} > "$MANIFEST"
|
|
}
|
|
|
|
STATE_FIXTURE="$WORK/state-fixture"
|
|
mkdir -p "$STATE_FIXTURE"
|
|
cp "$SCRIPT" "$STATE_FIXTURE/fetch-vendor.sh"
|
|
VENDOR_DIR="$WORK/vendor"
|
|
|
|
write_manifest "$WIDGET_C1"
|
|
cp "$MANIFEST" "$STATE_FIXTURE/vendor.manifest"
|
|
|
|
# Case 1: nothing cloned yet -> --check reports MISSING for all three, rc=1.
|
|
OUT="$(bash "$STATE_FIXTURE/fetch-vendor.sh" --check "$VENDOR_DIR")"; RC=$?
|
|
if [ "$RC" -ne 0 ] \
|
|
&& printf '%s\n' "$OUT" | grep -q '^MISSING widget' \
|
|
&& printf '%s\n' "$OUT" | grep -q '^MISSING lvgl' \
|
|
&& printf '%s\n' "$OUT" | grep -q '^MISSING luckfox-pico'; then
|
|
ok "--check reports MISSING and rc=1 when nothing is cloned"
|
|
else
|
|
bad "--check reports MISSING and rc=1 when nothing is cloned (rc=$RC)"
|
|
fi
|
|
|
|
# Case 2: --fetch clones each tree under its mapped path and checks out the pin.
|
|
OUT="$(bash "$STATE_FIXTURE/fetch-vendor.sh" --fetch "$VENDOR_DIR")"; RC=$?
|
|
if [ "$RC" -eq 0 ] \
|
|
&& [ -e "$VENDOR_DIR/widget/.git" ] \
|
|
&& [ -e "$VENDOR_DIR/ui/lvgl/.git" ] \
|
|
&& [ -e "$VENDOR_DIR/sdk/.git" ]; then
|
|
ok "--fetch clones lvgl under ui/lvgl and luckfox-pico under sdk"
|
|
else
|
|
bad "--fetch clones lvgl under ui/lvgl and luckfox-pico under sdk (rc=$RC)"
|
|
fi
|
|
if [ "$(git -C "$VENDOR_DIR/widget" rev-parse HEAD 2>/dev/null)" = "$WIDGET_C1" ]; then
|
|
ok "--fetch checks out the manifest-pinned commit"
|
|
else
|
|
bad "--fetch checks out the manifest-pinned commit"
|
|
fi
|
|
|
|
# Case 3: a checkout sitting at its pin --check's clean, rc=0.
|
|
OUT="$(bash "$STATE_FIXTURE/fetch-vendor.sh" --check "$VENDOR_DIR")"; RC=$?
|
|
if [ "$RC" -eq 0 ] && printf '%s\n' "$OUT" | grep -q "^OK widget ${WIDGET_C1:0:12}$"; then
|
|
ok "--check reports OK with no suffix for a clean checkout at the pin"
|
|
else
|
|
bad "--check reports OK with no suffix for a clean checkout at the pin"
|
|
fi
|
|
|
|
# Case 4: manifest moves to a commit the checkout is not on -> DRIFTED,
|
|
# rc=1, and the checkout itself is left untouched (never reset).
|
|
write_manifest "$WIDGET_C2"
|
|
cp "$MANIFEST" "$STATE_FIXTURE/vendor.manifest"
|
|
OUT="$(bash "$STATE_FIXTURE/fetch-vendor.sh" --check "$VENDOR_DIR")"; RC=$?
|
|
if [ "$RC" -ne 0 ] \
|
|
&& printf '%s\n' "$OUT" | grep -q "^DRIFTED widget want ${WIDGET_C2:0:12} have ${WIDGET_C1:0:12}"; then
|
|
ok "--check reports DRIFTED when HEAD does not match the pin"
|
|
else
|
|
bad "--check reports DRIFTED when HEAD does not match the pin"
|
|
fi
|
|
if [ "$(git -C "$VENDOR_DIR/widget" rev-parse HEAD 2>/dev/null)" = "$WIDGET_C1" ]; then
|
|
ok "a drifted checkout is reported, never reset"
|
|
else
|
|
bad "a drifted checkout is reported, never reset"
|
|
fi
|
|
|
|
# Case 5: back at the pin but with an uncommitted local change -> OK, but
|
|
# annotated, and still rc=0 (a dirty vendor tree is expected, not a failure).
|
|
write_manifest "$WIDGET_C1"
|
|
cp "$MANIFEST" "$STATE_FIXTURE/vendor.manifest"
|
|
echo "local debugging change" >> "$VENDOR_DIR/widget/file.txt"
|
|
OUT="$(bash "$STATE_FIXTURE/fetch-vendor.sh" --check "$VENDOR_DIR")"; RC=$?
|
|
if [ "$RC" -eq 0 ] && printf '%s\n' "$OUT" | grep -q "^OK widget ${WIDGET_C1:0:12} (locally modified)$"; then
|
|
ok "--check reports OK (locally modified) for a dirty checkout at the pin, rc=0"
|
|
else
|
|
bad "--check reports OK (locally modified) for a dirty checkout at the pin, rc=0"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Case 6: git clone fails (bad origin) -> reported, rc=1, no directory left
|
|
# behind for that tree, and -- the actual regression this guards -- the loop
|
|
# still reaches the remaining manifest entries and reports exactly one
|
|
# failure line for widget, not a second "FAILED to check out" once the
|
|
# clone's own continue has fired.
|
|
# ---------------------------------------------------------------------------
|
|
ORIGIN_BAD="$WORK/no-such-origin"
|
|
CLONEFAIL_FIXTURE="$WORK/clonefail-fixture"
|
|
mkdir -p "$CLONEFAIL_FIXTURE"
|
|
cp "$SCRIPT" "$CLONEFAIL_FIXTURE/fetch-vendor.sh"
|
|
{
|
|
printf 'widget\t%s\t%s\tbad origin, clone must fail\n' "$ORIGIN_BAD" "$WIDGET_C1"
|
|
printf 'lvgl\t%s\t%s\tlvgl name maps under ui/lvgl\n' "$ORIGIN_LVGL" "$LVGL_C"
|
|
printf 'luckfox-pico\t%s\t%s\tluckfox-pico name maps under sdk\n' "$ORIGIN_SDK" "$SDK_C"
|
|
} > "$CLONEFAIL_FIXTURE/vendor.manifest"
|
|
|
|
CLONEFAIL_DIR="$WORK/vendor-clonefail"
|
|
OUT="$(bash "$CLONEFAIL_FIXTURE/fetch-vendor.sh" --fetch "$CLONEFAIL_DIR" 2>&1)"; RC=$?
|
|
FAILED_COUNT="$(printf '%s\n' "$OUT" | grep -c 'FAILED to clone widget')"
|
|
if [ "$RC" -ne 0 ] && [ "$FAILED_COUNT" -eq 1 ] \
|
|
&& ! printf '%s\n' "$OUT" | grep -q 'FAILED to check out'; then
|
|
ok "--fetch reports FAILED to clone once and rc=1 for a bad origin"
|
|
else
|
|
bad "--fetch reports FAILED to clone once and rc=1 for a bad origin (rc=$RC, count=$FAILED_COUNT)"
|
|
fi
|
|
if [ ! -e "$CLONEFAIL_DIR/widget/.git" ]; then
|
|
ok "a failed clone leaves no checkout behind for that tree"
|
|
else
|
|
bad "a failed clone leaves no checkout behind for that tree"
|
|
fi
|
|
if [ -e "$CLONEFAIL_DIR/ui/lvgl/.git" ] && [ -e "$CLONEFAIL_DIR/sdk/.git" ]; then
|
|
ok "a clone failure on one tree does not stop the remaining trees from being fetched"
|
|
else
|
|
bad "a clone failure on one tree does not stop the remaining trees from being fetched"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Case 7: git checkout fails (pinned commit missing from the origin) ->
|
|
# reported, rc=1, the remaining trees still get fetched, and the checkout is
|
|
# left wherever the failed checkout left it -- a later --check must report
|
|
# that as DRIFTED, never mistake it for success.
|
|
# ---------------------------------------------------------------------------
|
|
BOGUS_COMMIT="deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
|
CHECKOUTFAIL_FIXTURE="$WORK/checkoutfail-fixture"
|
|
mkdir -p "$CHECKOUTFAIL_FIXTURE"
|
|
cp "$SCRIPT" "$CHECKOUTFAIL_FIXTURE/fetch-vendor.sh"
|
|
{
|
|
printf 'widget\t%s\t%s\tcommit missing from the origin, checkout must fail\n' "$ORIGIN_WIDGET" "$BOGUS_COMMIT"
|
|
printf 'lvgl\t%s\t%s\tlvgl name maps under ui/lvgl\n' "$ORIGIN_LVGL" "$LVGL_C"
|
|
printf 'luckfox-pico\t%s\t%s\tluckfox-pico name maps under sdk\n' "$ORIGIN_SDK" "$SDK_C"
|
|
} > "$CHECKOUTFAIL_FIXTURE/vendor.manifest"
|
|
|
|
CHECKOUTFAIL_DIR="$WORK/vendor-checkoutfail"
|
|
OUT="$(bash "$CHECKOUTFAIL_FIXTURE/fetch-vendor.sh" --fetch "$CHECKOUTFAIL_DIR" 2>&1)"; RC=$?
|
|
if [ "$RC" -ne 0 ] && printf '%s\n' "$OUT" | grep -q "FAILED to check out $BOGUS_COMMIT"; then
|
|
ok "--fetch reports FAILED to check out for a commit missing from the origin"
|
|
else
|
|
bad "--fetch reports FAILED to check out for a commit missing from the origin (rc=$RC)"
|
|
fi
|
|
# The regression this guards: if the checkout failure's own "continue" were
|
|
# ever dropped, the same loop iteration falls through into the have-vs-pin
|
|
# comparison below and prints a bogus DRIFTED/OK line for widget in this same
|
|
# --fetch run, on top of the FAILED line above.
|
|
if ! printf '%s\n' "$OUT" | grep -qE '^(DRIFTED {2}|OK {7})widget'; then
|
|
ok "a checkout failure does not fall through to a DRIFTED/OK line in the same run"
|
|
else
|
|
bad "a checkout failure does not fall through to a DRIFTED/OK line in the same run"
|
|
fi
|
|
if [ -e "$CHECKOUTFAIL_DIR/widget/.git" ] \
|
|
&& [ -e "$CHECKOUTFAIL_DIR/ui/lvgl/.git" ] && [ -e "$CHECKOUTFAIL_DIR/sdk/.git" ]; then
|
|
ok "a checkout failure on one tree does not stop the remaining trees from being fetched"
|
|
else
|
|
bad "a checkout failure on one tree does not stop the remaining trees from being fetched"
|
|
fi
|
|
|
|
OUT="$(bash "$CHECKOUTFAIL_FIXTURE/fetch-vendor.sh" --check "$CHECKOUTFAIL_DIR" 2>&1)"; RC=$?
|
|
if [ "$RC" -ne 0 ] && printf '%s\n' "$OUT" | grep -q "^DRIFTED widget want ${BOGUS_COMMIT:0:12}"; then
|
|
ok "a later --check reports the failed checkout as DRIFTED, never as success"
|
|
else
|
|
bad "a later --check reports the failed checkout as DRIFTED, never as success"
|
|
fi
|
|
|
|
[ "$FAIL" -eq 0 ] && echo "All fetch-vendor tests passed." || echo "Some fetch-vendor tests failed."
|
|
exit "$FAIL"
|