qemu and build: review fixes across the rig driver, boot script, and fetch helpers
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
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regression tests for build/fetch-buildroot-tarball.sh's retry/verify path.
|
||||
#
|
||||
# The script had never been exercised by anything (nothing calls it yet --
|
||||
# see its own header) or by a test, so its retry-on-mismatch, cleanup, and
|
||||
# already-verified short-circuit had never actually run. It also fetched with
|
||||
# no --connect-timeout/--max-time, so a connection that opens and then stalls
|
||||
# (a blackholed route, a hung proxy) would block forever instead of retrying;
|
||||
# issue tracked separately.
|
||||
#
|
||||
# Runs the real script (copied into a fixture dir so its own HERE-relative pin
|
||||
# lookup finds a pin we control) with a fake curl first on PATH, so no network
|
||||
# is used and the outcome of each attempt is exact.
|
||||
set -uo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT="$HERE/../../build/fetch-buildroot-tarball.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; }
|
||||
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
WANT_SHA="$(printf 'good-bytes' | sha256sum | awk '{print $1}')"
|
||||
|
||||
# A private copy of the real script plus a pin file we control, in its own
|
||||
# directory: the script resolves its pin relative to itself, so this is the
|
||||
# shipping file under test, not a reimplementation of it.
|
||||
setup() { # setup <case-name> -> prints the fixture dir
|
||||
local dir="$TMP/$1"
|
||||
mkdir -p "$dir/bin"
|
||||
cp "$SCRIPT" "$dir/fetch-buildroot-tarball.sh"
|
||||
printf '%s buildroot-2025.02.8.tar.xz\n' "$WANT_SHA" \
|
||||
> "$dir/buildroot-2025.02.8.tar.xz.sha256"
|
||||
printf '%s' "$dir"
|
||||
}
|
||||
|
||||
run() { # run <dir> <dest> -> stdout+stderr on stdout, $? in $RC
|
||||
local dir="$1" dest="$2"
|
||||
OUT="$(PATH="$dir/bin:$PATH" bash "$dir/fetch-buildroot-tarball.sh" "$dest" 2>&1)"
|
||||
RC=$?
|
||||
}
|
||||
|
||||
# --- case A: destination already matches the pin -> curl never runs -------
|
||||
dir="$(setup case-a)"
|
||||
printf 'good-bytes' > "$dir/dest.tar.xz"
|
||||
cat > "$dir/bin/curl" <<'FAKE'
|
||||
#!/bin/sh
|
||||
echo "curl should not have run" >&2
|
||||
exit 1
|
||||
FAKE
|
||||
chmod +x "$dir/bin/curl"
|
||||
run "$dir" "$dir/dest.tar.xz"
|
||||
if [ "$RC" -eq 0 ] && printf '%s' "$OUT" | grep -q 'already present and verified'; then
|
||||
ok "already-verified destination: curl never invoked"
|
||||
else
|
||||
bad "already-verified destination: curl never invoked (rc=$RC, out=$OUT)"
|
||||
fi
|
||||
|
||||
# --- case B: fails once, succeeds on retry -> verified, no extra attempts -
|
||||
dir="$(setup case-b)"
|
||||
cat > "$dir/bin/curl" <<FAKE
|
||||
#!/bin/sh
|
||||
cnt_file="$dir/curl-calls"
|
||||
n=0
|
||||
[ -f "\$cnt_file" ] && n=\$(cat "\$cnt_file")
|
||||
n=\$((n + 1))
|
||||
echo "\$n" > "\$cnt_file"
|
||||
out="" prev=""
|
||||
for a in "\$@"; do
|
||||
[ "\$prev" = "-o" ] && out="\$a"
|
||||
prev="\$a"
|
||||
done
|
||||
if [ "\$n" -lt 2 ]; then printf 'bad-bytes' > "\$out"
|
||||
else printf 'good-bytes' > "\$out"
|
||||
fi
|
||||
FAKE
|
||||
chmod +x "$dir/bin/curl"
|
||||
run "$dir" "$dir/dest.tar.xz"
|
||||
calls="$(cat "$dir/curl-calls" 2>/dev/null || echo 0)"
|
||||
if [ "$RC" -eq 0 ] && [ "$calls" -eq 2 ] && printf '%s' "$OUT" | grep -q 'sha256 verified'; then
|
||||
ok "mismatch then match: verified on attempt 2, stops retrying"
|
||||
else
|
||||
bad "mismatch then match: verified on attempt 2, stops retrying (rc=$RC calls=$calls out=$OUT)"
|
||||
fi
|
||||
|
||||
# --- case C: every attempt mismatches -> fails closed, no partial file left
|
||||
dir="$(setup case-c)"
|
||||
cat > "$dir/bin/curl" <<'FAKE'
|
||||
#!/bin/sh
|
||||
out="" prev=""
|
||||
for a in "$@"; do
|
||||
[ "$prev" = "-o" ] && out="$a"
|
||||
prev="$a"
|
||||
done
|
||||
printf 'always-bad' > "$out"
|
||||
FAKE
|
||||
chmod +x "$dir/bin/curl"
|
||||
run "$dir" "$dir/dest.tar.xz"
|
||||
if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q 'FATAL: could not fetch'; then
|
||||
ok "persistent mismatch: exits nonzero with FATAL"
|
||||
else
|
||||
bad "persistent mismatch: exits nonzero with FATAL (rc=$RC, out=$OUT)"
|
||||
fi
|
||||
if [ ! -e "$dir/dest.tar.xz" ]; then
|
||||
ok "persistent mismatch: no partial/corrupt tarball left at the destination"
|
||||
else
|
||||
bad "persistent mismatch: no partial/corrupt tarball left at the destination"
|
||||
fi
|
||||
attempts="$(printf '%s' "$OUT" | grep -c '== fetching')"
|
||||
if [ "$attempts" -eq 3 ]; then
|
||||
ok "persistent mismatch: exactly 3 attempts"
|
||||
else
|
||||
bad "persistent mismatch: exactly 3 attempts (got $attempts)"
|
||||
fi
|
||||
|
||||
# --- guard: the fetch carries a connect and overall timeout ---------------
|
||||
# Without these, --retry never fires (it only re-attempts a transfer curl has
|
||||
# already decided failed) and a connection that opens then stalls blocks
|
||||
# forever -- exactly the routing failure this workspace sees from some hosts.
|
||||
if grep -q -- '--connect-timeout' "$SCRIPT" && grep -q -- '--max-time' "$SCRIPT"; then
|
||||
ok "fetch carries --connect-timeout and --max-time"
|
||||
else
|
||||
bad "fetch carries --connect-timeout and --max-time"
|
||||
fi
|
||||
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo "ALL FETCH-BUILDROOT-TARBALL TESTS PASSED"
|
||||
else
|
||||
echo "FETCH-BUILDROOT-TARBALL TESTS FAILED"
|
||||
fi
|
||||
exit "$FAIL"
|
||||
Executable
+304
@@ -0,0 +1,304 @@
|
||||
#!/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"
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
#!/bin/bash
|
||||
# Regression tests for the post-build boot.img validation in
|
||||
# build/mk-bootimg.sh: the FIT metadata %512 check, the per-image
|
||||
# data-position %512 check (and fdtget's own failure path, issue #22 --
|
||||
# a missing or erroring fdtget used to be swallowed and treated as nothing
|
||||
# to check), and the >=4096 embedded-data-FIT check.
|
||||
#
|
||||
# Runs the real script end to end with stub mkimage/resource_tool/fdtget so
|
||||
# the arithmetic is exercised as it actually ships, not copied out and
|
||||
# re-tested in isolation. The stub mkimage answers the -B capability probe
|
||||
# and, for the real build invocation, writes a synthetic boot.img whose
|
||||
# 4-byte big-endian metadata-size word and total length are test-controlled --
|
||||
# the same field mk-bootimg.sh reads with `od -An -tu4 -j4 -N4 --endian=big`.
|
||||
set -uo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT="$HERE/../../build/mk-bootimg.sh"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
STUBS="$TMP/stubs"
|
||||
mkdir -p "$STUBS"
|
||||
|
||||
cat > "$STUBS/mkimage" <<'EOF'
|
||||
#!/bin/bash
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "Usage: mkimage [-T type] -l image" >&2
|
||||
echo " -B => align size in hex for FIT structure and header" >&2
|
||||
exit 1
|
||||
fi
|
||||
out=""
|
||||
for a in "$@"; do out="$a"; done
|
||||
python3 -c '
|
||||
import sys
|
||||
out, meta, total = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
|
||||
total = max(total, 8)
|
||||
data = bytearray(total)
|
||||
data[4:8] = (meta & 0xffffffff).to_bytes(4, "big")
|
||||
open(out, "wb").write(bytes(data))
|
||||
' "$out" "${MKBI_META:-512}" "${MKBI_TOTAL:-8192}"
|
||||
EOF
|
||||
chmod +x "$STUBS/mkimage"
|
||||
|
||||
cat > "$STUBS/resource_tool" <<'EOF'
|
||||
#!/bin/bash
|
||||
img=""
|
||||
for a in "$@"; do
|
||||
case "$a" in --image=*) img="${a#--image=}" ;; esac
|
||||
done
|
||||
echo stub > "$img"
|
||||
EOF
|
||||
chmod +x "$STUBS/resource_tool"
|
||||
|
||||
cat > "$STUBS/fdtget" <<'EOF'
|
||||
#!/bin/bash
|
||||
# Invoked as: fdtget -t u FILE /images/NAME data-position
|
||||
node="$4"
|
||||
name="${node#/images/}"
|
||||
var="FDTGET_$(printf '%s' "$name" | tr '[:lower:]' '[:upper:]')_POS"
|
||||
val="${!var-}"
|
||||
if [ -z "$val" ] || [ "$val" = "FAIL" ]; then
|
||||
echo "fdtget: FDT_ERR_NOTFOUND, $node data-position" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$val"
|
||||
EOF
|
||||
chmod +x "$STUBS/fdtget"
|
||||
|
||||
KERNEL="$TMP/zImage"; DTB="$TMP/rv1106-warden.dtb"
|
||||
head -c 4096 /dev/urandom > "$KERNEL"
|
||||
head -c 512 /dev/urandom > "$DTB"
|
||||
|
||||
fails=0
|
||||
run_case() { # run_case <name> <want_rc:ok|fail> <want_grep>
|
||||
local name="$1" want_rc="$2" want_grep="$3" out rc
|
||||
out="$(PATH="$STUBS:$PATH" "$SCRIPT" --kernel "$KERNEL" --dtb "$DTB" \
|
||||
--resource-tool "$STUBS/resource_tool" --out "$TMP/boot.img" 2>&1)"
|
||||
rc=$?
|
||||
if [ "$want_rc" = ok ] && [ "$rc" -ne 0 ]; then
|
||||
echo "FAIL: $name (expected success, got rc=$rc: $out)"; fails=$((fails + 1)); return
|
||||
fi
|
||||
if [ "$want_rc" = fail ] && [ "$rc" -eq 0 ]; then
|
||||
echo "FAIL: $name (expected failure, script exited 0: $out)"; fails=$((fails + 1)); return
|
||||
fi
|
||||
if ! printf '%s' "$out" | grep -qF -- "$want_grep"; then
|
||||
echo "FAIL: $name (output missing '$want_grep'): $out"; fails=$((fails + 1)); return
|
||||
fi
|
||||
echo "PASS: $name"
|
||||
}
|
||||
|
||||
# A well-formed image: metadata 512-aligned and small, every data-position
|
||||
# 512-aligned. The baseline every failure case below is a single change from.
|
||||
MKBI_META=512 MKBI_TOTAL=8192 \
|
||||
FDTGET_FDT_POS=512 FDTGET_KERNEL_POS=1024 FDTGET_RESOURCE_POS=1536 \
|
||||
run_case "well-formed image accepted" ok "FIT metadata 512 bytes"
|
||||
|
||||
# Metadata size itself not a multiple of 512 (the exact value measured from
|
||||
# the SDK's vendored mkimage 2017.09, see the comment above the probe).
|
||||
MKBI_META=1064 MKBI_TOTAL=8192 \
|
||||
FDTGET_FDT_POS=512 FDTGET_KERNEL_POS=1024 FDTGET_RESOURCE_POS=1536 \
|
||||
run_case "unaligned metadata size rejected" fail "not a multiple of 512"
|
||||
|
||||
# One sub-image's data-position not a multiple of 512; the FATAL must name it.
|
||||
MKBI_META=512 MKBI_TOTAL=8192 \
|
||||
FDTGET_FDT_POS=512 FDTGET_KERNEL_POS=148 FDTGET_RESOURCE_POS=1536 \
|
||||
run_case "unaligned data-position rejected" fail "/images/kernel data-position 148 is not 512-aligned"
|
||||
|
||||
# fdtget itself fails (not on PATH, or the FIT it just built is malformed).
|
||||
# Regression for issue #22: this used to be swallowed by `|| true` and
|
||||
# treated as "nothing to check" instead of a build failure.
|
||||
MKBI_META=512 MKBI_TOTAL=8192 \
|
||||
FDTGET_FDT_POS=512 FDTGET_KERNEL_POS=FAIL FDTGET_RESOURCE_POS=1536 \
|
||||
run_case "fdtget failure fails the build, not skips the check" fail \
|
||||
"fdtget could not read /images/kernel data-position"
|
||||
|
||||
# Metadata swelled to >=4096 bytes: an embedded-data FIT, which this U-Boot
|
||||
# rejects outright. 4096 is itself a multiple of 512 so this must be caught
|
||||
# by the second check, not mistaken for the first.
|
||||
MKBI_META=4096 MKBI_TOTAL=8192 \
|
||||
FDTGET_FDT_POS=512 FDTGET_KERNEL_POS=1024 FDTGET_RESOURCE_POS=1536 \
|
||||
run_case "embedded-data FIT rejected" fail "embedded-data FIT"
|
||||
|
||||
[ "$fails" -eq 0 ] && echo "All mk-bootimg boot.img validation tests passed." || echo "$fails test(s) failed."
|
||||
exit "$fails"
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
# Regression test: every offline regression-test script this job ships must
|
||||
# actually be invoked by the qemu-tools CI job, not just committed.
|
||||
#
|
||||
# run-probe-tests.sh (guards issue #17) sat in the tree unwired into
|
||||
# .github/workflows/ci.yml: it passed by hand but ran nowhere in CI, so a
|
||||
# regression in the mkimage probe would only have surfaced on the next
|
||||
# workflow_dispatch kernel build, not on every push/PR. The same gap later
|
||||
# reopened for six more scripts written the same way, so this now checks
|
||||
# every one of them (including itself) instead of only the first: it
|
||||
# isolates the qemu-tools job and checks each script's basename appears in
|
||||
# its steps.
|
||||
set -uo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
CI_YML="$HERE/../../.github/workflows/ci.yml"
|
||||
|
||||
[ -f "$CI_YML" ] || { echo "FAIL: no workflow file at $CI_YML"; exit 1; }
|
||||
|
||||
# Isolate the qemu-tools job: from its own header line up to (but not
|
||||
# including) the next job at the same two-space indent.
|
||||
job="$(awk '
|
||||
/^ qemu-tools:/ { inside = 1; print; next }
|
||||
inside && /^ [A-Za-z0-9_-]+:/ { exit }
|
||||
inside { print }
|
||||
' "$CI_YML")"
|
||||
|
||||
[ -n "$job" ] || { echo "FAIL: qemu-tools job not found in $CI_YML"; exit 1; }
|
||||
|
||||
# Every offline regression-test script the qemu-tools job owns. Add new
|
||||
# scripts here when they are written, not only when someone remembers to
|
||||
# wire them in -- that is the failure this test exists to catch.
|
||||
scripts=(
|
||||
"tests/mk-bootimg/run-probe-tests.sh"
|
||||
"qemu/tests/run-sh-args-test.sh"
|
||||
"qemu/tests/seed-dir.sh"
|
||||
"qemu/tests/stage-rootfs-perms.sh"
|
||||
"tests/fetch-vendor/run-fetch-vendor-tests.sh"
|
||||
"tests/fetch-buildroot-tarball/run-fetch-buildroot-tarball-tests.sh"
|
||||
"tests/mk-bootimg/run-boot-img-validate-tests.sh"
|
||||
"tests/mk-bootimg/run-help-tests.sh"
|
||||
"tests/mk-bootimg/run-ci-wiring-tests.sh"
|
||||
)
|
||||
|
||||
fail=0
|
||||
for s in "${scripts[@]}"; do
|
||||
base="$(basename "$s")"
|
||||
if echo "$job" | grep -q -- "$base"; then
|
||||
echo "PASS: qemu-tools job invokes $s"
|
||||
else
|
||||
echo "FAIL: qemu-tools job never runs $s"
|
||||
fail=1
|
||||
fi
|
||||
done
|
||||
|
||||
exit "$fail"
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# Regression test: --help must not spill into build/mk-bootimg.sh's own code.
|
||||
#
|
||||
# build/fetch-vendor.sh's --help sliced its own source with a hardcoded line
|
||||
# range that stopped one line too late, printing "set -uo pipefail" -- the
|
||||
# first line of code -- as the last line of help text. mk-bootimg.sh's
|
||||
# --help used the same hardcoded-range shape (currently pointed at the right
|
||||
# span), so the same slip was one header-comment edit away here too. It now
|
||||
# uses the same self-terminating awk pattern fetch-vendor.sh was fixed to
|
||||
# use, so this pins both that it stays in sync as the header grows or
|
||||
# shrinks and that it matches the header verbatim today.
|
||||
set -uo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT="$HERE/../../build/mk-bootimg.sh"
|
||||
[ -f "$SCRIPT" ] || { echo "FAIL: script not found at $SCRIPT"; exit 1; }
|
||||
|
||||
fail=0
|
||||
|
||||
HELP_OUT="$(bash "$SCRIPT" --help)"
|
||||
|
||||
if printf '%s\n' "$HELP_OUT" | grep -q 'set -euo pipefail'; then
|
||||
echo "FAIL: --help prints the script's own code"
|
||||
fail=1
|
||||
else
|
||||
echo "PASS: --help does not print the script's own code"
|
||||
fi
|
||||
|
||||
LAST_LINE="$(printf '%s\n' "$HELP_OUT" | tail -1)"
|
||||
if [ "$LAST_LINE" = "# [--resource-tool PATH]" ]; then
|
||||
echo "PASS: --help ends on the last header comment line, not past it"
|
||||
else
|
||||
echo "FAIL: --help ends on the last header comment line, not past it (got: $LAST_LINE)"
|
||||
fail=1
|
||||
fi
|
||||
|
||||
FIRST_LINE="$(printf '%s\n' "$HELP_OUT" | head -1)"
|
||||
if [ "$FIRST_LINE" = "# Package a bootable boot.img from a kernel this SDK built." ]; then
|
||||
echo "PASS: --help starts after the shebang, not on it"
|
||||
else
|
||||
echo "FAIL: --help starts after the shebang, not on it (got: $FIRST_LINE)"
|
||||
fail=1
|
||||
fi
|
||||
|
||||
exit "$fail"
|
||||
Reference in New Issue
Block a user