New guards close the spec coverage gaps: only the configuration and the diagnostic read reservations in the engine, no second language in the sources, the engine and geometry never read the version, a test lives beside its module, fixtures never copy a shipped file, and the property library stays in a closed list. The delivery test pins the www output and the French language passed by the entry point. The spec names three test projects and the three shell files. Checked: 2778 node, 550 browser and 62 long tests from the index alone; the version check passes. --- FR --- [ADD] épreuves : gardes de couverture du spec, livrable, spec corrigé De nouvelles gardes ferment les manques de couverture : seules la configuration et le diagnostic lisent les réservations dans le moteur, aucun second langage dans les sources, le moteur et la géométrie ne lisent pas la version, une épreuve vit près de son module, aucune donnée d'épreuve ne copie un fichier livré, la bibliothèque de propriétés reste en liste fermée. Le livrable fige la sortie www et la langue française du point d'entrée. Le spec nomme trois projets et trois fichiers de coquille. Vérifié : 2778 node, 550 navigateur, 62 longues ; version conforme. Assisted-by: Claude Opus 5.5
177 lines
9.2 KiB
JavaScript
177 lines
9.2 KiB
JavaScript
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
||
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||
|
||
// L'environnement où l'essai de démarrage lance la coquille (§ 8.6) : trois
|
||
// dossiers distincts sous la racine de l'essai, aucun sous un autre ; le
|
||
// lanceur portable publie exe, le poste a home pour dossier personnel et
|
||
// temp pour dossier temporaire, et la configuration XDG du poste réel n'y
|
||
// entre pas. Le module se charge sans rien lancer : l'essai ne part que
|
||
// lorsque node l'exécute.
|
||
import assert from 'node:assert/strict';
|
||
import { mkdtempSync, rmSync, symlinkSync } from 'node:fs';
|
||
import { tmpdir } from 'node:os';
|
||
import path, { dirname, join, relative, isAbsolute } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { decrireSysteme } from '../electron/fichiers.js';
|
||
import { estSous } from '../src/stockage/dossier_travail.js';
|
||
import { describe, test } from '../test/lanceur.js';
|
||
import { donneesApplicativesSurExe, dossiersDEssai, environnementDEssai, estLanceDirectement } from './essai_demarrage.js';
|
||
|
||
const RACINE_ESSAI = join('/', 'racine-essai', 'gtt-essai-demarrage-x');
|
||
|
||
// Vrai quand b est a ou se trouve sous a.
|
||
const sous = (a, b) => {
|
||
const chemin = relative(a, b);
|
||
return chemin === '' || (!chemin.startsWith('..') && !isAbsolute(chemin));
|
||
};
|
||
|
||
describe('essai de démarrage : dossiers et environnement (§ 8.6)', () => {
|
||
test('trois dossiers distincts sous la racine, aucun sous un autre', () => {
|
||
const dossiers = dossiersDEssai(RACINE_ESSAI);
|
||
const noms = ['exe', 'home', 'temp'];
|
||
assert.deepEqual(Object.keys(dossiers).sort(), noms);
|
||
const chemins = noms.map((nom) => dossiers[nom]);
|
||
assert.equal(chemins.length, 3);
|
||
for (const chemin of chemins) assert.ok(sous(RACINE_ESSAI, chemin) && chemin !== RACINE_ESSAI, chemin);
|
||
for (const a of chemins) {
|
||
for (const b of chemins) if (a !== b) assert.ok(!sous(a, b), `${b} sous ${a}`);
|
||
}
|
||
assert.equal(new Set(chemins).size, 3);
|
||
});
|
||
|
||
test('exe publié, home et temp posés, XDG_CONFIG_HOME retiré, le reste gardé', () => {
|
||
const dossiers = dossiersDEssai(RACINE_ESSAI);
|
||
const poste = Object.freeze({
|
||
HOME: join('/', 'maison-originale'),
|
||
XDG_CONFIG_HOME: join('/', 'maison-originale', 'configuration'),
|
||
TMPDIR: join('/', 'temporaire-du-poste'),
|
||
PATH: join('/', 'outils'),
|
||
LANG: 'fr_CA.UTF-8',
|
||
});
|
||
const env = environnementDEssai(poste, dossiers);
|
||
assert.equal(env.PORTABLE_EXECUTABLE_DIR, dossiers.exe);
|
||
assert.equal(env.HOME, dossiers.home);
|
||
assert.equal(env.TMPDIR, dossiers.temp);
|
||
assert.equal(env.TEMP, dossiers.temp);
|
||
assert.equal(env.TMP, dossiers.temp);
|
||
assert.ok(!Object.hasOwn(env, 'XDG_CONFIG_HOME'));
|
||
assert.equal(env.PATH, poste.PATH);
|
||
assert.equal(env.LANG, poste.LANG);
|
||
// L'environnement du poste reste tel quel.
|
||
assert.equal(poste.HOME, join('/', 'maison-originale'));
|
||
});
|
||
|
||
test('sous X11 sans XAUTHORITY, le fichier d’autorisation reste celui du dossier personnel réel', () => {
|
||
const dossiers = dossiersDEssai(RACINE_ESSAI);
|
||
const maison = join('/', 'maison-originale');
|
||
const avecX = environnementDEssai({ HOME: maison, DISPLAY: ':9' }, dossiers);
|
||
assert.equal(avecX.XAUTHORITY, join(maison, '.Xauthority'));
|
||
const pose = join('/', 'autorisation-posee');
|
||
assert.equal(environnementDEssai({ HOME: maison, DISPLAY: ':9', XAUTHORITY: pose }, dossiers).XAUTHORITY, pose);
|
||
assert.ok(!Object.hasOwn(environnementDEssai({ HOME: maison }, dossiers), 'XAUTHORITY'));
|
||
});
|
||
|
||
test('sous Windows, un temporaire du poste rangé sous LOCALAPPDATA ne met pas exe sous les données applicatives', () => {
|
||
const W = path.win32;
|
||
const profil = 'C:\\Users\\profil-essai';
|
||
const local = W.join(profil, 'AppData', 'Local');
|
||
const poste = Object.freeze({
|
||
USERPROFILE: profil,
|
||
APPDATA: W.join(profil, 'AppData', 'Roaming'),
|
||
LOCALAPPDATA: local,
|
||
TEMP: W.join(local, 'Temp'),
|
||
TMP: W.join(local, 'Temp'),
|
||
});
|
||
const dossiers = dossiersDEssai(W.join(poste.TEMP, 'gtt-essai-demarrage-x'), W);
|
||
const env = environnementDEssai(poste, dossiers, W);
|
||
// Les données applicatives de la coquille (§ 8.6 point 2) : AppData,
|
||
// dossier connu que l'environnement ne déplace pas, le LOCALAPPDATA de
|
||
// son environnement, et le temporaire, que TMP désigne.
|
||
const emplacements = { insensibleCasse: true, separateur: '\\' };
|
||
assert.equal(env.PORTABLE_EXECUTABLE_DIR, W.join(poste.TEMP, 'gtt-essai-demarrage-x', 'exe'));
|
||
assert.deepEqual(
|
||
[poste.APPDATA, env.LOCALAPPDATA, env.TMP].filter((dossier) => estSous(dossiers.exe, dossier, emplacements)),
|
||
[],
|
||
);
|
||
// Le profil, AppData et LOCALAPPDATA de la coquille sont sous home.
|
||
assert.equal(env.USERPROFILE, dossiers.home);
|
||
assert.ok(estSous(env.APPDATA, dossiers.home, emplacements) && env.APPDATA !== dossiers.home, env.APPDATA);
|
||
assert.ok(estSous(env.LOCALAPPDATA, dossiers.home, emplacements) && env.LOCALAPPDATA !== dossiers.home, env.LOCALAPPDATA);
|
||
assert.deepEqual(donneesApplicativesSurExe(env, poste, 'win32'), []);
|
||
assert.equal(poste.LOCALAPPDATA, local);
|
||
});
|
||
|
||
test("la garde nomme les données applicatives qui portent exe, avant tout lancement", () => {
|
||
// Sous Linux : des chemins composés par path.posix de bout en bout, que
|
||
// le système hôte de l'épreuve ne change pas.
|
||
const P = path.posix;
|
||
const R = P.join('/', 'racine-essai', 'gtt-essai-demarrage-x');
|
||
const dossiers = dossiersDEssai(R, P);
|
||
const env = environnementDEssai({ HOME: P.join('/', 'maison-originale') }, dossiers, P);
|
||
assert.deepEqual(donneesApplicativesSurExe(env, {}, 'linux'), []);
|
||
// Le temporaire de la coquille au-dessus d'exe.
|
||
assert.deepEqual(donneesApplicativesSurExe({ ...env, TMPDIR: R }, {}, 'linux'), [R]);
|
||
// Le dossier de configuration XDG au-dessus d'exe.
|
||
assert.deepEqual(donneesApplicativesSurExe({ ...env, XDG_CONFIG_HOME: R }, {}, 'linux'), [R]);
|
||
// Sous Windows : l'AppData du poste, dossier connu, que l'environnement
|
||
// de la coquille ne déplace pas ; puis le LOCALAPPDATA de la coquille.
|
||
const W = path.win32;
|
||
const racine = W.join('D:\\', 'essais', 'gtt-essai-demarrage-x');
|
||
const dossiersW = dossiersDEssai(racine, W);
|
||
const envW = environnementDEssai({}, dossiersW, W);
|
||
assert.deepEqual(donneesApplicativesSurExe(envW, {}, 'win32'), []);
|
||
assert.deepEqual(donneesApplicativesSurExe(envW, { APPDATA: W.join('D:\\', 'essais') }, 'win32'), [
|
||
W.join('D:\\', 'essais'),
|
||
]);
|
||
assert.deepEqual(donneesApplicativesSurExe({ ...envW, LOCALAPPDATA: 'd:\\ESSAIS' }, {}, 'win32'), ['d:\\ESSAIS']);
|
||
// Sans exécutable publié, rien ne le porte.
|
||
assert.deepEqual(donneesApplicativesSurExe({ TMPDIR: '/' }, {}, 'linux'), []);
|
||
});
|
||
|
||
test('la liste des données applicatives est celle que decrireSysteme compose : un emplacement qu’elle ajoute est jugé, et un app.getPath que l’essai ne sait pas émuler lève', () => {
|
||
const P = path.posix;
|
||
const R = P.join('/', 'racine-essai', 'gtt-essai-demarrage-x');
|
||
const dossiers = dossiersDEssai(R, P);
|
||
const env = environnementDEssai({ HOME: P.join('/', 'maison-originale') }, dossiers, P);
|
||
const autre = P.join('/', 'racine-essai');
|
||
// Une coquille qui range aussi GTT_DONNEES parmi ses données applicatives.
|
||
const avecUnDossier = (poste) => {
|
||
const decrit = decrireSysteme(poste);
|
||
const donneesApplicatives = [...decrit.emplacements.donneesApplicatives, poste.env.GTT_DONNEES];
|
||
return { ...decrit, emplacements: { ...decrit.emplacements, donneesApplicatives } };
|
||
};
|
||
assert.deepEqual(donneesApplicativesSurExe({ ...env, GTT_DONNEES: autre }, {}, 'linux', avecUnDossier), [autre]);
|
||
assert.deepEqual(donneesApplicativesSurExe({ ...env, GTT_DONNEES: P.join('/', 'ailleurs') }, {}, 'linux', avecUnDossier), []);
|
||
// Une coquille qui lit un autre dossier du système : l'essai le refuse
|
||
// plutôt que de juger une liste incomplète.
|
||
const avecUserData = (poste) => {
|
||
poste.cheminSysteme('userData');
|
||
return decrireSysteme(poste);
|
||
};
|
||
assert.throws(() => donneesApplicativesSurExe(env, {}, 'linux', avecUserData), /userData/);
|
||
});
|
||
});
|
||
|
||
describe('essai de démarrage : lancement direct', () => {
|
||
const MODULE = fileURLToPath(new URL('./essai_demarrage.js', import.meta.url));
|
||
|
||
// Le lien désigne le dossier du module : une jonction sous Windows, qui se
|
||
// crée sans privilège, un lien symbolique ailleurs.
|
||
test('le script atteint par un lien symbolique est le module', () => {
|
||
const dossier = mkdtempSync(join(tmpdir(), 'gtt-essai-lien-'));
|
||
try {
|
||
const lien = join(dossier, 'scripts');
|
||
symlinkSync(dirname(MODULE), lien, 'junction');
|
||
assert.equal(estLanceDirectement(join(lien, 'essai_demarrage.js')), true);
|
||
assert.equal(estLanceDirectement(MODULE), true);
|
||
} finally {
|
||
rmSync(dossier, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test('un autre script, un script absent ou aucun script ne lance pas l’essai', () => {
|
||
assert.equal(estLanceDirectement(fileURLToPath(import.meta.url)), false);
|
||
assert.equal(estLanceDirectement(join(tmpdir(), 'gtt-essai-absent', 'essai_demarrage.js')), false);
|
||
assert.equal(estLanceDirectement(undefined), false);
|
||
});
|
||
});
|