jq n'est plus utilisé sur le PC (ADR 0004) : tests/test_reference.sh se relance dans l'image ustagammon/build-c:2, qui contient aussi Python 3 pour le comparateur du moteur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
5.2 KiB
Bash
121 lines
5.2 KiB
Bash
#!/usr/bin/env bash
|
|
# Vérifie la cohérence des positions de référence (tests/reference/) :
|
|
# JSON valide, au moins 30 positions, chaque catégorie présente, un résultat
|
|
# par position aux niveaux 0 et 2 plis avec tous les champs du format
|
|
# (voir tests/reference/README.md). Ne lance pas gnubg : jq tourne dans
|
|
# l'image de la chaîne C (ADR 0004), dans laquelle le script se relance.
|
|
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
|
|
if [ -z "${USTAGAMMON_IN_DOCKER:-}" ]; then
|
|
image="${BUILD_C_IMAGE:?lancer via make test}"
|
|
docker image inspect "$image" >/dev/null 2>&1 \
|
|
|| { echo "Chaîne C absente : lancer d'abord make setup"; exit 1; }
|
|
exec docker run --rm --user "$(id -u):$(id -g)" -e USTAGAMMON_IN_DOCKER=1 \
|
|
-v "$PWD:/src:ro" -w /src "$image" bash tests/test_reference.sh
|
|
fi
|
|
|
|
cd tests/reference
|
|
|
|
erreurs=0
|
|
signaler() {
|
|
echo "$1"
|
|
erreurs=$((erreurs + 1))
|
|
}
|
|
|
|
jq empty positions.json || { echo "positions.json : JSON invalide"; exit 1; }
|
|
|
|
nombre="$(jq '.positions | length' positions.json)"
|
|
[ "$nombre" -ge 30 ] || signaler "positions.json : $nombre positions, 30 au minimum"
|
|
|
|
doublons="$(jq -r '[.positions[].id] | group_by(.) | map(select(length > 1)[0]) | join(" ")' positions.json)"
|
|
[ -z "$doublons" ] || signaler "positions.json : identifiants en double : $doublons"
|
|
|
|
for categorie in opening middlegame prime blitz race bearoff \
|
|
cube_no_double cube_double_take cube_double_pass match crawford jacoby; do
|
|
jq -e --arg c "$categorie" 'any(.positions[]; .categories | index($c))' positions.json >/dev/null \
|
|
|| signaler "positions.json : aucune position de catégorie « $categorie »"
|
|
done
|
|
|
|
# Champs obligatoires de chaque position d'entrée.
|
|
invalides="$(jq -r '.positions[]
|
|
| select((.id | type) != "string"
|
|
or (.type | IN("move", "cube") | not)
|
|
or (.position_id | test("^[A-Za-z0-9+/]{14}$") | not)
|
|
or (.match_id | test("^[A-Za-z0-9+/]{12}$") | not)
|
|
or (.categories | type) != "array"
|
|
or (.description | type) != "string"
|
|
or (if .type == "move" then (.dice | type) != "array" else .dice != null end))
|
|
| "positions.json : position \(.id) incomplète ou invalide"' positions.json)"
|
|
[ -z "$invalides" ] || signaler "$invalides"
|
|
|
|
# Filtre jq : liste les défauts d'un fichier de résultat (vide si conforme).
|
|
read -r -d '' controle <<'JQ' || true
|
|
def nombre: type == "number";
|
|
def probas: (keys == ["lose_backgammon", "lose_gammon", "win", "win_backgammon", "win_gammon"])
|
|
and all(.[]; nombre and . >= 0 and . <= 1);
|
|
def defauts($pos):
|
|
(if .id != $pos.id or .type != $pos.type or .position_id != $pos.position_id
|
|
or .match_id != $pos.match_id or .categories != $pos.categories
|
|
then "en-tête différent de positions.json" else empty end),
|
|
(if (.gnubg_commit | test("^[0-9a-f]{40}$")) and .met == "Kazaross-XG2" then empty
|
|
else "gnubg_commit ou met invalide" end),
|
|
(.context | if (.dice == $pos.dice) and (.match_length | nombre)
|
|
and (.score_player | nombre) and (.score_opponent | nombre)
|
|
and (.cube_value | nombre) and (.cube_owner | IN("centered", "player", "opponent"))
|
|
and ([.crawford, .jacoby, .beavers] | all(type == "boolean"))
|
|
then empty else "context incomplet" end),
|
|
(if (.plies | keys) == ["0", "2"] then empty else "niveaux attendus : 0 et 2" end),
|
|
(.plies | to_entries[] | .key as $n | .value |
|
|
(if (.evaluation.probabilities | probas) and (.evaluation.equity | nombre)
|
|
then empty else "niveau \($n) : evaluation incomplète" end),
|
|
(if $pos.type == "move" then
|
|
(.moves | if (.legal_move_count | nombre)
|
|
and (.ranking | length) == ([.legal_move_count, 10] | min)
|
|
and all(.ranking[]; (.rank | nombre) and (.move | type) == "string"
|
|
and (.ply | nombre) and (.equity | nombre) and (.equity_diff | nombre)
|
|
and (.probabilities | probas))
|
|
then empty else "niveau \($n) : classement des coups incomplet" end)
|
|
else
|
|
(.cube | if ([.no_double, .double_take, .double_pass, .optimal] | all(nombre))
|
|
and (.decision | type) == "string" and (.decision_text | type) == "string"
|
|
then empty else "niveau \($n) : décision de videau incomplète" end)
|
|
end));
|
|
defauts($pos)
|
|
JQ
|
|
|
|
# Décision de videau attendue à 2 plis selon la catégorie.
|
|
read -r -d '' decision <<'JQ' || true
|
|
.categories as $c | .plies["2"].cube.decision as $d
|
|
| if ($c | index("cube_no_double")) and ($d | test("^NO_?(RE)?DOUBLE_") | not) then "pas de double attendu, gnubg dit \($d)"
|
|
elif ($c | index("cube_double_take")) and ($d | IN("DOUBLE_TAKE", "REDOUBLE_TAKE") | not) then "double/prend attendu, gnubg dit \($d)"
|
|
elif ($c | index("cube_double_pass")) and ($d | IN("DOUBLE_PASS", "REDOUBLE_PASS") | not) then "double/passe attendu, gnubg dit \($d)"
|
|
else empty end
|
|
JQ
|
|
|
|
mapfile -t ids < <(jq -r '.positions[].id' positions.json)
|
|
for id in "${ids[@]}"; do
|
|
f="results/$id.json"
|
|
if [ ! -f "$f" ]; then
|
|
signaler "$f absent : lancer make reference"
|
|
continue
|
|
fi
|
|
if ! jq empty "$f" 2>/dev/null; then
|
|
signaler "$f : JSON invalide"
|
|
continue
|
|
fi
|
|
pos="$(jq --arg id "$id" '.positions[] | select(.id == $id)' positions.json)"
|
|
while read -r defaut; do
|
|
[ -n "$defaut" ] && signaler "$f : $defaut"
|
|
done < <(jq -r --argjson pos "$pos" "$controle" "$f"; jq -r "$decision" "$f")
|
|
done
|
|
|
|
for f in results/*.json; do
|
|
[ -e "$f" ] || continue
|
|
id="$(basename "$f" .json)"
|
|
printf '%s\n' "${ids[@]}" | grep -qxF "$id" || signaler "$f : position absente de positions.json"
|
|
done
|
|
|
|
[ "$erreurs" -eq 0 ]
|