Files

198 lines
7.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Barrido de compatibilidad componente <-> core real (change verify-core-compat).
# Monta un proyecto temporal por paquete contra el core real publicado, lo
# ejercita segun su nivel de evidencia (tests/compat/levels.json) con criterios
# estrictos (cualquier Deprecated/Warning/Notice/Fatal cuenta FAIL) y escribe
# tests/compat/report.json.
#
# Uso: scripts/compat-sweep [--only <paquete> ...]
# Env: DUCKBRAIN_CORE_URL repositorio del core (default: Config::coreUrl())
# DUCKBRAIN_CACHE_DIR caché donde CoreProvider mantiene el clon del core
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMPAT="$ROOT/tests/compat"
LEVELS="$COMPAT/levels.json"
REPORT="$COMPAT/report.json"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
ONLY=()
args=("$@")
i=0
while [ "$i" -lt "${#args[@]}" ]; do
case "${args[i]}" in
--only) i=$((i + 1)); ONLY+=("${args[i]}") ;;
*) echo "Uso: scripts/compat-sweep [--only <paquete> ...]" >&2; exit 2 ;;
esac
i=$((i + 1))
done
# --- resolver el core real (clon + checkout del mayor tag semver) --------------
CORE_INFO="$(php -d error_reporting=-1 -r '
require "'"$ROOT"'/src/autoload.php";
try {
$p = Toolbox\CoreProvider::fromUrl(
Toolbox\Config::coreUrl(), Toolbox\Config::cacheDir(), new Toolbox\Console()
);
$v = $p->version();
if ($v === null) {
fwrite(STDERR, "El core no tiene tags semver publicados; nada que verificar.\n");
exit(1);
}
echo $p->dir(), "\n", $v, "\n", (string) $p->commit(), "\n";
} catch (Throwable $e) {
fwrite(STDERR, "No se pudo acceder al core real: " . $e->getMessage() . "\n");
exit(1);
}' 2>"$WORK/core.err")" || {
echo "== ERROR: barrido abortado ==" >&2
cat "$WORK/core.err" >&2
exit 1
}
CORE_DIR="$(printf '%s\n' "$CORE_INFO" | sed -n 1p)"
CORE_VER="$(printf '%s\n' "$CORE_INFO" | sed -n 2p)"
CORE_SHA="$(printf '%s\n' "$CORE_INFO" | sed -n 3p)"
echo "== core real: v$CORE_VER ($CORE_SHA) en $CORE_DIR =="
echo
LEVELS_PATH="$LEVELS" php -r '
$levels = json_decode((string) file_get_contents(getenv("LEVELS_PATH")), true);
echo implode("\n", array_keys($levels)), "\n";' > "$WORK/pkgs"
RESULTS="$WORK/results"
: > "$RESULTS"
FAILED=0
wanted() {
[ "${#ONLY[@]}" -eq 0 ] && return 0
local w
for w in "${ONLY[@]}"; do [ "$w" = "$1" ] && return 0; done
return 1
}
stop_server() {
if [ -n "${SERVER_PID:-}" ]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
SERVER_PID=""
fi
}
# --- por paquete ---------------------------------------------------------------
while read -r PKG; do
wanted "$PKG" || continue
PROJ="$WORK/proj-$PKG"
LOG="$WORK/$PKG.server.log"
echo "── $PKG"
php -d error_reporting=-1 "$ROOT/bin/duckbrain" install "$PROJ" --core-dir="$CORE_DIR" --yes >/dev/null
php -d error_reporting=-1 "$ROOT/bin/duckbrain" add "$PKG" --root="$PROJ" --yes >/dev/null \
|| { echo "$PKG:add|fail" >> "$RESULTS"; echo " add fallo"; FAILED=1; continue; }
# Config del proyecto temporal: sqlite, clave para crypto y db local.
PROJ_PATH="$PROJ" php -r '
$proj = getenv("PROJ_PATH");
$f = "$proj/config.php";
$cfg = (string) file_get_contents($f);
$cfg = str_replace("define(\x27DB_TYPE\x27, \x27mysql\x27);", "define(\x27DB_TYPE\x27, \x27sqlite\x27);", $cfg);
$cfg = str_replace("define(\x27DB_NAME\x27, \x27\x27);", "define(\x27DB_NAME\x27, \x27$proj/app.sqlite\x27);", $cfg);
if (!str_contains($cfg, "PRIVATE_KEY")) {
$cfg .= "\ndefine(\x27PRIVATE_KEY\x27, \x27compat-sweep-clave-de-prueba-32!\x27);\n";
}
file_put_contents($f, $cfg);'
# Router de compatibilidad del fixture (si lo hay).
if [ -f "$COMPAT/$PKG/router.php" ]; then
mkdir -p "$PROJ/src/Routers"
sed "s|@COMPAT@|$COMPAT/$PKG|g" "$COMPAT/$PKG/router.php" > "$PROJ/src/Routers/Compat.php"
fi
# Servidor con diagnósticos al máximo y capturados.
BASE=""
: > "$LOG"
for PORT in $(seq 18100 18120); do
PHP_CLI_SERVER_WORKERS=4 php -d error_reporting=-1 -d display_errors=1 \
-d log_errors=1 -d error_log="$LOG" -S "127.0.0.1:$PORT" -t "$PROJ" "$PROJ/index.php" \
>/dev/null 2>&1 &
SERVER_PID=$!
for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
sleep 0.15
if curl -s -o /dev/null "http://127.0.0.1:$PORT/compat/ping"; then
BASE="http://127.0.0.1:$PORT"
break 2
fi
done
stop_server
done
if [ -z "$BASE" ]; then
echo " no hubo servidor en 18100-18120" >&2
echo "$PKG:server|fail" >> "$RESULTS"; FAILED=1; continue
fi
# Aserciones del fixture (si lo hay; los niveles "none" no tienen).
if [ -f "$COMPAT/$PKG/assert.sh" ]; then
BASE="$BASE" PROJ="$PROJ" PKG="$PKG" LOG="$LOG" RESULTS="$RESULTS" COMPAT="$COMPAT" \
bash "$COMPAT/$PKG/assert.sh" || true
fi
stop_server
# Criterio estricto: cualquier diagnostico en el log del servidor es fallo.
if grep -qE 'Deprecated|Warning:|Notice:|Fatal error|Parse error' "$LOG"; then
echo " diagnostico PHP detectado en el servidor (ver $LOG):"
grep -E 'Deprecated|Warning:|Notice:|Fatal error|Parse error' "$LOG" | sed 's/^/ /'
FAILED=1
fi
done < "$WORK/pkgs"
# --- merge de resultados con levels.json y reporte ------------------------------
LEVELS_PATH="$LEVELS" RESULTS_PATH="$RESULTS" REPORT_PATH="$REPORT" \
CORE_VER="$CORE_VER" CORE_SHA="$CORE_SHA" php -r '
$levels = json_decode((string) file_get_contents(getenv("LEVELS_PATH")), true);
$results = [];
foreach (file(getenv("RESULTS_PATH"), FILE_IGNORE_NEW_LINES) as $line) {
[$id, $status] = array_pad(explode("|", $line, 2), 2, "");
$results[$id] = $status;
}
// Corre parciales (--only): hereda del reporte previo los ok del mismo core.
$prior = is_file(getenv("REPORT_PATH"))
? (json_decode((string) file_get_contents(getenv("REPORT_PATH")), true) ?: [])
: [];
$stale = ($prior["core"]["version"] ?? null) !== getenv("CORE_VER");
$out = [];
$fail = 0;
foreach ($levels as $pkg => $comps) {
foreach ($comps as $comp => $level) {
$reason = null;
if (is_array($level)) { $reason = $level["reason"] ?? null; $level = $level["level"]; }
$id = "$pkg:$comp";
$status = $results[$id] ?? ($level === "none" ? "skipped" : "not_attempted");
if ($status === "not_attempted" && !$stale && ($prior["components"][$id]["status"] ?? "") === "ok") {
$status = "ok";
}
if (!in_array($status, ["ok", "skipped"], true)) { $fail++; }
$out[$id] = array_filter([
"status" => $status,
"level" => $level,
"reason" => $reason,
], fn ($v) => $v !== null);
}
}
$report = [
"core" => ["version" => getenv("CORE_VER"), "commit" => getenv("CORE_SHA")],
"generated" => gmdate("Y-m-d\TH:i:s\Z"),
"components" => $out,
];
file_put_contents(getenv("REPORT_PATH"), json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n");
foreach ($out as $id => $r) {
printf(" %-24s %-13s %s%s\n", $id, $r["status"], $r["level"], isset($r["reason"]) ? " (" . $r["reason"] . ")" : "");
}
exit($fail === 0 ? 0 : 1);'
STATUS=$?
if [ "$STATUS" = 0 ]; then
echo "OK: barrido de compatibilidad completo contra core v$CORE_VER"
else
echo "FAIL: hay componentes sin verificar o con fallos (ver $REPORT)" >&2
fi
exit $STATUS