Files
takana/scripts/upgrade-e2e-test.sh
Sergio 476168bb07 takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.

El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.

Y el hallazgo caro:  casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a  y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.

Además 14 rutas de módulo  en docs, que el barrido anterior no tocó
porque  no es frontera de palabra.
2026-09-09 19:28:48 +00:00

129 lines
5.4 KiB
Bash
Executable File

#!/bin/sh
# E2E de la Etapa E4 (upgrades atómicos con rollback) contra el binario `takana` REAL.
#
# Monta un store sintético con dos árboles "producto" (v1, v2), un root FHS vivo, y ejercita:
# apply v1 → apply v2 → rollback → rollback, verificando el estado del root en cada paso.
# Todo en host, sin VM: valida el cableado del CLI + la semántica generación/rollback de takana-upgrade.
#
# Uso: scripts/upgrade-e2e-test.sh
set -eu
ROOT_DIR="$(CDPATH= cd "$(dirname "$0")/.." && pwd)"
WORK="$(mktemp -d)"
trap 'chmod -R u+w "$WORK" 2>/dev/null || true; rm -rf "$WORK"' EXIT
STORE="$WORK/store"
ROOT="$WORK/root"
STATE="$WORK/state"
JOURNAL="$WORK/journal"
mkdir -p "$STORE" "$ROOT/usr/bin" "$ROOT/etc"
echo "==> compilando hammer"
cargo build -q -p takana-cli --manifest-path "$ROOT_DIR/Cargo.toml"
HAMMER="$ROOT_DIR/target/debug/hammer"
up() { "$HAMMER" --store "$STORE" upgrade --root "$ROOT" --state-root "$STATE" --journal "$JOURNAL" "$@"; }
# --- estado pristino del root (lo que el rollback final debe restaurar) ---
printf 'PRISTINE-ls' > "$ROOT/usr/bin/ls"
printf 'motd original\n' > "$ROOT/etc/motd"
# --- sella dos árboles producto sintéticos en el store (vía `takana build`? no: usamos un mini-store
# a mano replicando la forma <64hex>-<name>; el of_tree lo computa takana al aplicar) ---
seal_tree() {
# $1=hex64 $2=name $3=builder-fn
dir="$STORE/$1-$2"
rm -rf "$dir"; mkdir -p "$dir"
"$3" "$dir"
chmod -R a-w "$dir" # el store sella read-only; replicamos para fidelidad
}
hex() { printf '%s' "$1"; i=${#1}; while [ "$i" -lt 64 ]; do printf '0'; i=$((i+1)); done; }
build_v1() {
mkdir -p "$1/usr/bin" "$1/bin"
printf 'LS-v1' > "$1/usr/bin/ls"
printf 'OLDtool'> "$1/usr/bin/oldtool"
ln -s busybox "$1/bin/sh"
}
build_v2() {
mkdir -p "$1/usr/bin" "$1/bin"
printf 'LS-v2' > "$1/usr/bin/ls"
printf 'NEWtool'> "$1/usr/bin/newtool" # oldtool desaparece
ln -s busybox "$1/bin/sh"
}
seal_tree "$(hex aa11)" product-rootfs build_v1
seal_tree "$(hex bb22)" product-rootfs build_v2
V1="$(hex aa11)-product-rootfs"
V2="$(hex bb22)-product-rootfs"
assert() { # $1=path $2=expected-content $3=label
got="$(cat "$1" 2>/dev/null || echo '<ausente>')"
if [ "$got" != "$2" ]; then echo "FALLO [$3]: $1 = '$got', esperaba '$2'" >&2; exit 1; fi
echo " ok [$3] $1 = $2"
}
assert_absent() { [ ! -e "$1" ] || { echo "FALLO [$2]: $1 debería estar ausente" >&2; exit 1; }; echo " ok [$2] $1 ausente"; }
echo "==> apply v1"
up apply "$V1"
assert "$ROOT/usr/bin/ls" "LS-v1" "v1"
assert "$ROOT/usr/bin/oldtool" "OLDtool" "v1"
[ -L "$ROOT/bin/sh" ] || { echo "FALLO: bin/sh no es symlink" >&2; exit 1; }
echo "==> apply v2 (pisa ls, añade newtool, RETIRA oldtool)"
up apply "$V2"
assert "$ROOT/usr/bin/ls" "LS-v2" "v2"
assert "$ROOT/usr/bin/newtool" "NEWtool" "v2"
assert_absent "$ROOT/usr/bin/oldtool" "v2"
echo "==> status"
up status
echo "==> rollback (v2 → v1: ls vuelve, newtool se borra, oldtool se restaura)"
up rollback
assert "$ROOT/usr/bin/ls" "LS-v1" "rb→v1"
assert "$ROOT/usr/bin/oldtool" "OLDtool" "rb→v1"
assert_absent "$ROOT/usr/bin/newtool" "rb→v1"
echo "==> rollback (v1 → pristino: el root vuelve EXACTO al estado pre-upgrades)"
up rollback
assert "$ROOT/usr/bin/ls" "PRISTINE-ls" "rb→pristino"
assert "$ROOT/etc/motd" "$(printf 'motd original\n')" "rb→pristino"
assert_absent "$ROOT/usr/bin/oldtool" "rb→pristino"
echo "==> idempotencia: re-apply v1 sobre pristino vuelve a generar, status coherente"
up apply "$V1" >/dev/null
up apply "$V1" # mismo árbol ⇒ no-op
echo "==> diario: registró cambios"
LINES="$(wc -l < "$JOURNAL/mutations.jsonl")"
[ "$LINES" -gt 0 ] || { echo "FALLO: diario vacío" >&2; exit 1; }
echo " ok: $LINES eventos en el diario"
echo "==> prune: tras los dos rollback completos las gens 1 y 2 quedaron huérfanas (current=3)"
GENS_ANTES="$(ls "$STATE/generations" | wc -l)"
up prune
GENS_DESPUES="$(ls "$STATE/generations" | wc -l)"
[ "$GENS_ANTES" -gt "$GENS_DESPUES" ] || { echo "FALLO: prune no borró huérfanas ($GENS_ANTES$GENS_DESPUES)" >&2; exit 1; }
echo " ok: prune $GENS_ANTES$GENS_DESPUES generación(es)"
# el árbol vivo (gen 3 = v1) sigue intacto tras el prune.
assert "$ROOT/usr/bin/ls" "LS-v1" "post-prune"
echo "==> recover: simulamos un apply interrumpido plantando un pending.json y lo recuperamos"
# El recover real se prueba a fondo en los tests unitarios (corte a media proyección). Acá ejercitamos
# el CABLEADO del CLI contra artefactos reales: plantamos el manifiesto de la gen viva como intento
# pendiente; status debe avisar y recover (roll-forward, idempotente) completarlo y limpiarlo.
CUR=$(cat "$STATE/current")
cp "$STATE/generations/$CUR/manifest.json" "$STATE/pending.json"
up status | grep -q "apply interrumpido pendiente" || { echo "FALLO: status no avisó del intento" >&2; exit 1; }
echo " ok: status avisó del intento pendiente"
up recover | grep -q "COMPLETADO" || { echo "FALLO: recover no completó" >&2; exit 1; }
[ ! -e "$STATE/pending.json" ] || { echo "FALLO: recover no limpió el intento" >&2; exit 1; }
assert "$ROOT/usr/bin/ls" "LS-v1" "post-recover"
echo " ok: recover completó el intento y lo limpió"
# segundo recover = no-op.
up recover | grep -q "nada que recuperar" || { echo "FALLO: recover no es no-op sin intento" >&2; exit 1; }
echo " ok: recover sin intento es no-op"
echo
echo "✓✓ E4 upgrade E2E VERDE: apply/rollback atómicos + restauración exacta + idempotencia + diario + prune + recover"