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.
129 lines
5.4 KiB
Bash
Executable File
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"
|