1 of 39

JavaScriptの仕様と実装をつなぐtest262とWebKitにおけるその運用

2024/09/07

Web Developer Conference 2024

2 of 39

自己紹介

SUZUKI Sosuke

  • ユビー株式会社 プロダクト開発エンジニア
  • 筑波大学 B4
  • WebKit コミッター
  • Prettier メンテナー

3 of 39

https://prettier.io

一着につき$10 Prettierに寄付されます

4 of 39

話すこと

皆さん普段JavaScriptをプロダクトを作るための手段だと思っているかもしれません。

今日は「JavaScript自体を作る」という視点で考えてみましょう。

次のトピックについて話します。

  1. JavaScriptというプログラミング言語
  2. JavaScriptの開発を支えているtest262
  3. 標準化におけるtest262の役割
  4. WebKit(JavaScriptCore)でのtest262の使われ方

5 of 39

JavaScriptという

プログラミング言語

6 of 39

JavaScriptは大きい?

JavaScriptの仕様は巨大で複雑。ES2024のPDF版は816ページ。

大きさや複雑さの感じ方は人それぞれだが...。

普通の人間が仕様のすべてを把握して、動作を理解できるかと言われると...難しい。

ただ、巨大で複雑なプログラミング言語というもの自体は、他にもたまにある(例 C++)。

ちなみにC++20の仕様書は1853ページ(!)。

7 of 39

JavaScriptはWebブラウザに搭載されている

JavaScriptは主要なWebブラウザに搭載されている。現在これはWebAssemblyを除けば唯一のはず。これによって、いくつかの特徴が生まれている。

Webブラウザは後方互換性を重視する。Webブラウザの一部であるJavaScriptも当然。

サービス提供者ではなく、エンドユーザがブラウザ(処理系)を選択できる(「あのブラウザ好きじゃないからこっちのブラウザを使おう」)→すべての処理系が全く同じように動作することが期待されている。

8 of 39

JavaScriptは仕様と実装が明確に分離されている

JavaScriptは仕様と実装が明確に分かれている。

ECMA InternationalのTC39が仕様を決め、各JavaScript処理系の開発者たちがそれを実装する。メンバーの重複は一部あるが、団体としては別。

9 of 39

ブラウザは速く、JavaScriptの実装は難しい

現代Webブラウザは速さを追求している。

Webブラウザの一部であるJavaScript処理系も速さのために尽力している。JITコンパイル、並行・並列GC、インラインキャッシュ、など。これらは実装を難しくする。

10 of 39

JavaScriptの範囲は広がっている

ECMA-402(国際化API)やTemporal(イケてるDateみたいなやつ)など、JavaScriptの仕様の幅は広がっている。

これらの仕様は、いくつかの別の仕様やソフトウェアに関連する:

Unicode、ICU、Unicode CLDR、RFC 3339、RFC 9557、など。

11 of 39

JavaScriptというプログラミング言語

  • そもそも仕様が大きくて複雑である
  • パフォーマンスを追求しており、実装が複雑である
  • 仕様に忠実で、すべての処理系が同じように動くことが求められる
  • 国際化APIやTemporalなどの難しい仕様が広がっている

12 of 39

JavaScriptの開発を支えているtest262について

13 of 39

JavaScriptというプログラミング言語

  • そもそも仕様が大きくて複雑である
  • パフォーマンスを追求しており、実装が複雑である
  • 仕様に忠実で、すべての処理系が同じように動くことが求められる
  • 国際化APIやTemporalなどの難しい仕様が広がっている

こういうソフトウェアを開発・維持しなければならないとき、テストがあると助かる。

14 of 39

TC39の公式テストスイート - test262

test262は、TC39(JavaScriptの標準化を行っている団体)が提供するJavaScript処理系の動作をテストするためのテストスイート。

JavaScript処理系はtest262を実行し、自分が仕様に沿っているかをテストする。

https://github.com/tc39/test262 で管理されている。誰も見れるし、誰でもPull Requestを作れる。

15 of 39

例: Array.prototype.atのテスト

Array#atはECMAScript2022で追加された配列の後ろから値を取れる関数:

const array = [“foo”, “bar”, “baz”];

array.at(0); // “foo”

array.at(1); // “bar”

array.at(-1); // “baz”

16 of 39

例: Array.prototype.atのテスト

Array.prototype.toArrayに整数以外の値を渡したときの動作のテスト。

ファイルの頭にはライセンスとメタデータがある。

17 of 39

例: Array.prototype.atのテスト

次は関数の存在チェック。

assert.sameValueのようなtest262内で使える関数をharnessという。

18 of 39

例: Array.prototype.atのテスト

Array#atではあらゆる値が整数に変換される。

