JavaScriptの仕様と実装をつなぐtest262とWebKitにおけるその運用
2024/09/07
Web Developer Conference 2024
自己紹介
SUZUKI Sosuke
一着につき$10 Prettierに寄付されます
話すこと
皆さん普段JavaScriptをプロダクトを作るための手段だと思っているかもしれません。
今日は「JavaScript自体を作る」という視点で考えてみましょう。
次のトピックについて話します。
JavaScriptという
プログラミング言語
JavaScriptは大きい?
JavaScriptの仕様は巨大で複雑。ES2024のPDF版は816ページ。
大きさや複雑さの感じ方は人それぞれだが...。
普通の人間が仕様のすべてを把握して、動作を理解できるかと言われると...難しい。
ただ、巨大で複雑なプログラミング言語というもの自体は、他にもたまにある(例 C++)。
ちなみにC++20の仕様書は1853ページ(!)。
JavaScriptはWebブラウザに搭載されている
JavaScriptは主要なWebブラウザに搭載されている。現在これはWebAssemblyを除けば唯一のはず。これによって、いくつかの特徴が生まれている。
Webブラウザは後方互換性を重視する。Webブラウザの一部であるJavaScriptも当然。
サービス提供者ではなく、エンドユーザがブラウザ(処理系)を選択できる(「あのブラウザ好きじゃないからこっちのブラウザを使おう」)→すべての処理系が全く同じように動作することが期待されている。
JavaScriptは仕様と実装が明確に分離されている
JavaScriptは仕様と実装が明確に分かれている。
ECMA InternationalのTC39が仕様を決め、各JavaScript処理系の開発者たちがそれを実装する。メンバーの重複は一部あるが、団体としては別。
ブラウザは速く、JavaScriptの実装は難しい
現代Webブラウザは速さを追求している。
Webブラウザの一部であるJavaScript処理系も速さのために尽力している。JITコンパイル、並行・並列GC、インラインキャッシュ、など。これらは実装を難しくする。
JavaScriptの範囲は広がっている
ECMA-402(国際化API)やTemporal(イケてるDateみたいなやつ)など、JavaScriptの仕様の幅は広がっている。
これらの仕様は、いくつかの別の仕様やソフトウェアに関連する:
Unicode、ICU、Unicode CLDR、RFC 3339、RFC 9557、など。
JavaScriptというプログラミング言語
JavaScriptの開発を支えているtest262について
JavaScriptというプログラミング言語
こういうソフトウェアを開発・維持しなければならないとき、テストがあると助かる。
TC39の公式テストスイート - test262
test262は、TC39(JavaScriptの標準化を行っている団体)が提供するJavaScript処理系の動作をテストするためのテストスイート。
JavaScript処理系はtest262を実行し、自分が仕様に沿っているかをテストする。
https://github.com/tc39/test262 で管理されている。誰も見れるし、誰でもPull Requestを作れる。
例: Array.prototype.atのテスト
Array#atはECMAScript2022で追加された配列の後ろから値を取れる関数:
const array = [“foo”, “bar”, “baz”];
array.at(0); // “foo”
array.at(1); // “bar”
array.at(-1); // “baz”
例: Array.prototype.atのテスト
Array.prototype.toArrayに整数以外の値を渡したときの動作のテスト。
ファイルの頭にはライセンスとメタデータがある。
例: Array.prototype.atのテスト
次は関数の存在チェック。
assert.sameValueのようなtest262内で使える関数をharnessという。
例: Array.prototype.atのテスト
Array#atではあらゆる値が整数に変換される。
false null undefined “” 関数 配列 は 0 に
true 非空の文字列は 1 に
変換される。
という動作をテストしている。
標準化
プロセスにおけるtest262の役割
TC39の標準化プロセス
JavaScriptは日々進化するプログラミング言語。一年に一回仕様が更新される。
TC39が新しい仕様を策定し、JavaScript処理系開発者がそれを実装する。
具体的には、どのように仕様を決めていて、どの段階で実装されるのだろう?
プロポーザルのステージ
仕様に新しい機能を追加するときはプロポーザルを提出する。プロポーザルは進捗状況に応じてステージ0, 1, 2, 2,7, 3, 4 の6段階に分けられる。
ステージ0 | ただプロポーザルが作成された状態。すべてのプロポーザルは少なくともこの状態にある。 |
ステージ1 | ユースケースを示し、TC39が取り組む価値があるものだと認められるとステージ1。責任者が決まる。 |
ステージ2 | 具体的なインタフェースが示され、仕様書と同じ形式でdraftを書くとステージ2。上手くいけばこのままの形で入ることもありうるし、大幅に変わるかもしれない。 |
ステージ2.7 | draftの仕様がレビュワーによってレビューされるとステージ2.7。test262のテストが追加される。ここまでくると概ね仕様が固まっていて、テストや実装からのフィードバックなしには仕様は変わらない。 |
ステージ3 | テストが十分にあって、もう実装に着手できる状態になるとステージ3。ブラウザが実装を始める。 |
ステージ4 | 2つ以上の処理系で実装され、それらがtest262をパスするとステージ4。ステージ4になった提案は次の年のJavaScriptの仕様にはいることが確定する。 |
WebKitでのtest262運用
WebKit
WebKitはSafariのブラウザエンジン。Safari以外にもAppleのいくつかのソフトウェア(AppStoreとかiTunesとかそうらしい)や、PlayStationなどで使われている。
WebKitのJavaScript処理系はJavaScriptCore(=JSC)。JSCのソースコードはWebKitのリポジトリ( https://github.com/webkit/webkit )に完全に含まれている。
WebKitにおけるtest262
WebKitのリポジトリ内にtest262がそのまま含まれている。サブモジュールではなくtc39/test262をクローンしたものがそのまま入っている( https://github.com/WebKit/WebKit/tree/main/JSTests/test262 )。
WebKit内のtest262は、月に一回手動で更新している。/Tools/Scripts/test262-import というスクリプトで最新のtest262をクローンできる。
WebKitのtest262ランナー
./Tools/Scripts/test262-runner というテストランナーのスクリプトがある(Perl)。
Bocoupという会社が中心になって開発したらしい
(New Test262 Import and Runner in WebKit https://www.bocoup.com/blog/new-test262-import-and-runner-in-webkit)
test262-runnerの設定 - 既知の失敗するテスト一覧
test262-runnerは「既知の失敗するテストの一覧」をexpectations.yamlとして管理している。
JSCはtest262を100%通過しているわけではない。だからといって、test262-runnerの実行がずっと失敗しているのかというと、そうではない。
expectations.yamlに「どのテストケースが、どういうエラーメッセージで失敗しているのか」が記録されている。そのとおりに失敗した場合はtest262-runnerの実行は成功する。
test262-runnerの設定 - 既知の失敗するテスト一覧
RegExp.prototype[Symbol.match]のテストの一つが失敗していることを表す。
strictモードと非strictモードでそれぞれ実行している(ものによっては動作が変わるため)。
RegExp.prototype[Symbol.match]の動作を修正した状態でtest262-runnerを実行するとexpectations.yamlからこの項目が削除される。逆にソースコードの変更によって新たな失敗が生まれた場合は、それが追記される。
expectations.yamlの一部を抜粋したもの
test262-runnerの設定 - スキップするテスト
test262-runnerはスキップするテストをconfig.yamlで管理している。
まだ実装されていない機能や、OSのバージョンなどによって失敗したり成功したりするテストをスキップしている。
国際化APIはシステムにインストールされているICUに依存している。そのため、OSのバージョンによってはテストが通らない、ということがある。
config.yamlの一部を抜粋したもの
test262のCI
WebKitではPull RequestごとにもCIが実行されるが、test262-runnerはそこには含まれていない。
mainにマージされたあとで、macOS SonomaとVenturaのマシンで実行されている。
Apple-Sonoma-AppleSilicon-Release-Test262-Tests
https://build.webkit.org/#/builders/936
Apple-Ventura-Debug-Test262-Tests
https://build.webkit.org/#/builders/1025
Apple-Ventura-Release-Test262-Tests
新しいプロポーザルや仕様の変更の実装の流れ
もちろん実装する人によるけど、自分なら
TDDっぽい?
おまけ:
test262の誤り・
修正
test262は間違っていることがある
test262を書く人は実際の動く処理系なしで仕様だけ見てテストを記述することになる。お約束的なテストはコピペで作られたりもしている。
だから、間違っていることがある。実装側で使ってみて初めて気がつくことがある。
仕様を良く読んでいるとtest262でカバーされていないケースを見つけることもある。
Pull Requestを作ると大体マージされる。
おまけ: test262に
含まれているもの
test262の分類
1. language
基本的な言語機能のテスト。if文やfor文そのもの、など。
languageの中でもexpressionやstatementなどの分類がある。
2. built-ins
ビルトイン関数のテスト。さっきのArray.prototype.atのテストはこれに該当する。
(built-ins/Array/prototype/at/index-non-numeric-argument-tointeger.js)
3. intl402
ECMA-402で決まっている国際化API(Intl.DurationFormat とか)のテスト。
test262の分類
4. stating
まだ策定途中のプロポーザルのためのテスト。
5. annexB
仕様書のAnnexBに含まれる仕様のテスト。AnnexBはWebブラウザの互換性のため
にのみ残されている仕様。
おまけ: 各処理系の
test262通過状況
各処理系のtest262通過状況
test262はエッジケースのためのテストが多い。
V8もSpiderMonkeyもJavaScriptCoreもtest262通過率は100%ではない。
https://test262.fyi で確認すると...
まとめ
まとめ
JavaScriptは大きくて難しい。だから、テストがあると嬉しい。
TC39の人たちがtest262を管理している。
実装する人は、仕様を見ながらテストが通るように実装している。