Към съдържанието

Тестване с оракул

Всяко решение за имплементация в Phasis зависи от един въпрос: дава ли V8 същия изход?

JavaScript има фини семантични особености — NaN !== NaN, -0 === 0, [1] + 1 === "11", точният ред на изброяване на свойствата, моментът на изпълнение на микрозадачите Promise. Повторното им извеждане чрез четене на спецификацията е бавно и податливо на грешки. Phasis заобикаля това, като използва Node.js (V8) като оракул: напишете сценарий на JS, изпълнете го през V8 и запишете изхода като еталон; изпълнете го през Phasis, запишете отново изхода и ги сравнете.

Код
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

Две нива на тестване

Ниво 1 — персонализирани сценарии

Всеки сценарий в scenarios/ е малка програма JS с известен изход. Изпълнете един сценарий:

Обвивка
./bin/test-scenario operators/arithmetic

Изпълнете всички, само една категория или ги изпълнете паралелно:

Обвивка
./bin/test-regression
./bin/test-regression --jobs 4
./bin/test-regression --category expressions

Обновете оракула (e.g, например след актуализация на версията Node.js):

Обвивка
./bin/oracle --refresh operators/arithmetic

Сценариите са организирани по глави от ECMAScript: литерали, оператори, променливи, контролен поток, функции, обекти, масиви, класове, вградени функции, грешки, оперативна съвместимост и гранични случаи.

Ниво 2 — test262

Официалният набор за съответствие на TC39 е изтеглен като git подмодул в test262/. Всеки тест проверява сам себе си: преминава, ако не хвърли грешка, и се проваля, ако я хвърли (или обратното за тестовете negative). Ханесът зарежда assert.js и 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

Снимката на пълния набор се намира в COMPAT.md и се генерира отново чрез bin/compat-report. В CI работният поток compat-matrix.yml изпълнява тестовете на части и записва резултата; стартира се ръчно, а не при всяко изпращане.

Локална проверка преди изпращане

./bin/verify-all изпълнява проверката за качество, която CI налага:

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

Само по себе си това не е достатъчно — test262 също трябва да се изпълни за извадка при всяка промяна в парсера, интерпретатора или вградените функции. При всяка заявка за сливане CI изпълнява PHPStan, PHPCS, набора от unit тестове и регресионните тестове на оракула; WPT и наборите за популярни пакети се изпълняват, когато заявката за сливане променя src/ или съответните им fixture файлове. Пълната матрица test262 не е част от CI за заявките за сливане, затова локално изпълнявайте засегнатите категории на извадка.

Защо това работи

Когато V8 се приема за източник на истината, почти отпада нуждата от четене на спецификацията по време на разработката. Несъгласието с V8 винаги показва грешка в Phasis — никога спорна неяснота в спецификацията. След като поправката бъде включена, съответните тестове от test262 стават постоянни регресионни тестове.

Процентът на успешно преминалите тестове 100 % е резултат от тази дисциплина: всяка заявка за сливане се оценява спрямо V8, CI отхвърля регресии в наборите oracle, WPT и popular-package, а матрицата test262 потвърждава резултата от целия набор, когато бъде изпълнена.