Oracle-Tests
Jede Implementierungsentscheidung in Phasis hängt von einer Frage ab: Erzeugt V8 dieselbe Ausgabe?
JavaScript hat subtile Semantik – NaN !== NaN, -0 === 0, [1] + 1 === "11", die genaue Reihenfolge der Property-Aufzählung und der Zeitpunkt von Promise-Microtasks. Diese durch Lesen der Spezifikation erneut herzuleiten, ist langsam und fehleranfällig. Phasis umgeht das, indem es Node.js (V8) als Oracle behandelt: Schreibe ein JS-Szenario, führe es in V8 aus und speichere die Ausgabe als Wahrheit; führe es anschließend über Phasis aus, speichere auch diese Ausgabe und vergleiche beide.
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
Zwei Testebenen
Ebene 1 — Eigene Szenarien
Jedes Szenario in scenarios/ ist ein kleines JS-Programm mit bekannter Ausgabe. So führst du ein einzelnes Szenario aus:
./bin/test-scenario operators/arithmetic
Führe alle Szenarien, nur eine Kategorie oder mehrere parallel aus:
./bin/test-regression
./bin/test-regression --jobs 4
./bin/test-regression --category expressions
Oracle aktualisieren (e.g. nach einem Versionsupdate von Node.js):
./bin/oracle --refresh operators/arithmetic
Die Szenarien sind nach ECMAScript-Kapiteln geordnet: Literale, Operatoren, Variablen, Kontrollfluss, Funktionen, Objekte, Arrays, Klassen, integrierte Funktionen, Fehler, Interoperabilität und Sonderfälle.
Ebene 2 — test262
Die offizielle TC39-Konformitätssuite ist als git-Submodul unter test262/ ausgecheckt. Jeder Test prüft sich selbst: Er besteht, wenn er keinen Fehler auslöst, und schlägt fehl, wenn er es tut (bei negative-Tests umgekehrt). Vor jedem Test lädt der Test-Harness assert.js und sta.js.
./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
Der Snapshot der vollständigen Suite liegt in COMPAT.md und wird von bin/compat-report neu erzeugt. In CI führt der compat-matrix.yml-Workflow die Suite in Shards aus und committet das Ergebnis. Dieser Workflow wird manuell gestartet, nicht bei jedem Push.
Lokale Prüfung vor dem Push
./bin/verify-all führt die von CI erzwungene Qualitätsprüfung aus:
=== PHPStan === level 6, zero errors
=== Code Standards === PHPCS, zero warnings
=== PHPUnit === unit suite passes
=== Oracle Regression === scenarios under `scenarios/` pass
Das allein reicht nicht aus – bei jeder Änderung am Parser, Interpreter oder an integrierten Funktionen müssen auch betroffene test262-Tests stichprobenartig ausgeführt werden. Bei jedem Pull Request führt CI PHPStan, PHPCS, die Unit-Suite und die Oracle-Regression aus; die WPT- und Popular-Package-Suites laufen, wenn ein Pull Request src/ oder deren Fixtures ändert. Die vollständige test262-Matrix gehört nicht zu den Pull-Request-Prüfungen in CI; prüfe die betroffenen Kategorien daher lokal stichprobenartig.
Warum das funktioniert
V8 als Quelle der Wahrheit zu behandeln, erspart beim Entwickeln fast das gesamte Lesen der Spezifikation. Eine Abweichung von V8 weist immer auf einen Phasis-Fehler hin – niemals auf eine auslegbare Mehrdeutigkeit in der Spezifikation. Sobald eine Korrektur eingeht, werden die zugehörigen test262-Einträge zu dauerhaften Regressionstests.
Die Disziplin dahinter ermöglicht eine Erfolgsquote von 100 %: Jeder PR wird mit V8 verglichen, CI weist Regressionen in der Oracle-, WPT- und Popular-Package-Suite zurück, und die test262-Matrix bestätigt bei ihrer Ausführung das Ergebnis der vollständigen Suite.