Preuve « sans endpoint = identique au bit près », posée AVANT tout refactoring. `nymea-energy-decisions` est la suite `simulation` compilée à l'identique, plus un enregistreur branché à l'édition de liens (`--wrap`) : aucune ligne de la suite ni du moteur n'est modifiée. Chaque cycle (`simulationCallUpdate`) écrit une ligne NDJSON : télémétrie de l'arbitre (actions du créneau dans l'ordre, motifs, niveaux, budget, soutirage, consigne posée), actions laissées au proxy, état des Things. Les scénarios qui interrogent un `RuleBasedScheduler` local voient leur `Plan` sérialisé champ par champ. Exclus : `command.since` et les compteurs `totalEnergy*` du mock (horloge murale), les ThingId (alias par rang dans le créneau, à chaque ligne : deux bornes identiques reçoivent leur rang provisionné dans l'ordre d'un QHash). `planId` n'est pas lu. Référence générée sur 83483cc (1.15.2+etm69) : 66 fonctions, 23 673 cycles + 36 plans directs, deux exécutions identiques à l'octet. Chaque processus a un /tmp privé : libnymea range l'état de test sous /tmp/nymea-test, partagé et cumulatif (348 bornes orphelines trouvées dans energy-load-configuration.json). tests/auto/decisions/run-decisions.sh <build> check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
4.0 KiB
Bash
Executable File
91 lines
4.0 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
|
|
|
|
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" \
|
|
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
|