gestion_table_tournante_libre/src/moteur/empreintes.test.js
Mathieu Benoit c445df3dc8 [ADD] engine tests: reference fingerprints of today's plans and ranking
The coming configurable order must leave every default plan and ranking
unchanged at equal seed. Fingerprints of the plans of the four shipped
demos, of the acceptance switch, and of a twelve-proposal ranking where
each criterion is the first to split some neighbouring pair are written
once on the previous revision and verified by every run; rewriting them
needs GTT_EMPREINTES=ecrire, any other value is refused by name.
Checked: 20 node tests, 3 long; named mutations of the order caught.

--- FR ---

[ADD] épreuves du moteur : empreintes des plans et du classement actuels

L'ordre réglable à venir doit laisser chaque plan et chaque classement par
défaut inchangés à graine égale. Les empreintes des plans des quatre
démonstrations, de la bascule d'acceptation et d'un classement de douze
propositions où chaque critère départage une paire voisine sont écrites
une fois sur la révision précédente et vérifiées à chaque exécution ;
les récrire exige GTT_EMPREINTES=ecrire, toute autre valeur est refusée.
Vérifié : 20 épreuves node, 3 longues ; mutations nommées de l'ordre prises.

Assisted-by: Claude Opus 5.5
2026-10-11 13:45:29 -04:00

