#!/bin/bash # # Logic self-check for sbo-batch-test. Covers the pure, VM-independent parts: # dependency resolution (topo order, %README%, unmet-dep, cycles) and # BLOCKED-BY-DEP propagation (depends_on_failed). No overlay, no chroot. # # Run: bash test-logic.sh # set -uo pipefail SCRIPT="$(dirname "$0")/sbo-batch-test" T=$(mktemp -d) BASE=$(mktemp -d); mkdir -p "$BASE/var/log/packages" cleanup() { rm -rf "$T" "$BASE"; } trap cleanup EXIT # Fake SBo tree under one category. mk "". mk() { mkdir -p "$T/cat/$1"; echo "REQUIRES=\"$2\"" > "$T/cat/$1/$1.info"; } # Graph: # c (no deps) # b -> c # a -> b, %README% # d -> nonexistentpkg (unmet) # e -> f, f -> e (cycle) # g -> b (for blocked-by-dep: if b fails, g blocks) mk c "" mk b "c" mk a "b %README%" mk d "nonexistentpkg" mk e "f" mk f "e" mk g "b" # Source the script without running main(), then override config AFTER the # source (sourcing re-runs the CONFIG block, which would clobber test vars). LIB=$(mktemp) sed '/^main "\$@"$/d' "$SCRIPT" > "$LIB" # shellcheck disable=SC1090 source "$LIB" 2>/dev/null rm -f "$LIB" SBO_TREE_ROOTS=("$T") SLACKWARE_BASE="$BASE" pass=0; fail=0 ok() { echo " ok: $1"; ((pass++)); return 0; } bad() { echo " FAIL: $1"; ((fail++)); return 0; } # --- resolution ------------------------------------------------------------- resolve_target "$T/cat/a" order=""; for x in "${RESOLVED_ORDER[@]}"; do order+="$(basename "$x") "; done order="${order% }" # trim trailing space [[ "$order" == "c b a" ]] && ok "topo order c b a" || bad "topo order, got: [$order]" [[ "${HAS_README[$T/cat/a]:-}" == "1" ]] && ok "%README% recorded" || bad "%README% not recorded" [[ ${#UNMET[@]} -eq 0 ]] && ok "no false unmet" || bad "unexpected unmet" resolve_target "$T/cat/d" [[ ${#UNMET[@]} -eq 1 ]] && ok "unmet-dep caught" || bad "unmet-dep missed" resolve_target "$T/cat/e" [[ ${#CYCLES[@]} -ge 1 ]] && ok "cycle caught" || bad "cycle missed" # --- BLOCKED-BY-DEP (depends_on_failed) ------------------------------------- # depends_on_failed # returns 0 if the dir directly REQUIRES any prog in the dead list. dead=(b) if depends_on_failed "$T/cat/g" dead; then ok "g blocked when b dead"; else bad "g should block on b"; fi if depends_on_failed "$T/cat/a" dead; then ok "a blocked when b dead (direct dep)"; else bad "a should block on b"; fi dead=(c) # a does NOT directly require c (a->b->c). One-hop check must say no here. if depends_on_failed "$T/cat/a" dead; then bad "a wrongly blocked on c (not a direct dep)"; else ok "a not directly blocked by c"; fi # but b DOES directly require c. if depends_on_failed "$T/cat/b" dead; then ok "b blocked when c dead"; else bad "b should block on c"; fi dead=() if depends_on_failed "$T/cat/a" dead; then bad "a blocked with empty dead list"; else ok "no block when nothing dead"; fi # %README% token in REQUIRES must not be treated as a dead dep. dead=("%README%") if depends_on_failed "$T/cat/a" dead; then bad "%README% treated as dep"; else ok "%README% not treated as dep"; fi # Propagation invariant note: depends_on_failed is a DIRECT-requires check only. # Transitive blocking works because run_target iterates in topo order and adds # each blocked package's own prog name to `dead`, so the failure of c blocks b # (direct), then b's name enters `dead`, which then blocks a (direct on b). # Simulate that one-hop cascade here: dead=(c) # topo order for a is: c b a. c "fails" -> seed dead=(c). chain=(c b a) declare -A blocked=() for p in "${chain[@]}"; do [[ "$p" == "c" ]] && continue # c is the original failure, already in dead if depends_on_failed "$T/cat/$p" dead; then blocked[$p]=1 dead+=("$p") # mark dependents-of-this as blockable next hop fi done if [[ "${blocked[b]:-}" == "1" && "${blocked[a]:-}" == "1" ]]; then ok "transitive cascade c->b->a via one-hop propagation" else bad "cascade dead: b=${blocked[b]:-0} a=${blocked[a]:-0}" fi # --- package cache ---------------------------------------------------------- PKG_CACHE=$(mktemp -d) mkc() { mkdir -p "$PKG_CACHE/$1/$2"; : > "$PKG_CACHE/$1/$2/$3"; } # exact version match -> cached mkc net libfoo "libfoo-1.1-x86_64-1_danix.txz" [[ "$(cache_decision net libfoo 1.1)" == "cached" ]] && ok "cache hit on version match" || bad "cache_decision should be cached, got [$(cache_decision net libfoo 1.1)]" # different version cached -> bump:old:new [[ "$(cache_decision net libfoo 1.2)" == "bump:1.1:1.2" ]] && ok "cache bump reported" || bad "cache_decision bump wrong, got [$(cache_decision net libfoo 1.2)]" # nothing cached -> new [[ "$(cache_decision net libbar 1.0)" == "new" ]] && ok "cache new for absent prog" || bad "cache_decision should be new, got [$(cache_decision net libbar 1.0)]" # empty PKG_CACHE disables -> new PKG_CACHE_SAVE="$PKG_CACHE"; PKG_CACHE="" [[ "$(cache_decision net libfoo 1.1)" == "new" ]] && ok "empty PKG_CACHE disables (new)" || bad "disabled cache should be new, got [$(cache_decision net libfoo 1.1)]" PKG_CACHE="$PKG_CACHE_SAVE" # cache_path returns the hit file for a matching version hit="$(cache_path net libfoo 1.1)" [[ "$hit" == "$PKG_CACHE/net/libfoo/libfoo-1.1-x86_64-1_danix.txz" ]] && ok "cache_path returns hit" || bad "cache_path wrong, got [$hit]" # cache_path empty when version does not match [[ -z "$(cache_path net libfoo 9.9)" ]] && ok "cache_path empty on miss" || bad "cache_path should be empty on miss" # cache_store evicts: prog dir holds exactly the new file srctmp=$(mktemp -d); : > "$srctmp/libfoo-1.2-x86_64-1_danix.txz" cache_store net libfoo "$srctmp/libfoo-1.2-x86_64-1_danix.txz" count=$(find "$PKG_CACHE/net/libfoo" -name '*.t?z' | wc -l) [[ "$count" -eq 1 ]] && ok "cache_store evicts to one file" || bad "cache_store left $count files" [[ -e "$PKG_CACHE/net/libfoo/libfoo-1.2-x86_64-1_danix.txz" ]] && ok "cache_store stored new file" || bad "cache_store did not store new file" rm -rf "$srctmp" # version_of reads VERSION from .info mkdir -p "$T/cat/verpkg" printf 'PRGNAM="verpkg"\nVERSION="3.4.5"\nREQUIRES=""\n' > "$T/cat/verpkg/verpkg.info" [[ "$(version_of "$T/cat/verpkg")" == "3.4.5" ]] && ok "version_of reads VERSION" || bad "version_of wrong, got [$(version_of "$T/cat/verpkg")]" # --- result ----------------------------------------------------------------- echo echo "$pass passed, $fail failed" [[ $fail -eq 0 ]] || exit 1 echo "ALL LOGIC CHECKS PASS"