blog.euxn.me

core-js を抜いたらテストが flaky になった

2024-04-05 Fri.

core-js を抜いたらいくつかのテストが確率で落ちるようになったので調査した。 テスト環境は jest + jsdom である。

core-js のどれが影響しているのか二部探索して特定していくと、 core-js/stable/set-immediatecore-js/stable/clear-immediate が影響していることがわかった。

setImmediateclearImmediate は Node.js にあるが、ブラウザにはない関数である。 そのため、本来 jest or jsdom で補われて欲しいのだが、過去に削除されているようである。

When running in Jest, code assumes setImmediate and clearImmediate

実際に window.setImmediate を確認してみても undefined である。

一方 setImmediateclearImmediate が存在しない環境で使うとエラーになる……かと思いきや、react (v18.2.0) の中の scheduler では setImmediate が存在しない環境では迂回するように実装されている。

https://github.com/facebook/react/blob/76bbad3e34bc3403ddbe59e12845e8643dbb8d9f/packages/scheduler/src/forks/Scheduler.js#L94

https://github.com/facebook/react/blob/76bbad3e34bc3403ddbe59e12845e8643dbb8d9f/packages/scheduler/src/forks/Scheduler.js#L551

とはいえ実装を見ると実行していることはだいぶ異なるので、ここが原因で時間がかかっている可能性がある。今回はその仮定のもと対処法を考える。

対処法 1. core-js の setImmediate だけを入れる

1import "core-js/stable/set-immediate";

無いならば補う対応。 core-js 全体で 1 つのパッケージのため node_modules から大きなものを削減することはできないが、実行上はこれで問題なくなる。

対処法 2. node:timers の setImmediate を使う

1import { setImmediate, clearImmediate } from "node:timers";
2
3Object.defineProperty(window, "setImmediate", { value: setImmediate });

無いならば補う対応 2。この方法でもうまくいく、と思いきや、 CI では同様のタイムアウトが確率で発生してしまう。ローカルでも timeout 時間を短く設定すると失敗が再現するため、パフォーマンス観点の差だろうか?

また timer 全体の mock の影響を受けるという懸念もあり、今回は不採用。

対処法 3. happy-dom を使う

@happy-dom/jest-environment では setImmediate が設定されてる。

https://github.com/capricorn86/happy-dom/blob/master/packages/jest-environment/src/index.ts#L74

そのため happy-dom を使うことで解決が見込める。のだが、実際に使用してみると上記と同様に CI でのみ確率でタイムアウトが発生する。 内部実装を呼んでみるとここで挿入している setImmediate は node の global にある setImmediate 即ち timers のものと同様であるため、パフォーマンス問題と考える。

結論

テスト環境の高速化のために @happy-dom を入れたいというのはあるが今回とは別で対応するとして、 CI での実行時間ではタイムアウトする可能性があるのは特定のテストのみであるため、タイムアウト時間を長くする対応をする。 vitest に置き換えた際にまとめてまとめて不要なタイムアウトを消す作業を入れることで対応したい。

Other Works
2024-12-01 Sun.
OpenAPI Spec を出力できる DSL、TypeSpec の実践例
- ドワンゴ教育サービス開発者ブログ

2024-11-16 Sat.
型付き API リクエストを実現するいくつかの手法とその選択
- TSKaigi Kansai 2024

2024-09-10 Tue.
corepack が標準同梱じゃなくなる未来、 mise でパッケージマネージャを管理する
- Zenn

2024-09-10 Tue.
言語環境の管理は *env や *vm を超えて、 mise へ
- Zenn

2024-06-28 Fri.
TypeSpec を使い倒してる
- Kyoto.js 22

2024-05-11 Sat.
Powerfully Typed TypeScript
- TSKaigi 2024

2024-05-10 Fri.
pnpm の node_modules を探検して理解しよう
- ドワンゴ教育サービス開発者ブログ

2024-03-17 Sun.
neverthrow で局所的に Result 型を使い、 try-catch より安全に記述する
- Zenn

2023-12-20 Wed.
レガシーブラウザ向けのビルドオプションを剪定する
- ドワンゴ教育サービス開発者ブログ

2023-05-26 Fri.
Next.js で dynamic import を使い Client だけで動かす Component を実現する
- Zenn

2023-05-02 Tue.
Node.js でファイル名から拡張子を取り除く/取り出すために path.parse を使う
- Zenn

2023-02-27 Mon.
WSL2 で外部からアクセス可能にするために bridge mode を有効にする
- Zenn

2023-01-26 Thu.
init.vim & dein から init.lua & lazy.nvim へ、シンプル設定で移行した
- Zenn

2023-01-13 Fri.
kindle の本をブクログ形式の csv でエクスポートする@2023初春
- Zenn

2023-01-10 Tue.
自宅サーバの移設に際して docker から nerdctl に移行した
- Zenn

2023-01-10 Tue.
自宅サーバを rootless に移行した際のトラブル対応
- Zenn

2021-11-11 Thu.
並列実行した Promise で throw されても全てハンドルしたいときの方法(allSettled, finally, etc...)
- Zenn