at backyard

Color my life with the chaos of trouble.

Denoのsubprocessを用いたtestでTest case is leaking resources.というエラーが出るときの対応方法

Denoのテストで下記のようにsubprocessを使って、コマンド実行結果をテストしようとした。

Deno.test({
  name: "test name",
  fn: async () => {
    const p = Deno.run({
      cmd: [ /*コマンド*/],
      stdout: "piped",
    });

    const { code } = await p.status();

    // 返り値が0であることを確認
    assertEquals(code, 0);

    const rawOutput = await p.output();
    const stdoutResult = new TextDecoder().decode(rawOutput).trim();

    // コマンドの実行結果が"foo"であることを確認
    assertEquals(stdoutResult, "foo");
  }
});

だが、このテストをdeno test --allow-run で実行しようとすると、下記のようなエラーとなる。

AssertionError: Test case is leaking resources.
Before: {
  "0": "stdin",
  "1": "stdout",
  "2": "stderr"
}
After: {
  "0": "stdin",
  "1": "stdout",
  "2": "stderr",
  "4": "child"
}

Make sure to close all open resource handles returned from Deno APIs before
finishing test case.

どうやら開放されていないリソースがあるとこのようなエラーが表示されるらしい。
インターネットで検索してみたところ、日本語で書かれた解説を見つけたので下記に貼る。
(Zennで無償で公開されている Effective Deno内の内容となる)

テストを書こう|Effective Deno

そしてこのリソース開放に関するエラーを解消するためには下記のような対象方法がある。
が、これは上で紹介した書籍内にも書かれているが、あまりおすすめされた方法ではなく、本来であればリソース開放のための処理を記載する必要がある。

Deno.test({
  name: "test name",
  fn: async () => {
    const p = Deno.run({
      cmd: [ /*コマンド*/],
      stdout: "piped",
    });

    const { code } = await p.status();

    // 返り値が0であることを確認
    assertEquals(code, 0);

    const rawOutput = await p.output();
    const stdoutResult = new TextDecoder().decode(rawOutput).trim();

    // コマンドの実行結果が"foo"であることを確認
    assertEquals(stdoutResult, "foo");
  },
  sanitizeResources: false,
  sanitizeOps: false,
});

では、今回のようなsubprocessを用いた例の場合、どうすればよいかと思い調べていたところ、aleph.js 内のとあるPRのテストの書き方が参考になった。
(しかも偶然にも上で紹介したEffective Denoの作者の方が出したPRだった)

github.com

こちらの書き方を参考に下記のようにコードを修正した。

Deno.test({
  name: "test name",
  fn: async () => {
    const p = Deno.run({
      cmd: [ /*コマンド*/],
      stdout: "piped",
    });

    const { code } = await p.status();

    // 返り値が0であることを確認
    assertEquals(code, 0);

    const rawOutput = await p.output();
    const stdoutResult = new TextDecoder().decode(rawOutput).trim();

    // コマンドの実行結果が"foo"であることを確認
    assertEquals(stdoutResult, "foo");

    // リソースを閉じる
    await p.close();
  }
});

これでリソース開放に関する表示は解消された。

以上、備忘録でした。