| git.druid.rocks | index | druid520 | mp | test/ | suites/ | fuzz.sh |
test/suites/fuzz.sh
# suite: randomized soundness fuzzing -- thousands of seeded iterations
# generating random package graphs (deps/tags/prefs/slots/use/hooks/
# patches/conflicts) and asserting exact invariants against mp's actual
# behavior: transitive-closure equality, prune-closure equality, db
# shape, marker consistency, hook firing counts and order, patch gating,
# use-composed slot composition, lowest-pref provider choice, and clean
# failure on conflict. deterministic: FUZZ_SEED seeds everything,
# FUZZ_ITERATIONS sets the batch size (1000 by default), so a failing
# iteration reproduces exactly by rerunning with the same seed, and its
# full state (scratch ports, conf, db, hook log) is left on disk.
FUZZ_SEED=${FUZZ_SEED:-1337}
FUZZ_ITERATIONS=${FUZZ_ITERATIONS:-1000}
# deterministic lcg: fz_rnd <bound> sets REPLY in [0,bound).
fz_rnd() {
FUZZ_STATE=$(( (FUZZ_STATE * 1103515245 + 12345) & 2147483647 ))
REPLY=$(( FUZZ_STATE % $1 ))
}
# port index from a name or canon (fz..p<N>[:...]).
fz_idx_of() {
FZ_K_TMP=${1%%:*}
FZ_K_TMP=${FZ_K_TMP##*p}
REPLY=$FZ_K_TMP
}
# canon of a port, by index, name, or canon (all resolve to the same
# numeric suffix).
fz_canon_of() {
fz_idx_of "$1"
eval "REPLY=\$FZ_CANON_$REPLY"
}
# one iteration: generate, install, assert every invariant, prune-check,
# remove one package, re-check. returns 0/1.
fz_iteration() {
FUZZ_I=$1
FUZZ_STATE=$(( (FUZZ_SEED + FUZZ_I * 2654435761) & 2147483647 ))
reset_state
printf 'GLOBAL_DEPS=" "\n' >> "$CONF"
TAB=$(printf '\t')
# --- iteration shape ----------------------------------------
fz_rnd 100; FZ_MODE=$REPLY # <20 conflict, <45 provider-choice
fz_rnd 5; FZ_NP=$(( 3 + REPLY )) # 3..7 ports
FZ_NTAG=0
fz_rnd 100
if [ "$REPLY" -lt 60 ]; then FZ_NTAG=1; fi
if [ "$FZ_NTAG" -eq 1 ]; then
fz_rnd 100
[ "$REPLY" -lt 40 ] && FZ_NTAG=2
fi
if [ "$FZ_MODE" -ge 20 ] && [ "$FZ_MODE" -lt 45 ]; then
FZ_NTAG=1
fi
FZ_PIPELINE=0
fz_rnd 100
[ "$REPLY" -lt 25 ] && FZ_PIPELINE=1
# tag providers live at the BOTTOM of the index order (ports 1/2
# in provider-choice mode, port 1 otherwise), and %tag references
# only ever come from higher indices: a provider's own dependency
# closure (always lower indices) can then never reach a %tag
# reference, so no provider-referencing-its-own-tag cycle is
# possible. in provider-choice mode plain deps also skip ports 1/2
# entirely, so neither provider can be installed by any edge other
# than the tag itself (which would trip resolve_dep's "already
# satisfied" shortcut and skip the contested pick).
if [ "$FZ_MODE" -ge 20 ] && [ "$FZ_MODE" -lt 45 ]; then
FZ_PROVMAX=2
FZ_DEPMIN=3
else
FZ_PROVMAX=1
FZ_DEPMIN=2
fi
# --- generate ports ------------------------------------------
j=1
while [ "$j" -le "$FZ_NP" ]; do
name="fz${FUZZ_I}p$j"
fz_rnd 9; ver="1.$(( 1 + REPLY ))"
slot=""
slotuse=""
enabled=""
fz_rnd 100
if [ "$REPLY" -lt 20 ]; then
fz_rnd 3
case "$REPLY" in
0) slot="2" ;;
1) slot="3" ;;
2) slot="7" ;;
esac
fi
fz_rnd 100
[ "$REPLY" -lt 25 ] && slotuse="ssl lto"
if [ "$slotuse" != "" ]; then
fz_rnd 100; [ "$REPLY" -lt 50 ] && enabled=" ssl"
fz_rnd 100; [ "$REPLY" -lt 50 ] && enabled="$enabled lto"
joined=""
for f in ssl lto; do
case "$enabled" in
*" $f"*)
if [ "$joined" = "" ]; then
joined="$f"
else
joined="$joined-$f"
fi
;;
esac
done
if [ "$joined" != "" ]; then
slotpart="${slot:+$slot-}$joined"
else
slotpart="$slot"
fi
if [ "$slotpart" = "" ]; then
canon="$name"
else
canon="$name:$slotpart"
fi
else
if [ "$slot" = "" ]; then
canon="$name"
else
canon="$name:$slot"
fi
fi
# use-flag deps: enabled flags only, referencing EARLIER ports
# or a tag (acyclic by construction)
usedepstr=""
fz_rnd 100
if [ "$slotuse" != "" ] && [ "$REPLY" -lt 60 ] && [ "$j" -gt 1 ]; then
fz_rnd 100
if [ "$FZ_NTAG" -ge 1 ] && [ "$REPLY" -lt 50 ] && [ "$j" -gt "$FZ_PROVMAX" ]; then
usedep_ssl="%fz${FUZZ_I}t1"
elif [ "$j" -gt "$FZ_DEPMIN" ]; then
fz_rnd "$(( j - FZ_DEPMIN ))"
usedep_ssl="fz${FUZZ_I}p$(( FZ_DEPMIN + REPLY ))"
else
usedep_ssl=""
fi
[ "$usedep_ssl" != "" ] && usedepstr="ssl:$usedep_ssl"
fz_rnd 100
if [ "$REPLY" -lt 40 ] && [ "$j" -gt "$FZ_DEPMIN" ]; then
fz_rnd "$(( j - FZ_DEPMIN ))"
usedepstr="$usedepstr lto:b:fz${FUZZ_I}p$(( FZ_DEPMIN + REPLY ))"
fi
fi
deps=""
# in provider-choice mode the LAST port is forced to depend on
# the contested tag (its pkg_deps and FZ_DEPS agree, since the
# random deps are skipped entirely)
if [ "$FZ_MODE" -ge 20 ] && [ "$FZ_MODE" -lt 45 ] && [ "$j" -eq "$FZ_NP" ]; then
deps="%fz${FUZZ_I}t1"
else
fz_rnd 100
if [ "$REPLY" -lt 55 ] && [ "$j" -gt "$FZ_DEPMIN" ]; then
fz_rnd 2
ndep=$(( REPLY + 1 ))
k=0
while [ "$k" -lt "$ndep" ]; do
fz_rnd 100
if [ "$FZ_NTAG" -ge 1 ] && [ "$REPLY" -lt 35 ]; then
deps="$deps %fz${FUZZ_I}t1"
else
# bounded to start at FZ_DEPMIN, same as every other dep
# kind below (bdep/rdep/soft/usedep) -- in provider-choice
# mode this is load-bearing, not cosmetic: the provider-pick
# assertion assumes ports 1/2 are reachable ONLY through the
# contested tag edge (see the FZ_PROVMAX/FZ_DEPMIN comment
# above), and a plain dep landing on port 1 or 2 directly
# would install the "losing" provider for a real reason,
# tripping that assertion.
fz_rnd "$(( j - FZ_DEPMIN ))"
deps="$deps fz${FUZZ_I}p$(( FZ_DEPMIN + REPLY ))"
fz_rnd 100
if [ "$REPLY" -lt 30 ]; then
fz_rnd 2
if [ "$REPLY" -eq 0 ]; then
deps="${deps}>=1.0"
else
deps="${deps}~1"
fi
fi
fi
k=$(( k + 1 ))
done
fi
fi
soft=""
fz_rnd 100
if [ "$REPLY" -lt 15 ] && [ "$j" -gt "$FZ_DEPMIN" ]; then
fz_rnd "$(( j - FZ_DEPMIN ))"
soft="fz${FUZZ_I}p$(( FZ_DEPMIN + REPLY ))"
fi
bdep=""
rdep=""
fz_rnd 100
if [ "$REPLY" -lt 15 ] && [ "$j" -gt "$FZ_DEPMIN" ]; then
fz_rnd "$(( j - FZ_DEPMIN ))"
bdep="fz${FUZZ_I}p$(( FZ_DEPMIN + REPLY ))"
fz_rnd "$(( j - FZ_DEPMIN ))"
rdep="fz${FUZZ_I}p$(( FZ_DEPMIN + REPLY ))"
fi
hookpre=""
fz_rnd 100
[ "$REPLY" -lt 20 ] && hookpre="1"
hookdir=""
fz_rnd 100
[ "$REPLY" -lt 20 ] && hookdir="1"
haspatches=0
fz_rnd 100
[ "$REPLY" -lt 15 ] && haspatches=1
eval "FZ_CANON_$j=\"$canon\""
eval "FZ_DEPS_$j=\"$deps $rdep $bdep $usedepstr $soft\""
eval "FZ_RDEPS_$j=\"$deps $usedepstr $soft $rdep\""
eval "FZ_EN_$j=\"$enabled\""
eval "FZ_PATCH_$j=$haspatches"
eval "FZ_HOOKPRE_$j=$hookpre"
eval "FZ_HOOKDIR_$j=$hookdir"
# --- write the port ---------------------------------------
body="pkg_name=\"$name\"
pkg_ver=\"$ver\"
"
if [ "$slot" != "" ]; then
body="${body}pkg_slot=\"$slot\"
"
fi
if [ "$slotuse" != "" ]; then
body="${body}pkg_slot_use=\"$slotuse\"
pkg_use=\"$usedepstr\"
"
fi
if [ "$deps" != "" ]; then
body="${body}pkg_deps=\"$deps\"
"
fi
if [ "$soft" != "" ]; then
body="${body}pkg_deps_soft=\"$soft\"
"
fi
if [ "$bdep" != "" ]; then
body="${body}pkg_bdepend=\"$bdep\"
pkg_rdepend=\"$rdep\"
"
fi
if [ "$haspatches" -eq 1 ]; then
body="${body}fetch:
mkdir -p $name
"
fi
if [ "$FZ_PIPELINE" -eq 1 ] && [ $(( j % 3 )) -eq 0 ]; then
body="${body}build:
:
"
fi
body="${body}install:
touch \$MP_PREFIX/${name}-installed
remove:
rm -f \$MP_PREFIX/${name}-installed
"
mkport "$name" <<EOF
$body
EOF
j=$(( j + 1 ))
done
# --- tags / providers -----------------------------------------
# --- roots and expected closures -------------------------------
ROOTS=""
# in provider-choice mode the forced tag consumer (the last port)
# is always one of the roots, so the contested pick is guaranteed
# to actually happen; roots never land on the provider ports (a
# rooted provider would satisfy the tag before the pick).
if [ "$FZ_MODE" -ge 20 ] && [ "$FZ_MODE" -lt 45 ]; then
ROOTS=" fz${FUZZ_I}p$FZ_NP"
fz_rnd "$(( FZ_NP - FZ_DEPMIN + 1 ))"
ROOTS="$ROOTS fz${FUZZ_I}p$(( FZ_DEPMIN + REPLY ))"
else
fz_rnd 2
nroots=$(( REPLY + 1 ))
k=0
while [ "$k" -lt "$nroots" ]; do
fz_rnd "$(( FZ_NP - FZ_DEPMIN + 1 ))"
ROOTS="$ROOTS fz${FUZZ_I}p$(( FZ_DEPMIN + REPLY ))"
k=$(( k + 1 ))
done
fi
ROOT_CANONS=""
for r in $ROOTS; do
fz_canon_of "$r"
ROOT_CANONS="$ROOT_CANONS $REPLY"
done
# providers: fixed early indices (see the FZ_PROVMAX/FZ_DEPMIN
# rationale in the shape section). t2 (when generated) is never
# referenced by anything, so its provider is arbitrary.
PROVIDERS=""
PROV2=""
if [ "$FZ_MODE" -ge 20 ] && [ "$FZ_MODE" -lt 45 ]; then
p=1
p2=2
fz_rnd 50; pref=$(( REPLY + 1 ))
fz_rnd 50; pref2=$(( REPLY + 1 ))
[ "$pref2" -eq "$pref" ] && pref2=$(( pref2 % 50 + 1 ))
[ "$pref2" -eq "$pref" ] && pref2=$(( pref2 + 1 ))
fz_canon_of 1; name1=$REPLY
fz_canon_of 2; name2=$REPLY
mkdir -p "$SCRATCH/fz${FUZZ_I}p1"
cat >> "$SCRATCH/fz${FUZZ_I}p1/pkg.conf" <<EOF
pkg_tags="fz${FUZZ_I}t1"
pkg_pref="$pref"
EOF
mkdir -p "$SCRATCH/fz${FUZZ_I}p2"
cat >> "$SCRATCH/fz${FUZZ_I}p2/pkg.conf" <<EOF
pkg_tags="fz${FUZZ_I}t1"
pkg_pref="$pref2"
EOF
if [ "$pref2" -lt "$pref" ]; then
eval "FZ_TAGPROV_1=\"$name2\""
eval "FZ_TAGLIST_1=\"$name2 $name1\""
else
eval "FZ_TAGPROV_1=\"$name1\""
eval "FZ_TAGLIST_1=\"$name1 $name2\""
fi
eval "FZ_TAGPROVS_1=\"1 2\""
PROVIDERS="$name1 $name2"
PROV2="$name2"
fi
if [ "$FZ_MODE" -ge 45 ] || [ "$FZ_MODE" -lt 20 ]; then
k=1
while [ "$k" -le "$FZ_NTAG" ]; do
if [ "$k" -eq 1 ]; then
p=1
else
# excludes port 1 (t1's own fixed provider, just
# above): pkg.conf is a plain key=value format, so a
# second "pkg_tags=" line on the SAME port doesn't
# add a tag, it silently overwrites the first --
# picking the same port for both tags would leave
# t1 with no provider at all (a real, and correct,
# "no port provides tag" failure on install, just
# not the scenario this generator meant to create).
fz_rnd "$(( FZ_NP - 1 ))"
p=$(( REPLY + 2 ))
fi
fz_rnd 50; pref=$(( REPLY + 1 ))
fz_canon_of "$p"; name1=$REPLY
mkdir -p "$SCRATCH/fz${FUZZ_I}p$p"
cat >> "$SCRATCH/fz${FUZZ_I}p$p/pkg.conf" <<EOF
pkg_tags="fz${FUZZ_I}t$k"
pkg_pref="$pref"
EOF
eval "FZ_TAGPROV_$k=\"$name1\""
eval "FZ_TAGPROVS_$k=\"$p\""
eval "FZ_TAGLIST_$k=\"$name1\""
PROVIDERS="$PROVIDERS $name1"
k=$(( k + 1 ))
done
fi
# --- per-package config, hooks, patches -----------------------
j=1
while [ "$j" -le "$FZ_NP" ]; do
eval "en=\$FZ_EN_$j"
if [ "$en" != "" ]; then
cat >> "$CONF" <<EOF
fz${FUZZ_I}p$j:
USE=$en
EOF
fi
eval "hp=\$FZ_HOOKPRE_$j"
if [ "$hp" = "1" ]; then
cat >> "$CONF" <<EOF
fz${FUZZ_I}p$j:
hook_pre_install=echo pre$j >> /tmp/fz${FUZZ_I}-hooks.log
EOF
fi
eval "hd=\$FZ_HOOKDIR_$j"
if [ "$hd" = "1" ]; then
mkdir -p "$WD/hooks/post_install"
cat > "$WD/hooks/post_install/dir$j" <<EOF
#!/bin/sh
echo dir$j >> /tmp/fz${FUZZ_I}-hooks.log
EOF
chmod +x "$WD/hooks/post_install/dir$j"
fi
eval "hp2=\$FZ_PATCH_$j"
if [ "$hp2" -eq 1 ]; then
mkdir -p "$SCRATCH/fz${FUZZ_I}p$j/patches"
printf -- '--- a/dummy\n+++ b/dummy\n@@ -0,0 +1 @@\n+uncond\n' > "$SCRATCH/fz${FUZZ_I}p$j/patches/001.patch"
printf -- '--- a/dummy\n+++ b/dummy\n@@ -0,0 +1 @@\n+sslpatch\n' > "$SCRATCH/fz${FUZZ_I}p$j/patches/002.patch"
cat > "$SCRATCH/fz${FUZZ_I}p$j/patches/patches.conf" <<EOF
002.patch:
IF_USE=ssl
EOF
fi
j=$(( j + 1 ))
done
# --- random pre_build pipeline (chain, third gated off) --------
PIPE_ORDER=""
if [ "$FZ_PIPELINE" -eq 1 ]; then
mkdir -p "$WD/hooks/pre_build"
fz_rnd 3
case "$REPLY" in
0) first=a ;;
1) first=b ;;
2) first=c ;;
esac
rest=""
for L in a b c; do
[ "$L" = "$first" ] && continue
rest="$rest$L"
done
second=$(printf '%s' "$rest" | cut -c1)
third=$(printf '%s' "$rest" | cut -c2)
PIPE_ORDER="$first $second"
for L in $first $second $third; do
cat > "$WD/hooks/pre_build/$L" <<EOF
#!/bin/sh
echo pipe$L >> /tmp/fz${FUZZ_I}-hooks.log
EOF
chmod +x "$WD/hooks/pre_build/$L"
done
cat > "$WD/hooks/pre_build/hooks.conf" <<EOF
$second:
AFTER=$first
$third:
AFTER=$second
IF_USE=fz${FUZZ_I}zzznonexistent
EOF
fi
# --- conflict mode: target conflicts with root -----------------
CONFLICT_TARGET=""
if [ "$FZ_MODE" -lt 20 ]; then
fz_rnd "$FZ_NP"; ci1=$(( REPLY + 1 ))
fz_rnd "$FZ_NP"; ci2=$(( REPLY + 1 ))
while [ "$ci2" -eq "$ci1" ]; do
fz_rnd "$FZ_NP"; ci2=$(( REPLY + 1 ))
done
# every dep kind above only ever targets a STRICTLY lower
# index than its own port, so the port-index order is a
# valid topological order of the whole generated dep DAG --
# giving CONFLICT_TARGET the lower index guarantees it can
# never depend (directly or transitively) on CONFLICT_ROOT.
# without this, installing CONFLICT_TARGET first (below) can
# occasionally pull CONFLICT_ROOT in as an ordinary dep of
# its own, so by the time the conflict install runs
# CONFLICT_ROOT is already installed and the whole conflict
# check never triggers -- not a conflict-detection failure,
# just a scenario this generator should never have posed.
if [ "$ci1" -lt "$ci2" ]; then
ci_target=$ci1; ci_root=$ci2
else
ci_target=$ci2; ci_root=$ci1
fi
CONFLICT_TARGET="fz${FUZZ_I}p$ci_target"
CONFLICT_ROOT="fz${FUZZ_I}p$ci_root"
cat >> "$SCRATCH/$CONFLICT_TARGET/pkg.conf" <<EOF
pkg_conflicts="$CONFLICT_ROOT"
EOF
fi
# expected closures. the INSTALL closure must simulate resolve_dep's
# actual depth-first order exactly: deps are processed in
# effective_deps order, and a %tag dep picks the lowest-pref
# provider UNLESS another provider of that tag is already installed
# at that moment (pick_provider's installed-first shortcut), so the
# visit order decides which provider wins. the RUNTIME closure is a
# plain set reachability with prune's own "first lexicographically
# sorted installed provider" tag rule, order-free.
# fz_tok_target <tok> <idx> <runtime> resolves one dep token to a
# canon in REPLY ("" when it contributes nothing).
fz_tok_target() {
local tok idx mode target flag enx rest2 tag kk pl pc t
tok="$1"
idx="$2"
mode="$3"
target=""
case "$tok" in
ssl:*|lto:*)
flag=${tok%%:*}
eval "enx=\$FZ_EN_$idx"
case "$enx" in
*" $flag"*) ;;
*) REPLY=""; return 0 ;;
esac
rest2=${tok#*:}
case "$rest2" in
b:*) [ "$mode" = "1" ] && { REPLY=""; return 0; }; rest2=${rest2#b:} ;;
esac
tok="$rest2"
;;
esac
case "$tok" in
%*)
tag=${tok#%}
tag=${tag%%[>=<~]*}
kk=1
while [ "$kk" -le "$FZ_NTAG" ]; do
if [ "$tag" = "fz${FUZZ_I}t$kk" ]; then
if [ "$mode" = "1" ]; then
eval "target=\$FZ_RTAGPROV_$kk"
else
# install-time pick: first (lowest-pref)
# provider already in FZ_DONE, else the
# lowest-pref one outright.
eval "pl=\$FZ_TAGLIST_$kk"
target=""
for pc in $pl; do
case " $FZ_DONE " in
*" $pc "*)
if [ "$target" = "" ]; then
target="$pc"
fi
;;
esac
done
if [ "$target" = "" ]; then
target=$(echo "$pl" | cut -d' ' -f1)
fi
fi
break
fi
kk=$(( kk + 1 ))
done
;;
*)
case "$tok" in
\?*) t=${tok#\?} ;;
*) t="$tok" ;;
esac
t=${t%%[>=<~]*}
fz_canon_of "$t"
target=$REPLY
;;
esac
REPLY="$target"
return 0
}
# depth-first install simulation (the db-state shortcut needs the
# exact visit order).
fz_dfs() {
# cur/idx/ds/tok/target are recursion-sensitive: fz_dfs calls
# itself for a dependency's own dependencies before this loop
# is done with the rest of $ds, so without "local" each
# recursive descent clobbers idx/ds out from under the
# resuming outer call, making fz_tok_target check USE-flag
# gating (FZ_EN_$idx) against whatever port was visited last
# instead of the port actually being walked -- silently
# dropping (or wrongly keeping) later USE-gated dep tokens.
local cur idx ds tok target
cur="$1"
case " $FZ_DONE " in
*" $cur "*) return 0 ;;
esac
FZ_DONE="$FZ_DONE $cur"
fz_idx_of "$cur"
idx=$REPLY
case "$idx" in
''|*[!0-9]*) return 0 ;;
esac
[ "$idx" -ge 1 ] 2>/dev/null || return 0
[ "$idx" -le "$FZ_NP" ] 2>/dev/null || return 0
eval "ds=\$FZ_DEPS_$idx"
for tok in $ds; do
[ "$tok" = "" ] && continue
fz_tok_target "$tok" "$idx" 0
target=$REPLY
if [ "$target" != "" ]; then
case " $FZ_DONE " in
*" $target "*) ;;
*) fz_dfs "$target" ;;
esac
fi
done
return 0
}
FZ_DONE=""
FZ_REQ=""
for r in $ROOTS; do
fz_canon_of "$r"
rc=$REPLY
# every entry in $ROOTS is its own explicit "mp install"
# argument, so it belongs in FZ_REQ regardless of whether an
# EARLIER root's own dependency walk already reached it first
# (order in $ROOTS is exactly mp's own argv order) -- real mp
# now promotes an already-installed-as-a-dep package's
# requested flag the same way when it's later named
# explicitly, so gating this on "not already in FZ_DONE"
# would silently expect the OLD, un-promoted behavior again.
FZ_REQ="$FZ_REQ $rc"
fz_dfs "$rc"
done
EXPECTED_ALL="$FZ_DONE"
# prune's %tag closure marks the FIRST lexicographically-sorted
# installed provider of each tag (any installed provider satisfies
# the tag), not necessarily the install-time pick -- model that
# exactly, over the final installed set.
kk=1
while [ "$kk" -le "$FZ_NTAG" ]; do
eval "pl=\$FZ_TAGPROVS_$kk"
firstp=""
for pi in $pl; do
fz_canon_of "$pi"
c=$REPLY
# only INSTALLED providers count for prune's closure
case " $FZ_DONE " in
*" $c "*) ;;
*) continue ;;
esac
if [ "$firstp" = "" ]; then
firstp="$c"
elif [ "$c" \< "$firstp" ]; then
firstp="$c"
fi
done
eval "FZ_RTAGPROV_$kk=\"$firstp\""
kk=$(( kk + 1 ))
done
# runtime closure: plain set reachability over FZ_RDEPS.
fz_runtime_closure() {
queue="$ROOT_CANONS"
seen=""
while [ "$queue" != "" ]; do
cur=$(echo "$queue" | cut -d' ' -f1)
queue=$(echo "$queue" | cut -d' ' -f2-)
[ "$queue" = "$cur" ] && queue=""
case " $seen " in
*" $cur "*) continue ;;
esac
seen="$seen $cur"
fz_idx_of "$cur"
idx=$REPLY
case "$idx" in
''|*[!0-9]*) continue ;;
esac
[ "$idx" -ge 1 ] 2>/dev/null || continue
[ "$idx" -le "$FZ_NP" ] 2>/dev/null || continue
eval "ds=\$FZ_RDEPS_$idx"
for tok in $ds; do
[ "$tok" = "" ] && continue
fz_tok_target "$tok" "$idx" 1
target=$REPLY
if [ "$target" != "" ]; then
case " $queue $seen " in
*" $target "*) ;;
*) queue="$queue $target" ;;
esac
fi
done
done
EXPECTED_RUNTIME="$seen"
}
fz_runtime_closure
rm -f "/tmp/fz${FUZZ_I}-hooks.log"
# --- conflict mode: preinstall target, root must fail cleanly ----
if [ "$CONFLICT_TARGET" != "" ]; then
mp install "$CONFLICT_TARGET" >/dev/null 2>&1
out=$(mp install "$CONFLICT_ROOT" 2>&1)
rc=$?
if [ "$rc" -eq 0 ]; then
echo "conflict mode: expected failure, got success (out: $out)"
return 1
fi
case "$out" in
*"err: "*) ;;
*) echo "conflict mode: no clean err: (out: $out)"; return 1 ;;
esac
while IFS= read -r line; do
[ -z "$line" ] && continue
case "$line" in
*"$TAB"*"$TAB"*"$TAB"*"$TAB"*) echo "conflict mode: db line has >3 fields: $line"; return 1 ;;
esac
done < "$WD/db"
return 0
fi
# --- the install ------------------------------------------------
out=$(mp install $ROOTS 2>&1)
rc=$?
if [ "$rc" -ne 0 ]; then
echo "install failed (rc=$rc): $out"
jj=1
while [ "$jj" -le "$FZ_NP" ]; do
eval "c=\$FZ_CANON_$jj"
eval "d=\$FZ_DEPS_$jj"
echo "gen p$jj canon=$c deps=[$d]"
jj=$(( jj + 1 ))
done
echo "gen roots=[$ROOTS] mode=$FZ_MODE"
return 1
fi
# --- db shape ----------------------------------------------------
while IFS= read -r line; do
[ -z "$line" ] && continue
case "$line" in
*"$TAB"*"$TAB"*"$TAB"*"$TAB"*) echo "db line has >3 fields: $line"; return 1 ;;
esac
done < "$WD/db"
actual=$(cut -f1 "$WD/db" | sort)
expected=$(for x in $EXPECTED_ALL; do echo "$x"; done | sort)
if [ "$actual" != "$expected" ]; then
echo "closure mismatch: expected=[$expected] actual=[$actual]"
jj=1
while [ "$jj" -le "$FZ_NP" ]; do
eval "c=\$FZ_CANON_$jj"
eval "d=\$FZ_DEPS_$jj"
eval "e=\$FZ_EN_$jj"
echo "gen p$jj canon=$c deps=[$d] en=[$e]"
jj=$(( jj + 1 ))
done
echo "gen roots=[$ROOTS] mode=$FZ_MODE"
return 1
fi
while IFS= read -r line; do
[ -z "$line" ] && continue
name=$(echo "$line" | cut -f1)
req=$(echo "$line" | cut -f4)
case " $FZ_REQ " in
*" $name "*)
[ "$req" = "1" ] || { echo "root $name requested=$req"; return 1; }
;;
*)
[ "$req" = "0" ] || { echo "dep $name requested=$req"; return 1; }
;;
esac
done < "$WD/db"
# --- markers (slot prefixes respected) --------------------------
for x in $EXPECTED_ALL; do
base=${x%%:*}
suf=${x#*:}
[ "$suf" = "$x" ] && suf=""
if [ "$suf" = "" ]; then
[ -f "$MP_PREFIX/$base-installed" ] || { echo "marker missing for $x"; return 1; }
else
[ -f "$MP_PREFIX/$base-$suf/$base-installed" ] || { echo "slot marker missing for $x"; return 1; }
fi
done
# --- hooks: exact multiset + pipeline order ----------------------
# pre lines (per-package config hook) fire once for their own port;
# HOOKS_DIR files are GLOBAL per phase, so each generated dir file
# fires once for EVERY installed package; the pre_build pipeline
# fires once per installed package that has a build phase.
explog=""
for x in $EXPECTED_ALL; do
fz_idx_of "$x"
idx=$REPLY
eval "hp=\$FZ_HOOKPRE_$idx"
[ "$hp" = "1" ] && explog="$explog pre$idx"
if [ "$FZ_PIPELINE" -eq 1 ] && [ $(( idx % 3 )) -eq 0 ]; then
for L in $PIPE_ORDER; do
explog="$explog pipe$L"
done
fi
done
jj=1
while [ "$jj" -le "$FZ_NP" ]; do
eval "hd=\$FZ_HOOKDIR_$jj"
if [ "$hd" = "1" ]; then
for x in $EXPECTED_ALL; do
explog="$explog dir$jj"
done
fi
jj=$(( jj + 1 ))
done
explog_sorted=$(for x in $explog; do echo "$x"; done | sort)
actlog_sorted=$(sort "/tmp/fz${FUZZ_I}-hooks.log" 2>/dev/null)
if [ "$explog_sorted" != "$actlog_sorted" ]; then
echo "hook multiset mismatch: expected=[$explog_sorted] actual=[$actlog_sorted]"
return 1
fi
case "$explog" in
*pipe*)
first=$(echo "$PIPE_ORDER" | cut -d' ' -f1)
second=$(echo "$PIPE_ORDER" | cut -d' ' -f2)
pos1=$(grep -n "pipe$first" "/tmp/fz${FUZZ_I}-hooks.log" 2>/dev/null | head -1 | cut -d: -f1)
pos2=$(grep -n "pipe$second" "/tmp/fz${FUZZ_I}-hooks.log" 2>/dev/null | head -1 | cut -d: -f1)
if [ -z "$pos1" ] || [ -z "$pos2" ] || [ "$pos1" -ge "$pos2" ]; then
echo "pipeline order wrong ($first=$pos1 $second=$pos2)"
return 1
fi
;;
esac
# --- patches: gate expectations ---------------------------------
for x in $EXPECTED_ALL; do
fz_idx_of "$x"
idx=$REPLY
eval "hp2=\$FZ_PATCH_$idx"
[ "$hp2" -eq 1 ] || continue
dummy=$(cat "$WD/$x/fz${FUZZ_I}p$idx/dummy" 2>/dev/null)
case "$dummy" in
*uncond*) ;;
*) echo "patch 001 missing for $x"; return 1 ;;
esac
eval "en=\$FZ_EN_$idx"
case "$en" in
*" ssl"*)
case "$dummy" in
*sslpatch*) ;;
*) echo "ssl patch missing for $x"; return 1 ;;
esac
;;
*)
case "$dummy" in
*sslpatch*) echo "ssl patch wrongly applied for $x"; return 1 ;;
esac
;;
esac
done
# --- provider choice: lowest pref --------------------------------
if [ "$FZ_MODE" -ge 20 ] && [ "$FZ_MODE" -lt 45 ]; then
eval "want=\$FZ_TAGPROV_1"
case " $EXPECTED_ALL " in
*" $want "*) ;;
*)
echo "expected provider $want not installed"
jj=1
while [ "$jj" -le "$FZ_NP" ]; do
eval "c=\$FZ_CANON_$jj"
eval "d=\$FZ_DEPS_$jj"
echo "gen p$jj canon=$c deps=[$d]"
jj=$(( jj + 1 ))
done
echo "gen roots=[$ROOTS] expected=[$EXPECTED_ALL]"
return 1
;;
esac
if [ "$want" != "$PROV2" ]; then
case " $EXPECTED_ALL " in
*" $PROV2 "*) echo "wrong provider installed: $PROV2"; return 1 ;;
esac
fi
fi
# --- prune: exact equality with the runtime closure --------------
dry=$(mp prune --dry-run 2>&1)
prunable=""
for x in $EXPECTED_ALL; do
case " $EXPECTED_RUNTIME " in
*" $x "*) continue ;;
esac
case " $ROOT_CANONS " in
*" $x "*) continue ;;
esac
prunable="$prunable $x"
done
expected_p=$(for x in $prunable; do echo "would remove: $x"; done | sort)
actual_p=$(echo "$dry" | grep '^would remove:' | sort)
if [ "$expected_p" != "$actual_p" ]; then
echo "prune mismatch: expected=[$expected_p] actual=[$actual_p]"
return 1
fi
# --- random removal ----------------------------------------------
candidates=""
for x in $EXPECTED_ALL; do
case " $PROVIDERS " in
*" $x "*) continue ;;
esac
candidates="$candidates $x"
done
if [ "$candidates" != "" ]; then
n=0
for x in $candidates; do n=$(( n + 1 )); done
fz_rnd "$n"
pick=$(( REPLY + 1 ))
target=$(for x in $candidates; do echo "$x"; done | sed -n "${pick}p")
mp remove --force "$target" >/dev/null 2>&1
if grep -q "^$target$TAB" "$WD/db"; then
echo "remove failed for $target"
return 1
fi
base=${target%%:*}
suf=${target#*:}
[ "$suf" = "$target" ] && suf=""
if [ "$suf" = "" ]; then
[ -f "$MP_PREFIX/$base-installed" ] && { echo "marker survived removal of $target"; return 1; }
else
[ -f "$MP_PREFIX/$base-$suf/$base-installed" ] && { echo "slot marker survived removal of $target"; return 1; }
fi
fi
return 0
}
# the whole batch is one reported test (per-iteration diagnostics name
# the failing iteration), so 1000 iterations don't spam 1000 lines.
test_fuzz_batch() {
t_begin "fuzz_${FUZZ_ITERATIONS}_iterations_seed_${FUZZ_SEED}"
reset_state
FUZZ_I=1
while [ "$FUZZ_I" -le "$FUZZ_ITERATIONS" ]; do
ferr=$(fz_iteration "$FUZZ_I" 2>&1)
frc=$?
if [ "$frc" -ne 0 ]; then
t_fail "iteration $FUZZ_I (FUZZ_SEED=$FUZZ_SEED): $ferr"
echo "state left at iteration $FUZZ_I for inspection"
return
fi
if [ $(( FUZZ_I % 100 )) -eq 0 ]; then
echo " [fuzz] $FUZZ_I/$FUZZ_ITERATIONS iterations ok"
fi
FUZZ_I=$(( FUZZ_I + 1 ))
done
t_pass
}
run_fuzz_suite() {
test_fuzz_batch
}