たかぎとねこの忘備録

プログラミングに関する忘備録を自分用に残しときます。マサカリ怖い。

GitHub Actionsでテスト用のジョブを動かした時に`Process completed with exit code 139.`エラーが発生した場合の対処法

GitHub Actionsでテスト用のジョブを動かしていた時に、全てのテストが成功しているにもかかわらず、次のようなエラーが発生してジョブが失敗したことになってしまった

Segmentation fault (core dumped) 97
Error: Process completed with exit code 139.

TL;DR

最終的な原因はRustで書かれた@node-rs/bcryptパッケージをインポートしているファイルに置いてある関数のスパイを作成したことが原因だった。 なので、原因のパッケージ全体をモックすることで解決する。

// test/setup-test-env.ts
...
vi.mock("@node-rs/bcrypt", () => {
  return {
    hash: vi.fn().mockRejectedValue("password-hash"),
    verify: vi.fn().mockReturnValue(true),
  };
});

以下では、どういった流れで原因を特定できたかを忘れないための自戒としてネットの海に漂流させておくことにする。

Segmentation fault (core dumped)とは一体なんぞや?

調べてみると、Segmentation fault 11 (core dumped)とは、本来アクセスしちゃいけないメモリのアドレスにアクセスしようとした時に発生してしまうエラーの類らしい。

segmentation fault 11(core dumped)(以下、セグフォ)とは、本来はアクセスできないメモリのアドレスにアクセスする時に起こるエラーのことを言う。 www.toumasu-program.net

そして我らがStackoverflowにも次のようなコメントを発見。

Another possible cause (which I encountered today) is that you’re trying to read/write a file which is open. In this case, simply closing the file and rerunning the script solved the issue.

stackoverflow.com

以上の内容を踏まえて、ファイルの生成や更新関連でエラーが発生しているのかと考えた。 そして、テスト時のファイルの生成に関連がありそうなコンポーネントのスナップショットテストをいったんスキップするように設定した。

しかし結果は変わらず。

別のエラーの発生

続いて、エラーの直前で終わっているテストに狙いを絞ることにした。 そのファイル内で書かれていた複数のテストをコメントアウトして、ひとつのテストのみを実行するように修正した。

実行しみると、今度はSESSION_SECRETがセットされていないというエラーが発生していた。

これは内部でcreateCookieSessionStoragesecretsを指定する際、環境変数SESION_SECRETを参照しているにも関わらず環境変数が読み込めない時に発生するエラーだった。

if (!sessionSecret) {
     throw new Error("SESSION_SECRET must be set");
          ^
}

もしかしたら、設定されていない環境変数にアクセスしようとしたからSementation faultエラーが出たのかもしれないと思ったが、もちろん違った。 つまり上記のエラーに関しては、通常通りのテスト失敗としてカウントされた。今回のテーマとは何の関係もないエラーだった。

メモリ不足の可能性

GitHub ActionsでJestを使用した際に頻繁にSementation fault (core dumped)が発生していると書かれたIssueを見つけた。

github.com

もしかしたら、Node.js実行時のヒープ領域のメモリ容量が不足している可能性があるので、テストの実行時にNODE_OPTIONS環境変数を設定し、—-max-old-space-sizeを通してメモリ容量の確保を試みた。

e-words.jp

www.suzu6.net

# .github/workflows/deploy.yml
...
- name: Run vitest
  run: npm run test -- --coverage
  env:
    NODE_OPTIONS: --max-old-space-size=4096
...

もちろん解決しなかった。

@remix-run/server-runtimeからjsonをインポートしているのが原因なのか、vi.setSystemTimeを利用しているからなのか色々試してみたが、どれも検討外れだった。

問題を特定

最終的に、単体でGitHub Actionsで実行したときにエラーが発生するテストとエラーが発生しなかったテストの差分を調べた。

するとエラーが発生するテストにおける、テスト対象の関数のファイル内でのみ@node-rs/bcryptパッケージをインポートしていることがわかった。 そしてそのエラーは、、そのテスト対象の関数のスパイを作成した時にだけ発生するエラーであることがわかった。

@node-rs/bcryptとは

Rustで実装されたbcryptの実装パッケージである。 Node.jsにおける最も高速なbcrptであると謳っている。

github.com

napi-rsを使用してRustで作成されているNode.jsアドオンであり、他にもdeno-lint2などのパッケージが@node-rs配下で公開されている。

shisama.hatenablog.com

github.com

解決方法

なぜ、このようなエラーが発生するかはわからないが、vitestに設定したセットアップファイルであるsetup-test-env.ts内で次のようにモックをすることにした。

// test/setup-test-env.ts
...
vi.mock("@node-rs/bcrypt", () => {
  return {
    hash: vi.fn().mockRejectedValue("password-hash"),
    verify: vi.fn().mockReturnValue(true),
  };
});

これによりSegmentation Faultエラーが発生せずにGitHub Actions上でジョブが正常に終了することが確認することができた。

まとめ

この問題を特定するために数時間も費やしてしまったことが一番の後悔であった。

もし同じような問題に直面してしまった誰かにとってこの記事が一助にでもなるのなら、せめてもの救いである。