Aller au contenu

Tests de l'oracle

Chaque décision d'implémentation dans Phasis dépend d'une seule question : V8 produit-il le même résultat ?

JavaScript a une sémantique subtile — NaN !== NaN, -0 === 0, [1] + 1 === "11", l'ordre exact d'énumération des propriétés et le moment d'exécution des microtâches Promise. Reconstituer ces détails en lisant la spécification est lent et source d'erreurs. Phasis évite cela en traitant Node.js (V8) comme un oracle : écrivez un scénario JS, exécutez-le avec V8 et capturez le résultat de référence ; exécutez-le ensuite avec Phasis, capturez à nouveau le résultat, puis comparez-les.

Code
1. SETUP    → a JavaScript source file or snippet
2. ORACLE   → Node.js executes it, output captured as truth
3. ACTUAL   → Phasis executes it, output captured
4. COMPARE  → oracle vs actual, diff measures the gap

Deux niveaux de test

Niveau 1 — Scénarios personnalisés

Chaque scénario de scenarios/ est un petit programme JS dont le résultat est connu. Exécutez un scénario seul :

Shell
./bin/test-scenario operators/arithmetic

Exécutez-les tous, une seule catégorie, ou en parallèle :

Shell
./bin/test-regression
./bin/test-regression --jobs 4
./bin/test-regression --category expressions

Actualisez l'oracle (e.g après une mise à jour de version Node.js) :

Shell
./bin/oracle --refresh operators/arithmetic

Les scénarios sont organisés par chapitre ECMAScript : littéraux, opérateurs, variables, flux de contrôle, fonctions, objets, tableaux, classes, éléments intégrés, erreurs, interopérabilité et cas limites.

Niveau 2 — test262

La suite officielle de conformité TC39 est récupérée comme sous-module git sous test262/. Chaque test se vérifie lui-même : il réussit s'il ne déclenche pas d'exception et échoue dans le cas contraire (ou l'inverse pour les tests negative). Le harnais charge assert.js et sta.js avant chaque test.

Shell
./bin/test262                                    # full suite
./bin/test262 --category built-ins/Array         # subset
./bin/test262 --jobs 4                           # parallel
./bin/test262 --failures                         # one line per failing test
./bin/compat-report                              # full COMPAT.md + compat.json

L'instantané de la suite complète se trouve dans COMPAT.md et est régénéré par bin/compat-report. Dans la CI, le workflow compat-matrix.yml l'exécute en lots et valide le résultat ; ce workflow est lancé manuellement, pas à chaque push.

Vérification locale avant le push

./bin/verify-all exécute les contrôles qualité imposés par la CI :

Code
=== PHPStan ===                  level 6, zero errors
=== Code Standards ===           PHPCS, zero warnings
=== PHPUnit ===                  unit suite passes
=== Oracle Regression ===        scenarios under `scenarios/` pass

Cela ne suffit pas à lui seul : il faut aussi échantillonner test262 pour toute modification touchant l'analyseur, l'interpréteur ou les éléments intégrés. Pour chaque pull request, la CI exécute PHPStan, PHPCS, la suite unitaire et la régression de l'oracle ; les suites WPT et de paquets populaires s'exécutent si la pull request modifie src/ ou leurs fixtures. La matrice test262 complète ne fait pas partie de la CI des pull requests : échantillonnez donc localement les catégories concernées.

Pourquoi cela fonctionne

Prendre V8 comme référence élimine presque toute lecture de la spécification pendant le développement. Tout désaccord avec V8 révèle un bogue Phasis — jamais une ambiguïté de la spécification qui pourrait se discuter. Une fois le correctif intégré, les tests test262 correspondants deviennent des tests de régression permanents.

Le taux de réussite de 100 % découle de cette méthode : chaque PR est évaluée par rapport à V8, la CI rejette les régressions dans l'oracle, WPT et les suites de paquets populaires, et la matrice test262 confirme le résultat complet lorsqu'elle est exécutée.