180 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Empreintes de référence du moteur, série node (§ 5.7, § 14.10, § 15.5) : la
// petite démonstration et sa variante au conflit inévitable, sous les trois
// graines et le compte réduit de test/empreintes_moteur.js, et la petite sous
// chacune des seize combinaisons des quatre cases ; les deux encore sous le
// compte plus long de la section bascule, où l'ordre d'acceptation qui suit
// l'annulation de l'écart laisse sa trace dans le plan. Chaque plan rendu par
// rechercher se compare, empreinte et grandeurs nommées, au fichier versionné
// test/fixtures/moteur/empreintes_defaut.json : une empreinte qui change est
// un défaut, jamais une régénération. Le fichier ne se récrit que sous
// GTT_EMPREINTES=ecrire, par cette seule épreuve, qui recalcule alors toutes
// les sections — la grande et le classement compris — avant de les relire.
// La grande et sa variante sans exception se vérifient dans la série
// node-long (empreintes.long.test.js), le classement par evaluer dans
// src/application/empreintes.test.js.
import assert from 'node:assert/strict';
import { describe, test } from '../../test/lanceur.js';
import {
ARRET,
BASCULE,
CASES,
CLASSEMENT,
GRAINES,
GRANDEURS,
combinaisons,
differences,
ecrireEmpreintes,
empreinteDe,
empreinteDuPlan,
empreintesDesCombinaisons,
empreintesDesPlans,
lireEmpreintes,
modeEmpreintes,
} from '../../test/empreintes_moteur.js';
import { CATALOGUE } from '../demo/catalogue.js';
import { indexerPlan, normaliser } from './configuration.js';
import { HISTORIQUE_PAR_DEFAUT, rechercher } from './recherche.js';
if (modeEmpreintes(process.env) === 'ecrire') ecrireEmpreintes();
const REFERENCE = lireEmpreintes();
// Les configurations de cette série, dans l'ordre du catalogue.
const PETITES = ['petite', 'petite-conflit'];
describe('empreintes de référence : la petite démonstration et sa variante (§ 14.10)', () => {
for (const cle of PETITES) {
test(`${cle} : les plans des graines ${GRAINES.join(', ')}, à ${ARRET} mouvements, sont ceux de la référence`, () => {
const calcule = empreintesDesPlans(cle);
assert.deepEqual(differences(cle, calcule, REFERENCE.plans[cle]), []);
assert.deepEqual(calcule, REFERENCE.plans[cle], cle);
});
}
test('petite : sous chacune des seize combinaisons des quatre cases, graine 1, le plan est celui de la référence', () => {
const calcule = empreintesDesCombinaisons();
assert.equal(Object.keys(REFERENCE.combinaisons).length, 16);
assert.deepEqual(differences('petite', calcule, REFERENCE.combinaisons), []);
assert.deepEqual(calcule, REFERENCE.combinaisons);
});
for (const cle of BASCULE.cles) {
test(`${cle} : à ${BASCULE.arret} mouvements, où la meilleure affectation s’améliore après la bascule, les plans sont ceux de la référence`, () => {
const calcule = empreintesDesPlans(cle, BASCULE.arret);
assert.deepEqual(differences(`${cle} (bascule)`, calcule, REFERENCE.bascule[cle]), []);
assert.deepEqual(calcule, REFERENCE.bascule[cle], cle);
});
}
test('la référence a été relevée sous les réglages de test/empreintes_moteur.js', () => {
assert.deepEqual(REFERENCE.reglages, {
graines: [...GRAINES],
arret: ARRET,
historique: HISTORIQUE_PAR_DEFAUT,
cases: [...CASES],
classement: { ...CLASSEMENT, cles: [...CLASSEMENT.cles] },
bascule: { ...BASCULE, cles: [...BASCULE.cles] },
});
});
test('la référence couvre les quatre configurations du catalogue, chacune sous chaque graine', () => {
assert.deepEqual(
Object.keys(REFERENCE.plans),
CATALOGUE.map(({ cle }) => cle),
);
for (const { cle } of CATALOGUE) {
assert.deepEqual(Object.keys(REFERENCE.plans[cle]), GRAINES.map(String), cle);
}
});
});
describe('empreintes de référence : ce que porte une empreinte', () => {
// Un plan de la petite variante et son instance, la configuration du
// catalogue sous la graine 1.
function unPlan() {
const configuration = CATALOGUE.find(({ cle }) => cle === 'petite-conflit').construire();
const [{ plan }] = rechercher(configuration, { graine: 1, arret: 2_000, nombre: 1 });
return { instance: normaliser(configuration), plan };
}
test('les grandeurs nommées sont les six du § 14.10, dans cet ordre, et aucune composante du n-uplet', () => {
assert.deepEqual(GRANDEURS, [
'ecartAuPlafondAPrioriMax',
'excedentCollisions',
'collisionsCumulees',
'rencontresRepeteesChoisies',
'retoursChoisis',
'redondance',
]);
const { instance, plan } = unPlan();
assert.deepEqual(Object.keys(empreinteDuPlan(instance, plan).grandeurs), GRANDEURS);
});
test('deux plans qui ne diffèrent que par la table d’une personne à un tour ont deux empreintes', () => {
const { instance, plan } = unPlan();
const avant = empreinteDuPlan(instance, plan);
// La première personne des deux premières tables du tour 1 échange sa
// place : les grandeurs peuvent rester égales, le plan ne l'est pas.
const tours = plan.tours.map((tour) => tour.map((liste) => [...liste]));
const [a] = tours[0][0];
const [b] = tours[0][1];
tours[0][0][0] = b;
tours[0][1][0] = a;
const apres = empreinteDuPlan(instance, { ...plan, tours });
assert.notEqual(apres.empreinte, avant.empreinte);
assert.notDeepEqual(indexerPlan(instance, { ...plan, tours }), indexerPlan(instance, plan));
});
test('l’empreinte lit chaque grandeur sous son nom : une grandeur changée change l’empreinte', () => {
const { instance, plan } = unPlan();
const { empreinte, grandeurs } = empreinteDuPlan(instance, plan);
const tableDe = indexerPlan(instance, plan);
for (const nom of GRANDEURS) {
const autre = { ...grandeurs, [nom]: grandeurs[nom] + 1 };
assert.notEqual(empreinteDe(tableDe, autre), empreinte, nom);
}
assert.equal(empreinteDe(tableDe, grandeurs), empreinte);
});
test('les seize combinaisons sont distinctes, chacune nommée par ses cases cochées', () => {
const toutes = combinaisons();
assert.equal(toutes.length, 16);
assert.equal(new Set(toutes.map(({ cle }) => cle)).size, 16);
assert.deepEqual(toutes[0], { cle: CASES.join('+'), contraintes: Object.fromEntries(CASES.map((c) => [c, true])) });
assert.deepEqual(toutes.at(-1), { cle: 'aucune', contraintes: Object.fromEntries(CASES.map((c) => [c, false])) });
for (const { cle, contraintes } of toutes) {
assert.deepEqual(Object.keys(contraintes), CASES, cle);
const cochees = CASES.filter((c) => contraintes[c]);
assert.equal(cle, cochees.length === 0 ? 'aucune' : cochees.join('+'));
}
});
test('differences nomme la configuration, la graine absente de la référence et la grandeur changée', () => {
const attendu = { 1: { empreinte: 'a', grandeurs: { excedentCollisions: 2, redondance: 5 } } };
assert.deepEqual(differences('petite', attendu, attendu), []);
assert.deepEqual(
differences('petite', { 4: attendu[1] }, attendu),
['petite, 4 : absente de la référence', 'petite, 1 : référence non recalculée'],
);
assert.deepEqual(
differences('petite', { 1: { empreinte: 'b', grandeurs: { excedentCollisions: 3, redondance: 5 } } }, attendu),
['petite, 1 : excedentCollisions 2 → 3'],
);
assert.deepEqual(
differences('petite', { 1: { empreinte: 'b', grandeurs: attendu[1].grandeurs } }, attendu),
['petite, 1 : plan changé, grandeurs égales'],
);
});
});
describe('GTT_EMPREINTES : verifier par défaut, ecrire sur demande', () => {
test('absente ou vide : verifier ; verifier et ecrire se lisent tels quels ; une autre valeur lève en la nommant', () => {
assert.equal(modeEmpreintes({}), 'verifier');
assert.equal(modeEmpreintes({ GTT_EMPREINTES: '' }), 'verifier');
assert.equal(modeEmpreintes({ GTT_EMPREINTES: 'verifier' }), 'verifier');
assert.equal(modeEmpreintes({ GTT_EMPREINTES: 'ecrire' }), 'ecrire');
assert.throws(() => modeEmpreintes({ GTT_EMPREINTES: 'ecrir' }), /GTT_EMPREINTES.*« ecrir »/);
});
});