miniflare環境のテストを日本語で絞れるようになりました
@cloudflare/vitest-pool-workers
のリリース0.8.37より、テストケースを非ASCII文字でフィルタできるようになりました。
このようなテストスイートに対し……
describe('test sample', () => {
test('/⁄*イ`^ᗜ^リ', () => {
expect(1).toBe(1)
})
test('。。(dイロ-ロリ。。', () => {
expect(1).toBe(2)
})
})
引数 -t
でパターンを渡した際、テストケースが正常にフィルタされるようになります。
npx vitest src/sample.test.ts -t '/⁄\*イ`^ᗜ^リ'
% npx vitest src/sample.test.ts -t '/⁄\*イ`^ᗜ^リ'
DEV v3.1.4 /path/to/repo
[vpw:info] Starting isolated runtimes for vitest.config.mjs...
✓ src/sample.test.ts (2 tests | 1 skipped) 9ms
✓ test sample > /⁄*イ`^ᗜ^リ 5ms
↓ test sample > 。。(dイロ-ロリ。。
Test Files 1 passed (1)
Tests 1 passed | 1 skipped (2)
Start at 19:51:39
Duration 457ms (transform 41ms, setup 105ms, collect 1ms, tests 9ms, environment 0ms, prepare 73ms)
以下、発生していた問題とその原因、解決策をまとめました。
概要
miniflare
はCloudflare環境のシミュレータです[1]。Cloudflare Workersのアプリをwrangler dev
で起動するとき、内部ではこの子が動いています。
KVやD1, Durable Objectsなど、Workersで使える様々な機能のシミュレーションをサポートしており、非常に便利です。
この miniflare
に対してvitestとの統合を提供するパッケージが @cloudflare/vitest-pool-workers
です[2]。これにより、テストケースをWorkersランタイム内で実行できるようになります。
問題
発生していた問題を整理します。
前提: テストスイートの命名
vitestのテストスイートには自由な名前をつけることができます。
describe('test sample', ()=>{
test('test 1', ()=>{
expect(1).toEqual(1)
})
})
また、実行するテストケースは引数 -t
でフィルタできます。
npx vitest /path/to/file.ts -t 'sample'
勿論、非ASCII文字を含むスイート名でも同様の操作が可能です。
describe('テストサンプル', ()=>{
test('テストケース1', ()=>{
expect(1).toEqual(1)
})
})
npx vitest /path/to/file.ts -t 'サンプル'
テストをフィルタできない
しかしvitest-pool-workers
を使ってテストを実行する場合、後者のようなテストをフィルタしようとすると……
TypeError: Cannot convert argument to a ByteString because the character at index 1404 has a value of 21336 which is greater than 255.
❯ webidl.converters.ByteString ../node_modules/undici/lib/fetch/webidl.js:431:13
❯ Object.record<ByteString, ByteString> ../node_modules/undici/lib/fetch/webidl.js:281:28
❯ webidl.converters.HeadersInit ../node_modules/undici/lib/fetch/headers.js:579:63
❯ Object.RequestInit ../node_modules/undici/lib/fetch/webidl.js:370:17
❯ new Request ../node_modules/undici/lib/fetch/request.js:54:30
❯ new _Request ../node_modules/@cloudflare/vitest-pool-workers/node_modules/miniflare/dist/src/index.js:1879:5
❯ #fetcherFetchCall ../node_modules/@cloudflare/vitest-pool-workers/node_modules/miniflare/dist/src/index.js:12025:21
❯ #call ../node_modules/@cloudflare/vitest-pool-workers/node_modules/miniflare/dist/src/index.js:11943:71
❯ Proxy.fetch ../node_modules/@cloudflare/vitest-pool-workers/node_modules/miniflare/dist/src/index.js:11933:34
❯ runTests ../node_modules/@cloudflare/vitest-pool-workers/dist/pool/index.mjs:1517:26
よくわからないエラーが出て実行できない……という問題がありました。
原因
この問題は、テストのコンテキストをワーカー(テストを実行する環境, ≠Workers)へ正しく受け渡せていないことが原因で発生していました。
コンテキストのやりとり
workers-sdk
のコードを見ると、関数 runTests
内部でワーカーのコンテキストを MF-Vitest-Worker-Data
というリクエストヘッダに乗せて送信している箇所があります。
コンテキストはここで定義されています。737行目の config
オブジェクトがメタ情報を保持しており、-t
で与えたフィルタパターンは RegExp
オブジェクトとして プロパティ testNamePattern
に載っているようです(デバッガで確認)。
生成されたコンテキストはdevalue
によるシリアライズを経てリクエストヘッダに載り、ワーカーに送信されます。これを受け取ったワーカーは同じく devalue
によってデシリアライズを行い、コンテキストを復元します。
シリアライズの問題
先述の通り、フィルタパターンは config.testNamePattern
に格納されています。devalueのコードによれば、RegExp
オブジェクトは ['RegExp', obj.source, obj.flags?]
という型のtupleに変換されるようなので……
たとえば テスト.+
のようなパターンが testNamePattern
に渡された場合、シリアライズ結果は以下のようになります。
[
'RegExp',
'テスト.+'
]
テストランナーがこれをそのままリクエストヘッダに載せようとし、しかしリクエストヘッダに非ASCII文字を含めることはできないため、エラーになっていた……ということのようです。
対応
コンテキストのシリアライズ時にパターン文字列をそのまま渡さなければよいので……
- devalueの
RegExp
に対する処理に手を入れる - vitest-pool-workers側にカスタムのシリアライザを挟む
のいずれかで解決できそうです。
どちらのアプローチを取るかは迷ったのですが、devalueはデータをASCII文字列に変換するためのパッケージではないこと、またプレーンな string
についてもエンコード等の処理は挟まっていなかったことなどから、後者を選択しました。
幸いにもカスタムのシリアライズ処理を挟む仕組みがdevalueから提供されており(ReducersRevivers
)、vitest-pool-workers側でもすでに採用されているようだったので……
今回はここに RegExp
用のシリアライザを挟みました。
source
をBase64でエンコードするだけの簡単なオーバライドです。なお flags
にはアルファベットしか入らないため[3]、そのまま横流しにしています。
この修正(と、確認用のテストいくつか)をまとめてPRを提出、無事にapproveされました。
変更分を取り込んだバージョンが6月11日にリリースされたのをきっかけに、この記事を書いています。
おわりに
ちいさなバグフィックスとはいえ、Cloudflareのソフトウェアに一つ貢献できたのは嬉しかったです。(この修正がパッケージの方針として合っているかどうかはちょっと分かりませんが……)
Cloudflare Workersは今非常にアツいので、今後も積極的に使っていきたいところです。
それでは。
Discussion