false null undefined “” 関数 配列 は 0 に

true 非空の文字列は 1 に

変換される。

という動作をテストしている。

19 of 39

標準化

プロセスにおけるtest262の役割

20 of 39

TC39の標準化プロセス

JavaScriptは日々進化するプログラミング言語。一年に一回仕様が更新される。

TC39が新しい仕様を策定し、JavaScript処理系開発者がそれを実装する。

具体的には、どのように仕様を決めていて、どの段階で実装されるのだろう?

21 of 39

プロポーザルのステージ

仕様に新しい機能を追加するときはプロポーザルを提出する。プロポーザルは進捗状況に応じてステージ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の仕様にはいることが確定する。

22 of 39

WebKitでのtest262運用

23 of 39

WebKit

WebKitはSafariのブラウザエンジン。Safari以外にもAppleのいくつかのソフトウェア(AppStoreとかiTunesとかそうらしい)や、PlayStationなどで使われている。

WebKitのJavaScript処理系はJavaScriptCore(=JSC)。JSCのソースコードはWebKitのリポジトリ( https://github.com/webkit/webkit )に完全に含まれている。

24 of 39

WebKitにおけるtest262

WebKitのリポジトリ内にtest262がそのまま含まれている。サブモジュールではなくtc39/test262をクローンしたものがそのまま入っている( https://github.com/WebKit/WebKit/tree/main/JSTests/test262 )。

WebKit内のtest262は、月に一回手動で更新している。/Tools/Scripts/test262-import というスクリプトで最新のtest262をクローンできる。

25 of 39

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)

26 of 39

test262-runnerの設定 - 既知の失敗するテスト一覧

test262-runnerは「既知の失敗するテストの一覧」をexpectations.yamlとして管理している。

JSCはtest262を100%通過しているわけではない。だからといって、test262-runnerの実行がずっと失敗しているのかというと、そうではない。

expectations.yamlに「どのテストケースが、どういうエラーメッセージで失敗しているのか」が記録されている。そのとおりに失敗した場合はtest262-runnerの実行は成功する。

27 of 39

test262-runnerの設定 - 既知の失敗するテスト一覧

RegExp.prototype[Symbol.match]のテストの一つが失敗していることを表す。

strictモードと非strictモードでそれぞれ実行している(ものによっては動作が変わるため)。

RegExp.prototype[Symbol.match]の動作を修正した状態でtest262-runnerを実行するとexpectations.yamlからこの項目が削除される。逆にソースコードの変更によって新たな失敗が生まれた場合は、それが追記される。

expectations.yamlの一部を抜粋したもの

28 of 39

test262-runnerの設定 - スキップするテスト

test262-runnerはスキップするテストをconfig.yamlで管理している。

まだ実装されていない機能や、OSのバージョンなどによって失敗したり成功したりするテストをスキップしている。

国際化APIはシステムにインストールされているICUに依存している。そのため、OSのバージョンによってはテストが通らない、ということがある。

config.yamlの一部を抜粋したもの

29 of 39

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

https://build.webkit.org/#/builders/1027

30 of 39

新しいプロポーザルや仕様の変更の実装の流れ

もちろん実装する人によるけど、自分なら

  1. test262-import
  2. test262-runner を実行(未実装なので全部失敗する)
  3. test262-runner が通るように実装
  4. リファクタリング

TDDっぽい?

31 of 39

おまけ:

test262の誤り・

修正

32 of 39

test262は間違っていることがある

test262を書く人は実際の動く処理系なしで仕様だけ見てテストを記述することになる。お約束的なテストはコピペで作られたりもしている。

だから、間違っていることがある。実装側で使ってみて初めて気がつくことがある。

仕様を良く読んでいるとtest262でカバーされていないケースを見つけることもある。

Pull Requestを作ると大体マージされる。

33 of 39

おまけ: test262に

含まれているもの

34 of 39

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 とか)のテスト。

35 of 39

test262の分類

4. stating

まだ策定途中のプロポーザルのためのテスト。

5. annexB

仕様書のAnnexBに含まれる仕様のテスト。AnnexBはWebブラウザの互換性のため

にのみ残されている仕様。

36 of 39

おまけ: 各処理系の

test262通過状況

37 of 39

各処理系のtest262通過状況

test262はエッジケースのためのテストが多い。

V8もSpiderMonkeyもJavaScriptCoreもtest262通過率は100%ではない。

https://test262.fyi で確認すると...

38 of 39

まとめ

39 of 39

まとめ

JavaScriptは大きくて難しい。だから、テストがあると嬉しい。

TC39の人たちがtest262を管理している。

実装する人は、仕様を見ながらテストが通るように実装している。