diff options
Diffstat (limited to 'test-logic.sh')
| -rw-r--r-- | test-logic.sh | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/test-logic.sh b/test-logic.sh new file mode 100644 index 0000000..da50513 --- /dev/null +++ b/test-logic.sh @@ -0,0 +1,227 @@ +#!/bin/bash +# +# Copyright (C) 2026 Danilo M. <danix@danix.xyz> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# +# Logic self-check for test-build. Covers the pure, host-side parts: +# dependency resolution, override application, unknown-dep -> UNMET, cache +# decisions, and BLOCKED-BY-DEP propagation. No docker. +# +# Run: bash test-logic.sh +# +set -uo pipefail + +SCRIPT="$(dirname "$0")/test-build" +T=$(mktemp -d) +BASE_DB=$(mktemp -d); mkdir -p "$BASE_DB" + +cleanup() { rm -rf "$T" "$BASE_DB"; } +trap cleanup EXIT + +# Source the script without running main(). Sourcing re-runs the CONFIG block, +# so set test vars AFTER the source. +LIB=$(mktemp) +sed '/^main "\$@"$/d' "$SCRIPT" > "$LIB" +# shellcheck disable=SC1090 +source "$LIB" 2>/dev/null +rm -f "$LIB" + +pass=0; fail=0 +ok() { echo " ok: $1"; ((pass++)); return 0; } +bad() { echo " FAIL: $1"; ((fail++)); return 0; } + +# Placeholder assertion so this file runs before any logic exists. +ok "script sources without executing main" + +# --- version selection ------------------------------------------------------ +SBO_TREE_CURRENT="/trees/current" +SBO_TREE_STABLE="/trees/stable" +IMAGE_CURRENT="sbo-testbuild:current" +IMAGE_STABLE="sbo-testbuild:15.0" + +VERSION_ID="current"; select_version_paths +[[ "$ACTIVE_TREE" == "/trees/current" ]] && ok "current -> current tree" || bad "current tree wrong: [$ACTIVE_TREE]" +[[ "$ACTIVE_IMAGE" == "sbo-testbuild:current" ]] && ok "current -> current image" || bad "current image wrong: [$ACTIVE_IMAGE]" + +VERSION_ID="15.0"; select_version_paths +[[ "$ACTIVE_TREE" == "/trees/stable" ]] && ok "15.0 -> stable tree" || bad "stable tree wrong: [$ACTIVE_TREE]" +[[ "$ACTIVE_IMAGE" == "sbo-testbuild:15.0" ]] && ok "15.0 -> stable image" || bad "stable image wrong: [$ACTIVE_IMAGE]" + +# --- resolution ------------------------------------------------------------- +# Fake SBo tree under one category. mk <prog> "<REQUIRES>". +mk() { mkdir -p "$T/cat/$1"; echo "REQUIRES=\"$2\"" > "$T/cat/$1/$1.info"; } +mk c "" +mk b "c" +mk a "b %README%" +mk d "nonexistentpkg" +mk e "f" +mk f "e" +mk g "b" + +SBO_TREE_ROOTS=("$T") # resolver reads this global (set by select in real runs) + +resolve_target "$T/cat/a" +order=""; for x in "${RESOLVED_ORDER[@]}"; do order+="$(basename "$x") "; done +order="${order% }" +[[ "$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_dir (target from CWD/path, NOT the SBo tree) ------------- +# Local package dir, separate from the fake SBo tree above. +LOCALREPO=$(mktemp -d); mkdir -p "$LOCALREPO/mypkg" +echo 'REQUIRES=""' > "$LOCALREPO/mypkg/mypkg.info" +# 1. absolute path +got="$(resolve_target_dir "$LOCALREPO/mypkg" 2>/dev/null)" +[[ "$got" == "$LOCALREPO/mypkg" ]] && ok "target: absolute path" || bad "abs path got [$got]" +# 2. bare name resolved under CWD (./mypkg/) +got="$(cd "$LOCALREPO" && resolve_target_dir "mypkg" 2>/dev/null)" +[[ "$got" == "$LOCALREPO/mypkg" ]] && ok "target: bare name in CWD" || bad "bare-in-cwd got [$got]" +# 3. CWD itself is the package +got="$(cd "$LOCALREPO/mypkg" && resolve_target_dir "mypkg" 2>/dev/null)" +[[ "$got" == "$LOCALREPO/mypkg" ]] && ok "target: CWD is the package" || bad "cwd-is-pkg got [$got]" +# 4. missing -> failure +resolve_target_dir "nosuchpkg" >/dev/null 2>&1 && bad "missing target should fail" || ok "target: missing fails" +rm -rf "$LOCALREPO" + +resolve_target "$T/cat/e" +[[ ${#CYCLES[@]} -ge 1 ]] && ok "cycle caught" || bad "cycle missed" + +# --- installed_in_base ------------------------------------------------------ +# Pre-seed the base pkg set (TB_BASE_LOADED=1 bypasses docker). Names are the +# bare package name; installed_in_base must match a REQUIRES token against it. +TB_BASE_LOADED=1 +TB_BASE_PKGS=([python3]=1 [libfoo]=1) +installed_in_base python3 && ok "base pkg matched" || bad "base pkg not matched" +installed_in_base notthere && bad "absent pkg wrongly matched" || ok "absent pkg not matched" + +# a dep present in the base resolves as met, not UNMET +mk h "python3" +resolve_target "$T/cat/h" +[[ ${#UNMET[@]} -eq 0 ]] && ok "base-provided dep not unmet" || bad "base dep flagged unmet" +TB_BASE_LOADED=0; TB_BASE_PKGS=() + +# --- overrides -------------------------------------------------------------- +VERSION_ID="current" # reset: left at 15.0 by the version-selection checks above +OV=$(mktemp) +cat > "$OV" <<'EOF' +# comment ignored +drop: dropme +rename: oldname -> newname +fetch: fetchme +EOF +TB_OVERRIDES="$OV" +load_overrides + +[[ "${OV_DROP[dropme]:-}" == "1" ]] && ok "override drop parsed" || bad "drop not parsed" +[[ "${OV_RENAME[oldname]:-}" == "newname" ]] && ok "override rename parsed" || bad "rename not parsed" +[[ "${OV_FETCH[fetchme]:-}" == "1" ]] && ok "override fetch parsed" || bad "fetch not parsed" + +# rename maps a token +[[ "$(apply_rename oldname)" == "newname" ]] && ok "apply_rename maps" || bad "apply_rename wrong: [$(apply_rename oldname)]" +[[ "$(apply_rename untouched)" == "untouched" ]] && ok "apply_rename passthrough" || bad "apply_rename mangled untouched" + +# drop satisfies an external (no-dir) dep during resolution: not UNMET, absent +# from the order. mk k requires a dropme that has no dir in the tree. +OV_DROP=([extdrop]=1) +mk k "extdrop" +resolve_target "$T/cat/k" +[[ ${#UNMET[@]} -eq 0 ]] && ok "drop satisfies external dep (not unmet)" || bad "dropped external dep still unmet" +seen=""; for x in "${RESOLVED_ORDER[@]}"; do seen+="$(basename "$x") "; done +[[ "$seen" != *extdrop* ]] && ok "dropped dep absent from order" || bad "dropped dep in order: [$seen]" +load_overrides # restore OV_DROP[dropme] for the order-filter test below + +# drop filters the order (build a fake order of dirs; dropme is removed) +mk dropme "" +mk keepme "" +RESOLVED_ORDER=("$T/cat/dropme" "$T/cat/keepme") +apply_overrides_to_order +kept=""; for x in "${RESOLVED_ORDER[@]}"; do kept+="$(basename "$x") "; done +kept="${kept% }" +[[ "$kept" == "keepme" ]] && ok "drop removes from order" || bad "drop failed, order=[$kept]" + +# all entries dropped -> RESOLVED_ORDER must be truly empty (not one "" element) +OV_DROP=([dropme]=1 [keepme]=1) +RESOLVED_ORDER=("$T/cat/dropme" "$T/cat/keepme") +apply_overrides_to_order +[[ ${#RESOLVED_ORDER[@]} -eq 0 ]] && ok "all-dropped -> empty order" || bad "all-dropped left ${#RESOLVED_ORDER[@]} elem(s): [${RESOLVED_ORDER[*]}]" +OV_DROP=() +rm -f "$OV" + +# overrides only apply on current; on 15.0 they are a no-op +VERSION_ID="15.0" +TB_OVERRIDES="$OV" # file gone; must not error, must clear maps +load_overrides +[[ ${#OV_DROP[@]} -eq 0 ]] && ok "overrides inert on 15.0" || bad "overrides applied on 15.0" +VERSION_ID="current" + +# --- cache ------------------------------------------------------------------ +PKG_CACHE=$(mktemp -d) +CACHE_ROOT="$PKG_CACHE/sha256-deadbeef" # simulate a resolved digest namespace +mkc() { mkdir -p "$CACHE_ROOT/$1/$2"; : > "$CACHE_ROOT/$1/$2/$3"; } + +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 got [$(cache_decision net libfoo 1.1)]" +[[ "$(cache_decision net libfoo 1.2)" == "bump:1.1:1.2" ]] && ok "cache bump reported" || bad "cache bump got [$(cache_decision net libfoo 1.2)]" +[[ "$(cache_decision net libbar 1.0)" == "new" ]] && ok "cache new for absent" || bad "cache new got [$(cache_decision net libbar 1.0)]" + +hit="$(cache_path net libfoo 1.1)" +[[ "$hit" == "$CACHE_ROOT/net/libfoo/libfoo-1.1-x86_64-1_danix.txz" ]] && ok "cache_path returns hit" || bad "cache_path got [$hit]" +[[ -z "$(cache_path net libfoo 9.9)" ]] && ok "cache_path empty on miss" || bad "cache_path not empty on miss" + +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 "$CACHE_ROOT/net/libfoo" -name '*.t?z' | wc -l) +[[ "$count" -eq 1 ]] && ok "cache_store evicts to one file" || bad "cache_store left $count files" +[[ -e "$CACHE_ROOT/net/libfoo/libfoo-1.2-x86_64-1_danix.txz" ]] && ok "cache_store stored new file" || bad "cache_store did not store" +rm -rf "$srctmp" + +# disabled cache -> new +CACHE_ROOT_SAVE="$CACHE_ROOT"; USE_CACHE=0 +[[ "$(cache_decision net libfoo 1.2)" == "new" ]] && ok "--no-cache disables (new)" || bad "disabled cache got [$(cache_decision net libfoo 1.2)]" +USE_CACHE=1; CACHE_ROOT="$CACHE_ROOT_SAVE" + +# resolve_cache_root prunes stale digests of the SAME variant, keeps the other. +# No docker here, so the digest falls back to the tag-derived name; seed sibling +# dirs and confirm only the active-variant orphan is removed. +PRUNE_CACHE=$(mktemp -d) +PKG_CACHE="$PRUNE_CACHE"; ACTIVE_IMAGE="sbo-testbuild:current"; VERSION_ID="current" +mkdir -p "$PRUNE_CACHE/current-oldstale" "$PRUNE_CACHE/15.0-keepme" +resolve_cache_root +[[ -d "$CACHE_ROOT" ]] && ok "cache root created" || bad "cache root missing" +[[ ! -e "$PRUNE_CACHE/current-oldstale" ]] && ok "prune drops stale same-variant digest" || bad "stale current digest survived" +[[ -d "$PRUNE_CACHE/15.0-keepme" ]] && ok "prune keeps other-variant cache" || bad "15.0 cache wrongly pruned" +rm -rf "$PRUNE_CACHE" +VERSION_ID="current"; CACHE_ROOT="$CACHE_ROOT_SAVE" + +# --- BLOCKED-BY-DEP (depends_on_failed) ------------------------------------- +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)"; else bad "a should block on b"; fi + +dead=(c) +if depends_on_failed "$T/cat/a" dead; then bad "a wrongly blocked on c"; else ok "a not directly blocked by c"; fi +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"; else ok "no block when nothing dead"; fi + +dead=("%README%") +if depends_on_failed "$T/cat/a" dead; then bad "%README% treated as dep"; else ok "%README% not treated as dep"; fi + +echo +echo "$pass passed, $fail failed" +[[ $fail -eq 0 ]] || exit 1 +echo "ALL LOGIC CHECKS PASS" |
