Vai al contenuto

Test con oracle

Ogni decisione di implementazione in Phasis dipende da una domanda: V8 produce lo stesso output?

JavaScript ha semantiche sottili: NaN !== NaN, -0 === 0, [1] + 1 === "11", l’ordine esatto di enumerazione delle proprietà e il momento in cui vengono eseguite le microtask Promise. Ricavare questi dettagli leggendo la specifica è lento e soggetto a errori. Phasis aggira il problema usando Node.js (V8) come oracle: si scrive uno scenario JS, lo si esegue tramite V8 e si acquisisce l’output come verità; poi lo si esegue tramite Phasis, si acquisisce di nuovo l’output e lo si confronta.

Codice
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

Due livelli di test

Livello 1 — Scenari personalizzati

Ogni scenario in scenarios/ è un piccolo programma JS con un output noto. Eseguite un singolo scenario:

Shell
./bin/test-scenario operators/arithmetic

Eseguiteli tutti, una sola categoria oppure in parallelo:

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

Aggiornate l’oracle (e.g, ad esempio dopo un aggiornamento di versione di Node.js):

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

Gli scenari sono organizzati per capitolo ECMAScript: valori letterali, operatori, variabili, flusso di controllo, funzioni, oggetti, array, classi, funzioni integrate, errori, interoperabilità e casi limite.

Livello 2 — test262

La suite ufficiale di conformità TC39 è presente come sottomodulo git in test262/. Ogni test si verifica da sé: ha esito positivo se non genera un’eccezione e negativo se la genera (o viceversa per i test negative). L’harness carica assert.js e sta.js prima di ogni 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

Lo snapshot della suite completa si trova in COMPAT.md ed è rigenerato da bin/compat-report. Nella CI il workflow compat-matrix.yml lo esegue suddividendolo in shard e salva il risultato; viene avviato manualmente, non a ogni push.

Controllo locale prima del push

./bin/verify-all esegue i controlli di qualità imposti dalla CI:

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

Da solo non basta: occorre anche eseguire un campione di test262 per ogni modifica che tocca il parser, l’interprete o le funzioni integrate. A ogni pull request, la CI esegue PHPStan, PHPCS, la suite di unit test e la regressione oracle; le suite WPT e popular vengono eseguite quando la pull request modifica src/ o i relativi fixture. La matrice test262 completa non fa parte della CI delle pull request, quindi eseguite localmente un campione delle categorie interessate.

Perché funziona

Usare V8 come fonte di verità elimina quasi tutta la necessità di leggere la specifica durante lo sviluppo. Ogni divergenza da V8 indica un bug di Phasis, mai un’ambiguità della specifica su cui discutere. Una volta applicata una correzione, le relative voci test262 diventano test di regressione permanenti.

Il tasso di superamento del 100 % deriva da questa disciplina: ogni PR viene valutata rispetto a V8, la CI rifiuta le regressioni nelle suite oracle, WPT e popular e la matrice test262 conferma il risultato della suite completa quando viene eseguita.