#!/usr/bin/env bash
# Pruebas públicas del P2 (planificador por lotería en xv6-riscv).
#
#   ./test-p2.sh <directorio-xv6>
#
# Trabaja sobre una copia del árbol (no modifica el original):
#   1. copia tickettest.c, lotterytest.c (a user/) y pstat.h (a kernel/),
#   2. agrega _tickettest y _lotterytest a UPROGS si faltan,
#   3. compila y arranca xv6 sin interfaz gráfica con CPUS=1,
#   4. corre tickettest y lotterytest y revisa su salida.
#
# Si el compilador de RISC-V o QEMU no están instalados, el script se vuelve
# a ejecutar solo dentro del contenedor del curso (imagen sistemas-operativos).
#
# Variables opcionales:
#   WINDOW=300     duración de la medición de lotterytest, en ticks (~0.1 s c/u)
#   TOL=100        tolerancia de cada proporción, en milésimas (100 = ±10 %)
#   USERTESTS=1    además corre "usertests -q" (tarda varios minutos)
#   IMAGE=sistemas-operativos  imagen de Docker a usar

set -u

WINDOW=${WINDOW:-300}
TOL=${TOL:-100}
USERTESTS=${USERTESTS:-0}
IMAGE=${IMAGE:-sistemas-operativos}

