VSCode の Jest 拡張機能が asdf で管理している Node.js を呼び出せない問題
VSCode の Jest 拡張機能(vscode-jest)がたまに asdf で管理している Node.js を呼び出せない問題に遭遇しました。
今回は、その原因と対策についてちょっと深掘りしたので、ここに残します。
TL;DR
- VSCode の Jest 拡張機能は非インタラクティブ(non-interactive)&非ログイン(non-login)な
/bin/sh
シェルから Node.js を呼び出す - asdf は多くの場合
~/.bashrc
や~/.zshrc
に asdf の初期化コード(パスを通したり)を記述しているため、asdf で管理している Node.js を Jest 拡張機能は呼び出せない - いくつかの対策方法があるが、
.vscode/settings.json
のjest.shell
で Jest 実行のシェルをログインシェルにするのがたぶん楽。たぶん.vscode/settings.json"jest.shell": { "path": "/usr/bin/env", "args": ["zsh", "--login"] },
VSCode の Jest 拡張機能 vscode-jest について
VSCode には Jest[1]のテストを実行するための便利な拡張機能があります。
名を vscode-jest と言い、VSCode 上から Jest のテストを実行したり、テストの実行結果を見たりできます。
何があったか
ある日、VSCode で Node.js を使うプロジェクトを開いたところ、vscode-jest がエラーを吐きました。
> Test run exited at 2023/6/7 18:31:05 <
env: node: No such file or directory
[error] failed to retrieve test file list. TestExplorer might show incomplete test items
[info] jest process failed to start, most likely due to env or project configuration issues, please see: https://github.com/jest-community/vscode-jest/blob/master/README.md#jest-failed-to-run
env: node: No such file or directory
[error] Jest process "watch-tests" ended unexpectedly
[info] jest process failed to start, most likely due to env or project configuration issues, please see: https://github.com/jest-community/vscode-jest/blob/master/README.md#jest-failed-to-run
テストファイルの一覧が取得できないエラーと、ファイルを監視して自動でテストを動かす watch-tests
が失敗したというエラーの 2 つが出ていますが、どちらのエラーでも env: node: No such file or directory
が出ており、おそらく原因は同じと考えられます。
不思議だったのが、これまでは特段設定をしなくても Jest の拡張機能でテスト実行ができていたことです。
最初は何がいつもと違うのかわかりませんでした。
起こる条件
何回か色々試したところ、上記のエラーが出るのは code
コマンドを使ってない時でした。
code
コマンドを使わずに VSCode を起動するとは、例えば Dock の VSCode を右クリックして出てくる最近開いたディレクトリ一覧から開いたり、メニューバーの最近開いたディレクトリ一覧から開いたりすることを指しています。
Dock から開く場合
メニューバーから開く場合
実際、僕は Dock から VSCode を開くことがしばしばあり、その時にエラーが出ることがわかりました。
原因
色々ググったりして調べていたところ、個人の有志が書いた次のページが見つかりました。
このページに書いてあることと、vscode-jest の Issue コメントが全てなのですが、噛み砕いて行きます。
~/.bashrc
などを読まない
vscode-jest は どうやら、vscode-jest は Jest を呼び出す際に、非インタラクティブ(non-interactive)&非ログイン(non-login)な /bin/sh
シェルから Node.js、ひいては Jest を呼び出しているようです。
したがって、vscode-jest は ~/.bashrc
のようなファイルで export
した環境変数を読んでくれないわけですね。
/bin/sh
はどこへのパスが通っている?
non-login な そもそも non-login な /bin/sh
はどこへのパスが通っているのでしょうか?
まずは env -i /bin/sh -c 'env'
で環境変数を確認してみます。
❯ env -i /bin/sh -c 'env'
PWD=<実行ディレクトリのパス>
SHLVL=1
_=/usr/bin/env
おっと、$PATH
がないですね。$PATH
は定義されているのでしょうか?
env -i /bin/sh -c 'echo $PATH'
で確認できます。
❯ env -i /bin/sh -c 'echo $PATH'
/usr/gnu/bin:/usr/local/bin:/bin:/usr/bin:.
env
で PATH
が定義されてなかったのに、$PATH
の中身が表示されました。
これは、PATH
がデフォルトのシェル変数として登録されてるからですね[2]。
man bash
に載っています。
PATH The search path for commands. It is a colon-separated list of directories in which the shell looks for commands (see COMMAND EXECUTION below). A zero-length (null) directory name in the value
of PATH indicates the current directory. A null directory name may appear as two adjacent colons, or as an initial or trailing colon. The default path is system-dependent, and is set by the
administrator who installs bash. A common value is ``/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin''.
The default path is system-dependent, and is set by the administrator who installs bash. A common value is ``/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin''.
日本語訳)デフォルトのパスはシステムに依存し、bashをインストールした管理者が設定します。 一般的な値は ``/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin'' です。
どうやらシステム依存(インストールした人が決める)らしいです。
macOS (darwin?) の場合はそれらの開発者が設定してるのかもしれません。
少なくとも僕の環境の /bin/sh
では、次の場所が指定されていました。
/usr/gnu/bin
/usr/local/bin
/bin
/usr/bin
.
したがって、これらの場所に Node.js のバイナリ(node
)がないといけないわけですね。
/bin/sh
において Node.js へのパスが通ってなかった
自分の環境では non-login な 例えば、Node.js を Homebrew でインストールする際は、最終的に /opt/homebrew/bin/
に配置されます。
通常 /opt/homebrew
はパスが通ってない($PATH
に含まれていない)ので、/opt/homebrew/bin/brew shellenv
を ~/.bashrc
などで実行するようにして、/opt/homebrew/bin/
へのパスを通す必要があります[3]。
そして僕の場合、Node.js を asdf[4] で管理しています。
❯ where node
/Users/<ユーザ名>/.asdf/shims/node
/opt/homebrew/bin/node # 注釈:Homebrew でも Node.js が入っていた
asdf で管理するバイナリは ~/.asdf/shims
に配置されるため、Homebrew と同じく、パスを通す必要があります。
パスは ~/.zshrc
経由で設定するようにしているため、/bin/sh
ではパスが通っていない状態になっていました(.zshrc
で設定しているため、login、non-login 関わらずパスが通らない)。
これが原因だったんですね。
解決方法
どのように解決するかについても、上記で紹介した方のページに書かれていました。
この著者の場合、jest.shell
に普段利用するシェル(Zsh)を設定し、.zshenv
でパスを通すことで回避しています。
vscode-jest には jest.shell
という設定項目があり、そこで Jest の呼び出しに使うシェルを設定できます(何も設定しなければ /bin/sh
)。
また、Zsh の場合、どの場合((non-)login、(non-)interactive)でも ~/.zshenv
ファイルを読み込むため[6]、~/.zshenv
でパスを通すことで、Zsh から Jest を呼び出す際に Node.js へのパスが通るようになります。
しかし、~/.zshenv
に手を加えると、Zsh のシェルスクリプト実行時なども影響を受けてしまうため、なるべく書き換えたくないというのがあります。
それを回避する方法として、上記ページには Jest の実行に使うシェルをカスタマイズできる変更が入りそうとのことも書かれていました。当時は未リリースだったようですが、すでにリリースされてそうだったので、今回は --login
でログインシェルとして Jest を実行するようにしてみました。
実際の設定
"jest.shell": {
"path": "/usr/bin/env",
"args": ["zsh", "--login"]
},
というわけで、実際の設定が上記のものになります。
僕の場合、Zsh を env 経由で呼び出し、--login
オプションで Zsh をログインシェルとして起動するようにしています。
ログインシェルとして起動することにより、~/.zprofile
、~/.zlogin
が読まれるようになります。
そちらで Node.js へのパスを通すようにすることで、~/.zshenv
に手を加えずに済みます。
この設定を対象リポジトリの .vscode/settings.json
に書き加えることで、冒頭の問題は解決できました。
ただ、場合によっては /bin/sh
を起動するときよりも時間がかかるようになるかもしれません。
僕の場合は特に速度面で気になることはありませんでした。
おわりに
今回 VSCode の Jest 拡張の謎の挙動から始まり、ワークアラウンドはすぐに見つかったのですが、なんでそういう挙動になるのかが気になって色々調べました。
とりあえず再発しないようになって良かったです。
シェルのモードの違いとか読み込むファイルの順序とか、なかなか勉強になって良かったですね。
なんか間違ったことを書いてしまっていたり、もっと良い解決方法があったりしたらぜひ教えてください。
-
JavaScript のテスティングフレームワーク。https://jestjs.io ↩︎
-
ここら辺の挙動はシェルによって変わると思います。 ↩︎
-
Homebrew インストール後にやっといてねって出てくる。https://github.com/Homebrew/install/blob/716a1d024f32890ef75ea82c18a769abc24e9475/install.sh#L1010-L1025 ↩︎
-
ざっくり言うとバイナリのバージョン管理ツールです。ディレクトリごとに異なるバージョンのバイナリを使えます。 ↩︎
-
環境変数が引き継がれる仕組みはおそらく周知の事実だと思いますが、そこらへんの仕様が載ったページを貼りたくて探しました。しかし見つけられなかったので、誰か知ってる人いたら教えてください。 ↩︎
-
詳しくは
man zsh
のSTARTUP/SHUTDOWN FILES
を参照してください。https://linux.die.net/man/1/zsh#STARTUP/SHUTDOWN FILES ↩︎
Discussion