gestion_table_tournante_libre/src/moteur/empreintes.test.js

181 lines
8.1 KiB
JavaScript
Raw Normal View History

// © 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 »/);
});
});