if [ $# -ne 1 ] || [ ! -d "$1" ]; then
    echo "uso: $0 <directorio-xv6>" >&2
    exit 2
fi
SRC="$(cd "$1" && pwd)"
HERE="$(cd "$(dirname "$0")" && pwd)"

# ¿Estamos fuera del contenedor? Entonces volver a lanzar adentro.
if ! command -v qemu-system-riscv64 >/dev/null 2>&1 ||
   ! command -v timeout >/dev/null 2>&1; then
    if command -v docker >/dev/null 2>&1; then
        if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
            echo "error: no existe la imagen $IMAGE; constrúyanla con env/run.sh" >&2
            exit 2
        fi
        exec docker run --rm \
            -e WINDOW="$WINDOW" -e TOL="$TOL" -e USERTESTS="$USERTESTS" \
            -v "$SRC":/student:ro -v "$HERE":/p2:ro \
            "$IMAGE" bash /p2/test-p2.sh /student
    fi
    echo "error: no se encontró qemu-system-riscv64 ni docker" >&2
    exit 2
fi

for f in tickettest.c lotterytest.c pstat.h; do
    if [ ! -f "$HERE/$f" ]; then
        echo "error: falta $HERE/$f" >&2
        exit 2
    fi
done
if [ ! -f "$SRC/Makefile" ] || [ ! -f "$SRC/kernel/proc.c" ]; then
    echo "error: $SRC no parece un árbol de xv6-riscv" >&2
    exit 2
fi

WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
cp -a "$SRC/." "$WORK/"
cd "$WORK" || exit 2

PASS=0
FAIL=0
ok()   { echo "  PASS  $1"; PASS=$((PASS + 1)); }
bad()  { echo "  FAIL  $1"; FAIL=$((FAIL + 1)); }
summary() {
    echo
    echo "Resultado: $PASS pasaron, $FAIL fallaron"
    [ "$FAIL" -eq 0 ] && exit 0 || exit 1
}

# 1-2. Archivos provistos y UPROGS.
cp "$HERE/tickettest.c" "$HERE/lotterytest.c" user/
cp "$HERE/pstat.h" kernel/pstat.h
for prog in tickettest lotterytest; do
    if ! grep -q "_${prog}\b" Makefile; then
        sed -i "/^UPROGS=/a\\\t\$U/_${prog}\\\\" Makefile
    fi
done

# 3. Compilación.
echo "== Compilación"
make clean >/dev/null 2>&1
if make CPUS=1 kernel/kernel fs.img >build.log 2>&1; then
    ok "xv6 compila con tickettest y lotterytest"
else
    bad "xv6 no compila; últimas líneas:"
    grep -v "^riscv64" build.log | tail -n 25 | sed 's/^/        /'
    summary
fi

# Arranque de QEMU. La entrada va por un FIFO para escribir comandos
# solo cuando la salida muestra que xv6 está listo.
TOTAL_TIMEOUT=$((WINDOW / 5 + 180))
[ "$USERTESTS" = 1 ] && TOTAL_TIMEOUT=$((TOTAL_TIMEOUT + 1200))
mkfifo qemu.in
timeout "$TOTAL_TIMEOUT" make CPUS=1 qemu <qemu.in >qemu.log 2>&1 &
QPID=$!
exec 3>qemu.in

# wait_for <texto> <segundos>: espera a que aparezca el texto en la salida.
wait_for() {
    local i=0
    while [ "$i" -lt "$2" ]; do
        grep -qF -- "$1" qemu.log && return 0
        grep -q "^panic" qemu.log && return 1
        kill -0 "$QPID" 2>/dev/null || return 1
        sleep 1
        i=$((i + 1))
    done
    return 1
}
send() { printf '%s\n' "$1" >&3; }
stop_qemu() {
    printf '\001x' >&3 2>/dev/null   # Ctrl-A x: salir de QEMU
    exec 3>&-
    wait "$QPID" 2>/dev/null
}
show_log() {
    echo "        --- últimas líneas de la consola de xv6 ---"
    tr -d '\r' <qemu.log | tail -n "${1:-25}" | sed 's/^/        /'
}

echo "== Arranque"
if wait_for "init: starting sh" 60; then
    ok "xv6 arranca y llega a la shell"
else
    bad "xv6 no llegó a la shell"
    show_log
    stop_qemu
    summary
fi
sleep 1

# 4a. tickettest
echo "== tickettest (settickets y getpinfo)"
send tickettest
if wait_for "tickettest: done" 60; then
    tr -d '\r' <qemu.log | grep -E "^tickettest: .*: (ok|FAIL)$" |
        sed -e 's/^tickettest: \(.*\): ok$/  PASS  \1/' \
            -e 's/^tickettest: \(.*\): FAIL$/  FAIL  \1/'
    n_ok=$(tr -d '\r' <qemu.log | grep -cE "^tickettest: .*: ok$")
    n_bad=$(tr -d '\r' <qemu.log | grep -cE "^tickettest: .*: FAIL$")
    PASS=$((PASS + n_ok))
    FAIL=$((FAIL + n_bad))
    [ "$n_ok" -eq 0 ] && bad "tickettest no reportó ninguna verificación"
else
    bad "tickettest no terminó (¿panic o cuelgue?)"
    show_log
    stop_qemu
    summary
fi

# 4b. lotterytest
echo "== lotterytest (proporción 30:20:10, ventana de $WINDOW ticks)"
send "lotterytest $WINDOW"
if ! wait_for "lotterytest: done" $((WINDOW / 5 + 60)); then
    bad "lotterytest no terminó (¿panic o cuelgue?)"
    show_log
    stop_qemu
    summary
fi
LOG="$(tr -d '\r' <qemu.log)"
if echo "$LOG" | grep -q "^lotterytest: FAIL"; then
    echo "$LOG" | grep "^lotterytest: FAIL" | sed 's/^/        /'
    bad "lotterytest reportó un error"
else
    # Ticks medidos para 30, 20 y 10 tickets.
    a=$(echo "$LOG" | sed -n 's/^lotterytest: tickets 30 ticks \([0-9-]*\)$/\1/p')
    b=$(echo "$LOG" | sed -n 's/^lotterytest: tickets 20 ticks \([0-9-]*\)$/\1/p')
    c=$(echo "$LOG" | sed -n 's/^lotterytest: tickets 10 ticks \([0-9-]*\)$/\1/p')
    if [ -z "$a" ] || [ -z "$b" ] || [ -z "$c" ]; then
        bad "no se pudo leer la salida de lotterytest"
        show_log
    else
        total=$((a + b + c))
        echo "        ticks medidos: 30 -> $a, 20 -> $b, 10 -> $c (total $total)"
        if [ "$total" -ge $((WINDOW / 2)) ]; then
            ok "los hijos acumulan ticks (total $total >= $((WINDOW / 2)))"
        else
            bad "muy pocos ticks contados (total $total, se esperaban >= $((WINDOW / 2)))"
        fi
        if [ "$total" -gt 0 ]; then
            # Proporción de cada hijo en milésimas frente a lo esperado.
            for pair in "30 $a 500" "20 $b 333" "10 $c 167"; do
                set -- $pair
                got=$(($2 * 1000 / total))
                diff=$((got - $3))
                [ "$diff" -lt 0 ] && diff=$((-diff))
                if [ "$diff" -le "$TOL" ]; then
                    ok "proporción con $1 tickets: $got/1000 (esperado $3 ± $TOL)"
                else
                    bad "proporción con $1 tickets: $got/1000 (esperado $3 ± $TOL)"
                fi
            done
        fi
        if [ "$a" -gt "$b" ] && [ "$b" -gt "$c" ]; then
            ok "más tickets implica más CPU ($a > $b > $c)"
        else
            bad "el orden no es 30 > 20 > 10 ($a, $b, $c)"
        fi
    fi
fi

# Opcional: usertests -q, para comprobar que no se rompió nada.
if [ "$USERTESTS" = 1 ]; then
    echo "== usertests -q (puede tardar varios minutos)"
    send "usertests -q"
    for _ in $(seq 1 1200); do
        grep -qE "ALL TESTS PASSED|SOME TESTS FAILED|^panic" qemu.log && break
        kill -0 "$QPID" 2>/dev/null || break
        sleep 1
    done
    if grep -q "ALL TESTS PASSED" qemu.log; then
        ok "usertests -q"
    else
        bad "usertests -q"
        show_log 15
    fi
fi

stop_qemu
summary
