Patrick Schurig e5c649e69a test(décisions): horloge murale figée au démarrage de chaque processus (brief 4 T0.5)
Constaté le 2026-10-16 : sur un code inchangé, la vérification échouait une heure après la
génération de la référence. testTheLearningLogIsFedByTheCycle date ses entrées sur
QDateTime::currentDateTimeUtc() (l'heure courante plus trois jours) : la référence ne valait
que dans l'heure UTC où elle avait été produite.

L'enregistreur définit clock_gettime : avec ETM_DECISIONS_EPOCH, CLOCK_REALTIME part de cet
instant au premier appel puis avance au rythme réel ; les horloges monotones (QTimer,
QElapsedTimer) ne sont pas touchées. Le lanceur pose 2026-10-16T05:00:00Z, l'heure où la
référence a été générée : la référence commitée n'est pas régénérée, elle est reproduite à
l'octet (md5 ea2c1db7…, 23 739 lignes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:18:09 +02:00

96 lines
4.4 KiB
Bash
Executable File

#!/bin/bash
# Décisions de référence (brief 4 T0.5) — rejoue les scénarios de `simulation` et compare, cycle
# par cycle, ce que l'arbitre a décidé au fichier de référence commité.
#
# run-decisions.sh <build> record <sortie.ndjson> enregistre seulement
# run-decisions.sh <build> check enregistre puis compare à la référence
# (code 1 au premier écart, et il est montré)
#
# <build> : arbre construit depuis le .pro racine avec CONFIG+=build_tests (le binaire
# `nymea-energy-decisions` y est sous tests/auto/decisions/).
#
# AGENTS.md : UN PROCESSUS PAR TEST, chacun sous MemoryMax=2G. Les tests ne tournent jamais en
# parallèle (le harnais nymea ouvre des ports fixes). Un test qui sort en erreur est rejoué UNE
# fois — instabilité connue du port 4847 — et le rejeu est annoncé, jamais masqué.
set -u
ICI="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REFERENCE="$ICI/reference/decisions.ndjson.xz"
BUILD="$(cd "${1:?build}" && pwd)"
MODE="${2:?record|check}"
BIN_DIR="$BUILD/tests/auto/decisions"
BIN="$BIN_DIR/nymea-energy-decisions"
[ -x "$BIN" ] || { echo "binaire absent : $BIN" >&2; exit 2; }
case "$MODE" in
record) SORTIE="${3:?sortie}" ;;
check) SORTIE="$(mktemp --suffix=.ndjson)" ;;
*) echo "mode inconnu : $MODE" >&2; exit 2 ;;
esac
TRAVAIL="$(mktemp -d)"
trap 'rm -rf "$TRAVAIL"' EXIT
# L'HORLOGE MURALE de chaque processus part de cet instant (voir decisionrecorder.cpp) : des
# scénarios datent leurs entrées sur l'heure courante, et la référence doit valoir à toute heure.
# 2026-10-16T05:00:00Z, début d'heure : un test qui arrondit à l'heure a 60 min de marge.
EPOQUE=1792126800
export NYMEA_PLUGINS_PATH="$BUILD/tests/mocks/plugins/energymocks:/usr/lib/x86_64-linux-gnu/nymea/plugins"
# UN /tmp PRIVÉ ET VIDE PAR PROCESSUS. libnymea range l'état du serveur de test sous
# `/tmp/nymea-test`, en dur : configuration des charges, energy.conf, magasin de prévision. Ce
# répertoire est partagé par toutes les exécutions de la machine et il s'accumule — mesuré le
# 2026-10-16, un energy-load-configuration.json de 348 bornes orphelines laissé là le 14. Sans
# isolation, le résultat dépendrait de l'historique de la machine, pas du code.
lancer() { # $1 = fonction, $2 = fichier NDJSON du test
rm -f "$2"
( cd "$BIN_DIR" && ETM_DECISIONS_OUT="$2" ETM_DECISIONS_EPOCH="$EPOQUE" \
systemd-run --user --scope -q -p MemoryMax=2G -p MemorySwapMax=0 \
bwrap --dev-bind / / --tmpfs /tmp --bind "$BUILD" "$BUILD" --bind "$TRAVAIL" "$TRAVAIL" \
--chdir "$BIN_DIR" \
./target_wrapper.sh ./nymea-energy-decisions "$1" > "$TRAVAIL/$1.log" 2>&1 )
}
mapfile -t FONCTIONS < <(cd "$BIN_DIR" && ./nymea-energy-decisions -functions 2>/dev/null \
| sed -n 's/^\([A-Za-z_][A-Za-z0-9_]*\)()$/\1/p')
[ "${#FONCTIONS[@]}" -gt 0 ] || { echo "aucune fonction listée" >&2; exit 2; }
: > "$SORTIE"
total=0
for f in "${FONCTIONS[@]}"; do
out="$TRAVAIL/$f.ndjson"
lancer "$f" "$out"; code=$?
if [ "$code" -ne 0 ]; then
echo "REJEU $f (code $code) — $(grep -m1 -E 'FAIL!' "$TRAVAIL/$f.log" | cut -c1-160)" >&2
lancer "$f" "$out"; code=$?
fi
n=0; [ -f "$out" ] && { n=$(wc -l < "$out"); cat "$out" >> "$SORTIE"; }
total=$((total + n))
resume=$(grep -m1 '^Totals:' "$TRAVAIL/$f.log")
printf '{"fn":"%s","fin":true,"exit":%d,"cycles":%d}\n' "$f" "$code" "$n" >> "$SORTIE"
printf '%-48s exit=%d cycles=%-6d %s\n' "$f" "$code" "$n" "$resume" >&2
done
# Le contrôle ne doit pas pouvoir disparaître : un enregistreur débranché (wrap perdu, binaire
# reconstruit sans lui) produirait un fichier sans cycle, que la comparaison verrait comme un
# écart massif — mais on le dit ici en clair.
[ "$total" -gt 0 ] || { echo "AUCUN cycle enregistré — l'enregistreur n'est pas branché" >&2; exit 2; }
echo "fonctions=${#FONCTIONS[@]} cycles=$total" >&2
[ "$MODE" = record ] && exit 0
ATTENDU="$TRAVAIL/attendu.ndjson"
xz -dc "$REFERENCE" > "$ATTENDU" || exit 2
if cmp -s "$ATTENDU" "$SORTIE"; then
echo "IDENTIQUE à la référence ($(wc -l < "$ATTENDU") lignes)" >&2
rm -f "$SORTIE"
exit 0
fi
echo "ÉCART avec la référence — premières lignes divergentes (- attendu, + obtenu) :" >&2
diff "$ATTENDU" "$SORTIE" | head -c 6000 >&2
echo >&2
echo "fichier obtenu conservé : $SORTIE" >&2
exit 1