[FIX] matrix: UTF-8 locale for Chromium, skip ps reading when absent
On Linux, Chromium converts origin-private file names through the process locale. The debian:13 and fedora:44 images set no LANG, so every accented name failed with TypeMismatchError there, while Arch, which sets C.UTF-8, passed. The container steps now run under LANG=C.UTF-8, and a browser test names that cause when a Chromium cannot write an accented name. The long test that reads the real ps skips with its reason when ps is missing. Checked: debian-13 and fedora-44 test_navigateur and test_long green in containers; 36 browser and 26 node-long tests locally. --- FR --- [FIX] matrice : locale UTF-8 pour Chromium, lecture ps sautée sans ps Sous Linux, Chromium convertit les noms de fichiers de l'origine privée par la locale du processus. Les images debian:13 et fedora:44 ne posent aucun LANG : chaque nom accentué y échouait (TypeMismatchError), alors qu'Arch, qui pose C.UTF-8, passait. Les étapes des conteneurs tournent sous LANG=C.UTF-8, et une épreuve du navigateur nomme cette cause quand un Chromium n'écrit pas un nom accentué. L'épreuve longue qui lit le vrai ps se saute, raison dite, quand ps manque. Vérifié : debian-13 et fedora-44 au vert en conteneur ; 36 + 26 en local. Assisted-by: Claude Opus 5.5
This commit is contained in:
parent
8d03d5ba41
commit
63efdb21a1
3 changed files with 47 additions and 7 deletions
|
|
@ -119,8 +119,15 @@ preparation() {
|
||||||
|
|
||||||
# Étapes sous le compte dev, chacune annoncée par une ligne « === étape
|
# Étapes sous le compte dev, chacune annoncée par une ligne « === étape
|
||||||
# statut » que le bilan relit. L'essai et le serveur de Vite ne se jouent
|
# statut » que le bilan relit. L'essai et le serveur de Vite ne se jouent
|
||||||
# que sur une installation réussie.
|
# que sur une installation réussie. Elles tournent sous LANG=C.UTF-8, des
|
||||||
|
# paramètres régionaux UTF-8 comme ceux d'un poste de développement, que la
|
||||||
|
# plupart des images ne posent pas : sous le « C » qu'elles laissent,
|
||||||
|
# Chromium ne convertit plus un nom hors ASCII de l'OPFS, et les épreuves
|
||||||
|
# du navigateur n'éprouvent plus le projet. C.UTF-8 vient avec la glibc,
|
||||||
|
# sans paquet de langue ; une image qui ne le porterait pas retomberait sur
|
||||||
|
# « C », comme sans la variable.
|
||||||
ETAPES_DEV='
|
ETAPES_DEV='
|
||||||
|
export LANG=C.UTF-8
|
||||||
etape() { local nom="$1"; shift; if "$@"; then echo "=== $nom ok"; else echo "=== $nom ÉCHEC"; return 1; fi; }
|
etape() { local nom="$1"; shift; if "$@"; then echo "=== $nom ok"; else echo "=== $nom ÉCHEC"; return 1; fi; }
|
||||||
cd "$HOME/projet"
|
cd "$HOME/projet"
|
||||||
etape install_dev ./install_dev.sh || exit 1
|
etape install_dev ./install_dev.sh || exit 1
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,27 @@ describe('fichiers web : la plateforme et sa racine (§ 8.8, § 13.1)', () => {
|
||||||
expect(await fs.typeSupport(DOCUMENTS)).toBe('inconnu');
|
expect(await fs.typeSupport(DOCUMENTS)).toBe('inconnu');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sous Linux, Chromium convertit le nom d'une entrée de l'OPFS par les
|
||||||
|
// paramètres régionaux de son processus. Hors UTF-8 — LANG absent,
|
||||||
|
// LC_ALL=C —, un nom hors ASCII ne se convertit pas et désigne le dossier
|
||||||
|
// qui le porte : un fichier y lève TypeMismatchError, un dossier y rend son
|
||||||
|
// parent. Les épreuves qui écrivent un nom accentué n'éprouvent alors plus
|
||||||
|
// le module ; celle-ci, sur l'OPFS seul, nomme la cause.
|
||||||
|
test("l'OPFS de ce Chromium nomme une entrée hors ASCII : un fichier et un dossier accentués se créent et se listent sous leur nom", async () => {
|
||||||
|
const racine = await opfs();
|
||||||
|
const cause = 'Chromium tourne sous des paramètres régionaux qui ne sont pas UTF-8 : lancer les épreuves sous LANG=C.UTF-8';
|
||||||
|
let refus = null;
|
||||||
|
try {
|
||||||
|
await racine.getFileHandle('\u{E9}t\u{E9}.txt', { create: true });
|
||||||
|
} catch (erreur) {
|
||||||
|
refus = erreur.name;
|
||||||
|
}
|
||||||
|
expect(refus, cause).toBeNull();
|
||||||
|
const dossier = await racine.getDirectoryHandle('\u{E9}', { create: true });
|
||||||
|
expect(await dossier.isSameEntry(racine), cause).toBe(false);
|
||||||
|
expect(await nomsOpfs(), cause).toStrictEqual(['\u{E9}', '\u{E9}t\u{E9}.txt']);
|
||||||
|
});
|
||||||
|
|
||||||
test("la racine documents est le dossier gestion_table_tournante_libre de l'OPFS : ce qui s'y écrit s'y lit en UTF-8 sans marque d'ordre d'octets, et ce qui s'y pose se lit et se liste", async () => {
|
test("la racine documents est le dossier gestion_table_tournante_libre de l'OPFS : ce qui s'y écrit s'y lit en UTF-8 sans marque d'ordre d'octets, et ce qui s'y pose se lit et se liste", async () => {
|
||||||
const fs = await preparer();
|
const fs = await preparer();
|
||||||
await fs.creerDossier(DOCUMENTS, 'corbeille');
|
await fs.creerDossier(DOCUMENTS, 'corbeille');
|
||||||
|
|
|
||||||
|
|
@ -57,18 +57,23 @@ function dossierTemporaire() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// test du lanceur, dont chaque épreuve retire à sa fin les dossiers
|
// test du lanceur, dont chaque épreuve retire à sa fin les dossiers
|
||||||
// temporaires qu'elle a ouverts.
|
// temporaires qu'elle a ouverts ; le corps reçoit le contexte de l'épreuve.
|
||||||
function testNettoye(nom, ...suite) {
|
function testNettoye(nom, ...suite) {
|
||||||
const corps = suite.pop();
|
const corps = suite.pop();
|
||||||
test(nom, ...suite, async () => {
|
test(nom, ...suite, async (contexte) => {
|
||||||
try {
|
try {
|
||||||
await corps();
|
await corps(contexte);
|
||||||
} finally {
|
} finally {
|
||||||
for (const dossier of ouverts.splice(0)) rmSync(dossier, { recursive: true, force: true });
|
for (const dossier of ouverts.splice(0)) rmSync(dossier, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ce que dit une commande lancée par spawnSync : l'exception qui l'a
|
||||||
|
// empêchée de démarrer, sinon sa sortie d'erreur. Toujours une chaîne,
|
||||||
|
// seul message que node:assert accepte.
|
||||||
|
const ditPar = (lancee) => String(lancee.error?.message ?? lancee.stderr ?? '');
|
||||||
|
|
||||||
// Charge electron/preload.cjs comme un rendu en bac à sable, contre un
|
// Charge electron/preload.cjs comme un rendu en bac à sable, contre un
|
||||||
// electron dont invoke porte chaque appel au gestionnaire que servir a posé
|
// electron dont invoke porte chaque appel au gestionnaire que servir a posé
|
||||||
// sur ce canal ; arguments et réponse sont copiés comme l'IPC les copie, et
|
// sur ce canal ; arguments et réponse sont copiés comme l'IPC les copie, et
|
||||||
|
|
@ -350,7 +355,14 @@ describe('coquille sur un vrai disque', () => {
|
||||||
testNettoye(
|
testNettoye(
|
||||||
"la lecture par ps (macOS) passe TZ=UTC et LC_ALL=C à la commande : le démarrage inscrit est en temps universel, et un processus d'un autre fuseau lit la séance vivante",
|
"la lecture par ps (macOS) passe TZ=UTC et LC_ALL=C à la commande : le démarrage inscrit est en temps universel, et un processus d'un autre fuseau lit la séance vivante",
|
||||||
{ skip: SOUS_WINDOWS },
|
{ skip: SOUS_WINDOWS },
|
||||||
async () => {
|
async (contexte) => {
|
||||||
|
// L'épreuve joue macOS avec le ps de l'hôte. Sous Linux, la coquille
|
||||||
|
// lit /proc et ne demande pas ps, qu'une image de système minimale ne
|
||||||
|
// porte pas : sans lui, l'épreuve se saute et dit pourquoi.
|
||||||
|
if (spawnSync('ps', ['-p', String(process.pid)]).error?.code === 'ENOENT') {
|
||||||
|
contexte.skip("ps introuvable sur ce système : la lecture du démarrage par ps ne s'y éprouve pas");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const documents = join(dossierTemporaire(), 'Documents');
|
const documents = join(dossierTemporaire(), 'Documents');
|
||||||
mkdirSync(documents, { recursive: true });
|
mkdirSync(documents, { recursive: true });
|
||||||
const fichiers = creerFichiers({
|
const fichiers = creerFichiers({
|
||||||
|
|
@ -366,7 +378,7 @@ describe('coquille sur un vrai disque', () => {
|
||||||
env: { ...process.env, LC_ALL: 'C', TZ: 'UTC' },
|
env: { ...process.env, LC_ALL: 'C', TZ: 'UTC' },
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
});
|
});
|
||||||
assert.equal(enUtc.status, 0, enUtc.stderr);
|
assert.equal(enUtc.status, 0, ditPar(enUtc));
|
||||||
assert.equal(pose.demarrage, enUtc.stdout.trim());
|
assert.equal(pose.demarrage, enUtc.stdout.trim());
|
||||||
|
|
||||||
// Un second processus, dans un fuseau qui n'est pas celui de
|
// Un second processus, dans un fuseau qui n'est pas celui de
|
||||||
|
|
@ -387,7 +399,7 @@ describe('coquille sur un vrai disque', () => {
|
||||||
},
|
},
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
});
|
});
|
||||||
assert.equal(relu.status, 0, relu.stderr);
|
assert.equal(relu.status, 0, ditPar(relu));
|
||||||
assert.deepEqual(JSON.parse(relu.stdout), {
|
assert.deepEqual(JSON.parse(relu.stdout), {
|
||||||
fuseau: autre,
|
fuseau: autre,
|
||||||
tenu: { pris: false, seance: 'seance-a', depuis: pose.depuis, vivant: true },
|
tenu: { pris: false, seance: 'seance-a', depuis: pose.depuis, vivant: true },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue