[ADD] bench: make banc orchestrator, first reading and constants table
`make banc` runs the node and browser measures, assembles releve.json in
the registry's order and fails when a measure is missing, a harness
check falls, a text measure is not in Noto Sans, or the time per image
is not held. It prints values to adopt and never edits constantes.js; ρ
is reported but stays at its working default on the reference machine.
Each bench constant cites the CPU slowdown of its own measure. The
reading names the machine class only: identifying keys, accented or
camelCase, and account paths are refused.
Checked: node 3836, browser 758, node-long 101; make banc exits 0.
--- FR ---
[ADD] banc : orchestrateur make banc, premier relevé, constantes
`make banc` lance les mesures node et navigateur, assemble releve.json
dans l'ordre du registre et échoue sur une mesure manquante, un contrôle
du harnais tombé, une mesure de texte hors Noto Sans ou un temps par
image non tenu. Il imprime les valeurs à adopter sans toucher
constantes.js ; ρ est relevé mais reste à son défaut de travail. Chaque
constante du banc cite le ralentissement de sa propre mesure. Le relevé
ne nomme que la classe de machine : clés identifiantes, accentuées ou
camelCase, et chemins de compte sont refusés.
Vérifié : node 3836, navigateur 758, longues 101 ; make banc sort en 0.
Assisted-by: Claude Opus 5.5
2026-10-10 15:31:25 -04:00
|
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
|
|
// L'orchestrateur du banc de mesure (§ 19.10), que lancent npm run banc et
|
|
|
|
|
|
// make banc. Son déroulé :
|
|
|
|
|
|
//
|
|
|
|
|
|
// 1. effacer les sorties du navigateur (scripts/banc/sorties/<nom>.json,
|
|
|
|
|
|
// chacune par son nom), pour qu'aucune sortie d'un passage précédent
|
|
|
|
|
|
// ne se lise comme une mesure de celui-ci ;
|
|
|
|
|
|
// 2. relever le coût du plafond a priori sous node (plafond.js) ;
|
|
|
|
|
|
// 3. jouer le projet banc de Vitest (vitest.banc.config.js), qui écrit
|
|
|
|
|
|
// les sorties ; une épreuve tombée — un contrôle du harnais, une
|
|
|
|
|
|
// mesure qui se refuse — arrête le passage sans relevé ;
|
|
|
|
|
|
// 4. lire les sorties, et assembler le relevé : la configuration de la
|
|
|
|
|
|
// machine, puis chaque mesure attendue, une fois, dans l'ordre du
|
|
|
|
|
|
// registre, avec le ralentissement sous lequel elle s'est prise ; une
|
|
|
|
|
|
// mesure manquante arrête le passage sans relevé ;
|
|
|
|
|
|
// 5. juger : une mesure de texte prise hors de la police embarquée
|
|
|
|
|
|
// arrête le passage sans relevé ; un verdict « non tenu » s'écrit au
|
|
|
|
|
|
// relevé, qui est une donnée, et rend la sortie non nulle ;
|
|
|
|
|
|
// 6. écrire scripts/banc/releve.json, puis imprimer le tableau des
|
|
|
|
|
|
// constantes, les valeurs proposées face à celles du registre et les
|
|
|
|
|
|
// écarts de leur accord.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Le banc écrit un relevé, jamais le registre : src/geometrie/constantes.js
|
|
|
|
|
|
// ne se lit ici que pour l'ordre et les valeurs, et une valeur s'adopte à la
|
|
|
|
|
|
// main (releve.js). La configuration du relevé est la liste fermée du
|
|
|
|
|
|
// contrat — cadre, facteur d'échelle, versions, nombre de processeurs
|
|
|
|
|
|
// logiques, ralentissement, police — et ne nomme pas la machine : ni nom
|
|
|
|
|
|
// d'hôte, ni modèle de processeur, ni utilisateur, ni chemin, ni date
|
|
|
|
|
|
// (contrainte 27). Code de sortie : 0 quand le relevé est écrit et que
|
|
|
|
|
|
// chaque verdict est tenu, 1 sinon.
|
|
|
|
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
|
|
import { readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
|
|
|
|
import os from 'node:os';
|
|
|
|
|
|
import { join } from 'node:path';
|
|
|
|
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
|
|
import { CONSTANTES } from '../../src/geometrie/constantes.js';
|
|
|
|
|
|
import { optionsBanc, projetBanc } from './projet.js';
|
|
|
|
|
|
import { accordRegistre, nombre, tableauConstantes, verifierReleve } from './releve.js';
|
|
|
|
|
|
|
|
|
|
|
|
/** La racine du projet, d'où se résolvent les chemins relatifs. */
|
|
|
|
|
|
export const RACINE = fileURLToPath(new URL('../../', import.meta.url));
|
|
|
|
|
|
|
|
|
|
|
|
/** Le relevé, relatif à la racine. */
|
|
|
|
|
|
export const CHEMIN_RELEVE = 'scripts/banc/releve.json';
|
|
|
|
|
|
|
|
|
|
|
|
// Le dossier des sorties du navigateur, que navigateur.js écrit.
|
|
|
|
|
|
const DOSSIER_SORTIES = 'scripts/banc/sorties';
|
|
|
|
|
|
|
|
|
|
|
|
/** Les sorties que les épreuves du banc écrivent, par nom de fichier sans
|
|
|
|
|
|
* extension : le harnais, puis chaque mesure. */
|
[ADD] engine for quality: A_max, facilitator diversity, run trace, pairs
The quality page needs figures the engine did not yet produce. It now
bounds each person's distinct memberships (A_max) and checks it with
the indicator guard; reads facilitator diversity over the evening and
per round; records the search's moves and record points; lists repeated
and imposed pairs; and lays out charts through one geometry module with
two bench thresholds. Chart colour tokens carry their own validation.
Default plans still equal the reference fingerprints; nothing new is
written to the file yet.
Checked: node 4768, navigateur 967, node-long 128; new modules at 100 %.
--- FR ---
[ADD] moteur de la qualité : A_max, diversité, course, paires
La page de qualité demande des chiffres que le moteur ne produisait pas.
Il borne désormais les appartenances distinctes de chacun (A_max) et la
garde des indicateurs la vérifie ; il lit la diversité des animateurs sur
la soirée et par tour ; il relève les mouvements et les points de la
recherche ; il liste les paires répétées et imposées ; un seul module de
géométrie dispose les graphiques, avec deux seuils au banc. Les jetons
des graphiques portent leur propre validation. Les plans par défaut
égalent les empreintes ; rien de neuf ne s'écrit encore au fichier.
Vérifié : node 4768, navigateur 967, longues 128 ; nouveaux modules à 100 %.
Assisted-by: Claude Opus 5.5
2026-10-12 04:29:27 -04:00
|
|
|
|
export const SORTIES_NAVIGATEUR = Object.freeze(['harnais', 'temoin', 'sieges_lisibles', 'temps_image', 'parallele', 'qualite']);
|
[ADD] bench: make banc orchestrator, first reading and constants table
`make banc` runs the node and browser measures, assembles releve.json in
the registry's order and fails when a measure is missing, a harness
check falls, a text measure is not in Noto Sans, or the time per image
is not held. It prints values to adopt and never edits constantes.js; ρ
is reported but stays at its working default on the reference machine.
Each bench constant cites the CPU slowdown of its own measure. The
reading names the machine class only: identifying keys, accented or
camelCase, and account paths are refused.
Checked: node 3836, browser 758, node-long 101; make banc exits 0.
--- FR ---
[ADD] banc : orchestrateur make banc, premier relevé, constantes
`make banc` lance les mesures node et navigateur, assemble releve.json
dans l'ordre du registre et échoue sur une mesure manquante, un contrôle
du harnais tombé, une mesure de texte hors Noto Sans ou un temps par
image non tenu. Il imprime les valeurs à adopter sans toucher
constantes.js ; ρ est relevé mais reste à son défaut de travail. Chaque
constante du banc cite le ralentissement de sa propre mesure. Le relevé
ne nomme que la classe de machine : clés identifiantes, accentuées ou
camelCase, et chemins de compte sont refusés.
Vérifié : node 3836, navigateur 758, longues 101 ; make banc sort en 0.
Assisted-by: Claude Opus 5.5
2026-10-10 15:31:25 -04:00
|
|
|
|
|
|
|
|
|
|
/** Les gardes du banc : des mesures hors du registre, qui ne proposent pas
|
|
|
|
|
|
* de constante mais disent si une hypothèse tient, par ordre de nom. */
|
|
|
|
|
|
export const GARDES = Object.freeze(['DUREE_GENERATION_PAR_DEFAUT', 'TEMPS_IMAGE_LISTES']);
|
|
|
|
|
|
|
|
|
|
|
|
/** Les mesures qu'un relevé porte : chaque constante du banc du registre,
|
|
|
|
|
|
* dans son ordre, puis les gardes. Une constante du banc ajoutée au
|
|
|
|
|
|
* registre est attendue d'office, et manque tant que le banc ne la relève
|
|
|
|
|
|
* pas. */
|
|
|
|
|
|
export const ATTENDUES = Object.freeze([...CONSTANTES.filter((c) => c.proprietaire === 'banc').map((c) => c.nom), ...GARDES]);
|
|
|
|
|
|
|
|
|
|
|
|
/** La police embarquée, sous laquelle se prend toute mesure de texte. */
|
|
|
|
|
|
export const FAMILLE_EMBARQUEE = 'Noto Sans';
|
|
|
|
|
|
|
[ADD] quality page: walkthrough captures, edge cases, contrast guards
The quality page is now photographed and guarded like the other
screens. The walkthrough captures each view and the seven edge cases
where a sentence replaces a chart; the transversal guards measure the
page's marks, keyboard access and tooltips, a dotted reference line
against the chart background and a run point against what its trace
covers. Proved and remaining shares differ by outline, not colour
alone; tooltips keep clear of the sticky banner; rank axes print whole
numbers. The two bench thresholds of the page are measured.
Checked: node 5096, navigateur 1137, node-long 128; captures published.
--- FR ---
[ADD] page de qualité : parcours, cas limites, gardes du contraste
La page de qualité est désormais photographiée et gardée comme les autres
écrans. Le parcours photographie chaque vue et les sept cas limites où une
phrase remplace un graphique ; les gardes transversales mesurent ses
marques, son clavier et ses infobulles, un repère pointillé contre le fond
du graphique, un point de relevé contre ce que son tracé recouvre. Prouvé
et reste se distinguent par le contour, pas par la seule couleur ; les
infobulles évitent le bandeau collant ; les rangs s'écrivent en entiers.
Les deux seuils de la page sont mesurés au banc.
Vérifié : node 5096, navigateur 1137, longues 128 ; captures publiées.
Assisted-by: Claude Opus 5.5
2026-10-12 16:38:04 -04:00
|
|
|
|
// Les mesures qui lisent le rendu du texte, et nomment donc leur police :
|
|
|
|
|
|
// le seuil de la matrice en est, sa colonne de noms prenant la largeur
|
|
|
|
|
|
// rendue du nom de référence.
|
|
|
|
|
|
const MESURES_DE_TEXTE = Object.freeze([
|
|
|
|
|
|
'SEUIL_SIEGES_LISIBLES',
|
|
|
|
|
|
'TOLERANCE_CHAINE_TEMOIN',
|
|
|
|
|
|
'SEUIL_MATRICE_PARTICIPANTS',
|
|
|
|
|
|
'LARGEUR_TRONCATURE_NOM_PX',
|
|
|
|
|
|
]);
|
[ADD] bench: make banc orchestrator, first reading and constants table
`make banc` runs the node and browser measures, assembles releve.json in
the registry's order and fails when a measure is missing, a harness
check falls, a text measure is not in Noto Sans, or the time per image
is not held. It prints values to adopt and never edits constantes.js; ρ
is reported but stays at its working default on the reference machine.
Each bench constant cites the CPU slowdown of its own measure. The
reading names the machine class only: identifying keys, accented or
camelCase, and account paths are refused.
Checked: node 3836, browser 758, node-long 101; make banc exits 0.
--- FR ---
[ADD] banc : orchestrateur make banc, premier relevé, constantes
`make banc` lance les mesures node et navigateur, assemble releve.json
dans l'ordre du registre et échoue sur une mesure manquante, un contrôle
du harnais tombé, une mesure de texte hors Noto Sans ou un temps par
image non tenu. Il imprime les valeurs à adopter sans toucher
constantes.js ; ρ est relevé mais reste à son défaut de travail. Chaque
constante du banc cite le ralentissement de sa propre mesure. Le relevé
ne nomme que la classe de machine : clés identifiantes, accentuées ou
camelCase, et chemins de compte sont refusés.
Vérifié : node 3836, navigateur 758, longues 101 ; make banc sort en 0.
Assisted-by: Claude Opus 5.5
2026-10-10 15:31:25 -04:00
|
|
|
|
|
|
|
|
|
|
// La machine de référence du banc : le poste, son processeur ralenti ×4 par
|
|
|
|
|
|
// le navigateur. C'est le ralentissement de la configuration du relevé ;
|
|
|
|
|
|
// chaque mesure porte en plus celui sous lequel elle s'est prise, que cite
|
|
|
|
|
|
// la configuration de sa constante.
|
|
|
|
|
|
const RALENTISSEMENT_REFERENCE = 4;
|
|
|
|
|
|
|
|
|
|
|
|
// Les constantes du banc que la machine de référence relève sans les
|
|
|
|
|
|
// proposer : ρ y reste à son défaut de travail. Leur mesure s'imprime pour
|
|
|
|
|
|
// information, jamais « à adopter ».
|
|
|
|
|
|
const RELEVEES_SANS_ADOPTION = Object.freeze(['RALENTISSEMENT_PLEINE_CHARGE']);
|
|
|
|
|
|
|
|
|
|
|
|
// Les champs de sommet qu'une sortie peut porter en plus de ses mesures, et
|
|
|
|
|
|
// que le relevé reprend tels quels.
|
|
|
|
|
|
const CHAMPS_REPRIS = Object.freeze(['plafondAPriori', 'parallele']);
|
|
|
|
|
|
|
|
|
|
|
|
const cheminSortie = (nom) => `${DOSSIER_SORTIES}/${nom}.json`;
|
|
|
|
|
|
const estAbsent = (erreur) => erreur?.code === 'ENOENT';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* La configuration du relevé : le cadre du projet banc au facteur 1, les
|
|
|
|
|
|
* versions du navigateur et de Node, le nombre des processeurs logiques, le
|
|
|
|
|
|
* ralentissement de la machine de référence, la police embarquée. Du
|
|
|
|
|
|
* système, seul availableParallelism se lit : ni nom d'hôte, ni modèle de
|
|
|
|
|
|
* processeur, ni utilisateur, ni dossier.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {{systeme: Pick<typeof os, 'availableParallelism'>,
|
|
|
|
|
|
* node: string, navigateur: string|null}} options
|
|
|
|
|
|
* @returns {import('./releve.js').ConfigurationReleve}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function configurationDuBanc({ systeme, node, navigateur }) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
cadre: { ...projetBanc().provide.cadreBanc },
|
|
|
|
|
|
facteurEchelle: 1,
|
|
|
|
|
|
navigateur,
|
|
|
|
|
|
node,
|
|
|
|
|
|
processeursLogiques: systeme.availableParallelism(),
|
|
|
|
|
|
ralentissementCpu: RALENTISSEMENT_REFERENCE,
|
|
|
|
|
|
police: FAMILLE_EMBARQUEE,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Le rang d'une mesure dans le relevé : son rang au registre, puis, pour
|
|
|
|
|
|
// une garde, après toutes les constantes, par ordre de nom.
|
|
|
|
|
|
function rangeur(constantes) {
|
|
|
|
|
|
const rang = (nom) => {
|
|
|
|
|
|
const auRegistre = constantes.findIndex((c) => c.nom === nom);
|
|
|
|
|
|
return auRegistre >= 0 ? auRegistre : constantes.length;
|
|
|
|
|
|
};
|
|
|
|
|
|
return (a, b) => rang(a.nom) - rang(b.nom) || (a.nom < b.nom ? -1 : a.nom > b.nom ? 1 : 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Le relevé d'un passage : la configuration, les mesures de toutes les
|
|
|
|
|
|
* sorties — chaque attendue une fois, rangées dans l'ordre du registre reçu
|
|
|
|
|
|
* puis les gardes par ordre de nom —, et plafondAPriori et parallele quand
|
|
|
|
|
|
* une sortie les porte. Une mesure sans ralentissement propre reçoit celui
|
|
|
|
|
|
* de la configuration de sa sortie, quand celle-ci en porte un : en copie,
|
|
|
|
|
|
* les sorties reçues restent intactes. Les sorties se lisent dans l'ordre
|
|
|
|
|
|
* de la table ; une sortie sans mesures (le harnais) n'apporte rien.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {{configuration: import('./releve.js').ConfigurationReleve,
|
|
|
|
|
|
* sorties: Map<string, {mesures?: object[], plafondAPriori?: object[], parallele?: object[]}>,
|
|
|
|
|
|
* attendues: ReadonlyArray<string>,
|
|
|
|
|
|
* constantes?: ReadonlyArray<import('../../src/geometrie/constantes.js').Constante>}} options
|
|
|
|
|
|
* @returns {import('./releve.js').Releve}
|
|
|
|
|
|
* @throws {TypeError} des sorties qui ne sont pas une Map
|
|
|
|
|
|
* @throws {Error} une mesure attendue absente, une mesure en double ou
|
|
|
|
|
|
* inattendue, un champ repris porté par deux sorties, ou un relevé que
|
|
|
|
|
|
* verifierReleve refuse — le message nomme chaque faute
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function assembler({ configuration, sorties, attendues, constantes = CONSTANTES }) {
|
|
|
|
|
|
if (!(sorties instanceof Map)) throw new TypeError('sorties du banc : une Map attendue');
|
|
|
|
|
|
const mesures = [];
|
|
|
|
|
|
const repris = new Map();
|
|
|
|
|
|
for (const [nomSortie, sortie] of sorties) {
|
|
|
|
|
|
const ralentissementSortie = sortie.configuration?.ralentissementCpu;
|
|
|
|
|
|
for (const mesure of sortie.mesures ?? []) {
|
|
|
|
|
|
if (!attendues.includes(mesure.nom)) throw new Error(`mesure inattendue : ${mesure.nom} (sortie ${nomSortie})`);
|
|
|
|
|
|
if (mesures.some((m) => m.nom === mesure.nom)) throw new Error(`mesure en double : ${mesure.nom} (sortie ${nomSortie})`);
|
|
|
|
|
|
const herite = ralentissementSortie !== undefined && !Object.hasOwn(mesure, 'ralentissementCpu');
|
|
|
|
|
|
mesures.push(herite ? { ...mesure, ralentissementCpu: ralentissementSortie } : mesure);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const champ of CHAMPS_REPRIS) {
|
|
|
|
|
|
if (!Object.hasOwn(sortie, champ)) continue;
|
|
|
|
|
|
if (repris.has(champ)) throw new Error(`${champ} : porté par deux sorties, ${repris.get(champ).sortie} et ${nomSortie}`);
|
|
|
|
|
|
repris.set(champ, { sortie: nomSortie, valeur: sortie[champ] });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const absentes = attendues.filter((nom) => !mesures.some((m) => m.nom === nom));
|
|
|
|
|
|
if (absentes.length > 0) throw new Error(`mesure attendue absente : ${absentes.join(', ')}`);
|
|
|
|
|
|
const releve = { format: 1, configuration, mesures: [...mesures].sort(rangeur(constantes)) };
|
|
|
|
|
|
for (const champ of CHAMPS_REPRIS) if (repris.has(champ)) releve[champ] = repris.get(champ).valeur;
|
|
|
|
|
|
const fautes = verifierReleve(releve, constantes);
|
|
|
|
|
|
if (fautes.length > 0) throw new Error(`relevé refusé : ${fautes.map(({ chemin, faute }) => `${chemin} : ${faute}`).join(' ; ')}`);
|
|
|
|
|
|
return releve;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Le jugement d'un relevé assemblé. bloquants : chaque mesure de texte
|
|
|
|
|
|
* prise sous une autre police que la police embarquée, ou qui n'en nomme
|
|
|
|
|
|
* aucune — le relevé ne s'écrit pas. nonTenus : chaque mesure au verdict
|
|
|
|
|
|
* « nonTenu », avec son critère — le relevé s'écrit, la sortie est non
|
|
|
|
|
|
* nulle. Dans l'ordre des mesures.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {import('./releve.js').Releve} releve
|
|
|
|
|
|
* @returns {{bloquants: string[], nonTenus: string[]}}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function jugement(releve) {
|
|
|
|
|
|
const bloquants = [];
|
|
|
|
|
|
const nonTenus = [];
|
|
|
|
|
|
for (const mesure of releve.mesures) {
|
|
|
|
|
|
if (MESURES_DE_TEXTE.includes(mesure.nom) && mesure.police !== FAMILLE_EMBARQUEE) {
|
|
|
|
|
|
bloquants.push(`${mesure.nom} mesurée sous la police « ${mesure.police ?? 'inconnue'} », et non sous ${FAMILLE_EMBARQUEE}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (mesure.verdict === 'nonTenu') nonTenus.push(`${mesure.nom} : verdict non tenu — ${mesure.critere}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return { bloquants, nonTenus };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Le relevé committé, lu sur le disque.
|
|
|
|
|
|
const releveCommite = (racine = RACINE) => JSON.parse(readFileSync(join(racine, CHEMIN_RELEVE), 'utf8'));
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Le tableau des constantes (§ 19.8, § 19.10) : tableauConstantes sur le
|
|
|
|
|
|
* registre et un relevé, par défaut le relevé committé. Une ligne par
|
|
|
|
|
|
* constante, sous un en-tête et son séparateur.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {{releve?: import('./releve.js').Releve,
|
|
|
|
|
|
* constantes?: ReadonlyArray<import('../../src/geometrie/constantes.js').Constante>}} [options]
|
|
|
|
|
|
* @returns {string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function regionConstantes({ releve = releveCommite(), constantes = CONSTANTES } = {}) {
|
|
|
|
|
|
return tableauConstantes(constantes, releve);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Une valeur et son unité, à la française.
|
|
|
|
|
|
const avecUnite = (valeur, unite) => (valeur === null ? 'aucune valeur' : `${nombre(valeur)} ${unite}`);
|
|
|
|
|
|
|
|
|
|
|
|
// Ce qu'un passage imprime après l'écriture du relevé : le tableau, chaque
|
|
|
|
|
|
// constante du banc face à sa valeur proposée — égale, à adopter, ou
|
|
|
|
|
|
// relevée sans adoption —, chaque garde, puis l'accord du registre avec le
|
|
|
|
|
|
// relevé.
|
|
|
|
|
|
function imprimer(releve, sortie) {
|
|
|
|
|
|
sortie('');
|
|
|
|
|
|
sortie(regionConstantes({ releve }));
|
|
|
|
|
|
sortie('');
|
|
|
|
|
|
sortie('Valeurs proposées par le banc (à adopter à la main dans src/geometrie/constantes.js) :');
|
|
|
|
|
|
for (const constante of CONSTANTES) {
|
|
|
|
|
|
if (constante.proprietaire !== 'banc') continue;
|
|
|
|
|
|
const mesure = releve.mesures.find((m) => m.nom === constante.nom);
|
|
|
|
|
|
let suite = mesure.valeurProposee === constante.valeur ? 'égale' : 'à adopter';
|
|
|
|
|
|
if (RELEVEES_SANS_ADOPTION.includes(constante.nom)) suite = 'relevé, non adopté : défaut de travail sous la machine de référence';
|
|
|
|
|
|
sortie(
|
|
|
|
|
|
`- ${constante.nom} : registre ${avecUnite(constante.valeur, constante.unite)}, ` +
|
|
|
|
|
|
`relevé ${avecUnite(mesure.valeurProposee, constante.unite)} — ${suite}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
sortie('Gardes :');
|
|
|
|
|
|
for (const mesure of releve.mesures) {
|
|
|
|
|
|
if (!GARDES.includes(mesure.nom)) continue;
|
|
|
|
|
|
const verdict = mesure.verdict === undefined ? '' : `, verdict ${mesure.verdict === 'tenu' ? 'tenu' : 'non tenu'}`;
|
|
|
|
|
|
sortie(`- ${mesure.nom} : ${avecUnite(mesure.valeurProposee, mesure.unite)}${verdict}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const ecarts = accordRegistre(CONSTANTES, releve);
|
|
|
|
|
|
if (ecarts.length === 0) sortie('Accord du registre avec le relevé : aucun écart.');
|
|
|
|
|
|
else {
|
|
|
|
|
|
sortie('Accord du registre avec le relevé :');
|
|
|
|
|
|
for (const { nom, faute } of ecarts) sortie(`- ${nom} : ${faute}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
sortie('La région « constantes » de README.md se met à jour par node scripts/documentation.js engendrer.');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Le relevé du plafond a priori, par le script lui-même : son JSON imprimé.
|
|
|
|
|
|
function plafondParScript(racine) {
|
|
|
|
|
|
const resultat = spawnSync(process.execPath, [join(racine, 'scripts/banc/plafond.js')], {
|
|
|
|
|
|
cwd: racine,
|
|
|
|
|
|
encoding: 'utf8',
|
|
|
|
|
|
stdio: ['ignore', 'pipe', 'inherit'],
|
|
|
|
|
|
});
|
|
|
|
|
|
if (resultat.status !== 0) throw new Error(`plafond.js : code ${resultat.status}`);
|
|
|
|
|
|
return JSON.parse(resultat.stdout);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Le projet banc de Vitest, sorties à l'écran ; rend son code de sortie.
|
|
|
|
|
|
function vitestDuBanc(racine) {
|
|
|
|
|
|
const resultat = spawnSync(process.execPath, [join(racine, 'node_modules/vitest/vitest.mjs'), 'run', '--config', 'vitest.banc.config.js'], {
|
|
|
|
|
|
cwd: racine,
|
|
|
|
|
|
stdio: 'inherit',
|
|
|
|
|
|
});
|
|
|
|
|
|
return resultat.status ?? 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// La version du Chromium du banc, lancé par Playwright sous les options du
|
|
|
|
|
|
// projet banc : « Chromium <version> ».
|
|
|
|
|
|
async function versionNavigateur() {
|
|
|
|
|
|
const { chromium } = await import('playwright');
|
|
|
|
|
|
const { launchOptions = {} } = optionsBanc(process.env);
|
|
|
|
|
|
const navigateur = await chromium.launch({ ...launchOptions, headless: true });
|
|
|
|
|
|
try {
|
|
|
|
|
|
return `Chromium ${navigateur.version()}`;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
await navigateur.close();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Un passage du banc, selon le déroulé de l'en-tête. Chaque effet se reçoit,
|
|
|
|
|
|
* pour que les épreuves le remplacent ; par défaut, ceux de la machine.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {{racine?: string, mesurerPlafond?: () => object[], lancerVitest?: () => number,
|
|
|
|
|
|
* lireNavigateur?: () => Promise<string>, systeme?: typeof os, node?: string,
|
|
|
|
|
|
* effacer?: (chemin: string) => void, lire?: (chemin: string) => string,
|
|
|
|
|
|
* ecrire?: (chemin: string, texte: string) => void, sortie?: (ligne: string) => void}} [effets]
|
|
|
|
|
|
* @returns {Promise<0|1>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function executer({
|
|
|
|
|
|
racine = RACINE,
|
|
|
|
|
|
mesurerPlafond = () => plafondParScript(racine),
|
|
|
|
|
|
lancerVitest = () => vitestDuBanc(racine),
|
|
|
|
|
|
lireNavigateur = versionNavigateur,
|
|
|
|
|
|
systeme = os,
|
|
|
|
|
|
node = process.version,
|
|
|
|
|
|
effacer = (chemin) => rmSync(join(racine, chemin), { force: true }),
|
|
|
|
|
|
lire = (chemin) => readFileSync(join(racine, chemin), 'utf8'),
|
|
|
|
|
|
ecrire = (chemin, texte) => writeFileSync(join(racine, chemin), texte),
|
|
|
|
|
|
sortie = (ligne) => process.stdout.write(`${ligne}\n`),
|
|
|
|
|
|
} = {}) {
|
|
|
|
|
|
for (const nom of SORTIES_NAVIGATEUR) effacer(cheminSortie(nom));
|
|
|
|
|
|
let releve;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const plafondAPriori = mesurerPlafond();
|
|
|
|
|
|
const code = lancerVitest();
|
|
|
|
|
|
if (code !== 0) {
|
|
|
|
|
|
sortie(`Une épreuve du banc est tombée (code ${code}) : un contrôle du harnais, ou une mesure refusée. Aucun relevé écrit.`);
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
const sorties = new Map([['plafond', { plafondAPriori }]]);
|
|
|
|
|
|
for (const nom of SORTIES_NAVIGATEUR) {
|
|
|
|
|
|
let texte;
|
|
|
|
|
|
try {
|
|
|
|
|
|
texte = lire(cheminSortie(nom));
|
|
|
|
|
|
} catch (erreur) {
|
|
|
|
|
|
if (estAbsent(erreur)) continue;
|
|
|
|
|
|
throw erreur;
|
|
|
|
|
|
}
|
|
|
|
|
|
sorties.set(nom, JSON.parse(texte));
|
|
|
|
|
|
}
|
|
|
|
|
|
const configuration = configurationDuBanc({ systeme, node, navigateur: await lireNavigateur() });
|
|
|
|
|
|
releve = assembler({ configuration, sorties, attendues: ATTENDUES });
|
|
|
|
|
|
} catch (erreur) {
|
|
|
|
|
|
sortie(`Aucun relevé écrit : ${erreur.message}`);
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { bloquants, nonTenus } = jugement(releve);
|
|
|
|
|
|
if (bloquants.length > 0) {
|
|
|
|
|
|
for (const motif of bloquants) sortie(motif);
|
|
|
|
|
|
sortie('Aucun relevé écrit : une mesure de texte hors de la police embarquée ne mesure pas le logiciel.');
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
ecrire(CHEMIN_RELEVE, `${JSON.stringify(releve, null, 2)}\n`);
|
|
|
|
|
|
sortie(`Relevé écrit : ${CHEMIN_RELEVE}`);
|
|
|
|
|
|
imprimer(releve, sortie);
|
|
|
|
|
|
for (const motif of nonTenus) sortie(motif);
|
|
|
|
|
|
return nonTenus.length > 0 ? 1 : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Vrai quand ce fichier est le script que Node a lancé, et non un module
|
|
|
|
|
|
// importé.
|
|
|
|
|
|
function estLanceDirectement() {
|
|
|
|
|
|
if (process.argv[1] === undefined) return false;
|
|
|
|
|
|
try {
|
|
|
|
|
|
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (estLanceDirectement()) process.exitCode = await executer();
|