gestion_table_tournante_libre/test/machine.js
Mathieu Benoit 3028fca2ed [FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.

--- FR ---

[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés

Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.

Assisted-by: Claude Opus 5.5
2026-10-09 04:02:03 -04:00

73 lines
4 KiB
JavaScript

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Réglages des séries node selon le nombre de cœurs de la machine (§ 14.14).
//
// Le budget de relance, deux secondes, tient avec des processus complets à
// partir de quatre cœurs. En deçà, le lanceur ne porte la série que dans un
// seul processus, et la relance qui suit un module de base — que presque
// toutes les épreuves importent — dépasse ce budget : de l'ordre de trois
// secondes sur deux cœurs. Une petite machine prend donc des threads, moins
// coûteux à démarrer qu'un processus, sur tous ses cœurs, et les épreuves
// lourdes de la série surveillée passent dans la série longue : la même
// relance y tient alors sous une seconde et demie.
//
// Aucune épreuve ne disparaît : elle change de série, et ce qu'elle éprouve
// ne dépend pas de la machine. Les threads partagent un processus : la garde
// de test/fils.test.js tient la série node à l'écart de l'état global du
// processus, sans quoi une épreuve en troublerait une autre.
import { availableParallelism } from 'node:os';
// Nombre de cœurs à partir duquel la série surveillée garde des processus
// complets et toutes ses épreuves.
export const SEUIL_COEURS = 4;
// Épreuves de la série surveillée qui passent dans node-long sur une petite
// machine. L'épreuve de bout en bout lance de vraies recherches : c'est la
// plus lourde de la série, et toute modification du moteur la rejoue.
export const LOURDES = Object.freeze(['src/moteur/integration.test.js']);
/**
* Réglages des projets node et node-long pour un nombre de cœurs.
*
* Sous la mesure de la couverture, aucune épreuve ne quitte la série node :
* les seuils de branches d'indicateurs.js et de plafond.js se mesurent avec
* l'épreuve de bout en bout, sur une petite machine comme sur une grande. La
* mesure ne relance rien sous surveillance ; le budget de relance n'y joue
* pas. Le reste des réglages ne change pas.
*
* @param {number} coeurs entier ≥ 1
* @param {{couverture?: boolean}} [options] couverture vraie quand le
* lanceur mesure la couverture (--coverage)
* @returns {{petiteMachine: boolean, coeurs: number, pool: 'forks'|'threads',
* maxWorkers: number|undefined, horsSerieSurveillee: string[]}}
* maxWorkers undefined laisse au lanceur son défaut
*/
export function reglagesSeries(coeurs, { couverture = false } = {}) {
if (!Number.isInteger(coeurs) || coeurs < 1) {
throw new RangeError(`nombre de cœurs ${String(coeurs)} : entier ≥ 1 attendu`);
}
if (coeurs >= SEUIL_COEURS) {
return { petiteMachine: false, coeurs, pool: 'forks', maxWorkers: undefined, horsSerieSurveillee: [] };
}
const horsSerieSurveillee = couverture ? [] : [...LOURDES];
return { petiteMachine: true, coeurs, pool: 'threads', maxWorkers: coeurs, horsSerieSurveillee };
}
// Cœurs que ce processus peut occuper. availableParallelism suit l'affinité
// du processus : sous « taskset -c 0,1 », il rend 2 sur une machine qui en a
// seize, ce qui permet d'éprouver le mode d'une petite machine sur une grande.
export const coeursDisponibles = () => availableParallelism();
// Ligne que le rapporteur imprime, pour qu'un développeur sache sur quelle
// série sa relance a porté. Une petite machine sans épreuve hors de la série
// surveillée — sous la mesure de la couverture — la dit entière.
export function ligneMachine({ petiteMachine, coeurs, horsSerieSurveillee }) {
const compte = coeurs === 1 ? '1 cœur' : `${coeurs} cœurs`;
if (!petiteMachine) return `Machine : ${compte} — processus complets, série surveillée entière.`;
const ou = coeurs === 1 ? 'sur son seul cœur' : `sur les ${coeurs} cœurs`;
const serie = horsSerieSurveillee.length === 0
? 'série surveillée entière'
: `en série longue : ${horsSerieSurveillee.join(', ')}`;
return `Machine : ${compte}, en deçà de ${SEUIL_COEURS} — threads ${ou} ; ${serie}.`;
}