【Golang】【wasm】テストの仕方と注意点 @ Go 1.16+
WebAssembly 用のアプリでもテストがしたい
Go 言語(以下 Golang)で組んだ WebAssembly(
wasm
)が動きはするものの、go test
でテストを実行するとexec format error
やimports syscall/js
エラーが出る。fork/exec /tmp/go-buildxxxxxxxxx/xxxx/my_pkg_hoge.test: exec format error
imports syscall/js: build constraints exclude all Go files in /usr/local/go/src/syscall/js
- 検証環境: Go 1.15.6(
golang:1.15-alpine
), 1.16.5(tinygo/tinygo:latest
), 1.16.6(golang:alpine
)(いずれも Docker 環境)
TL; DR (今北産業)
-
node.js
をインストールする。 - 環境変数の
PATH
にgo_js_wasm_exec
のあるディレクトリを追加する。
(一般的に${GOPATH}/misc/wasm
) - 実行時に OS とアーキテクチャを指定する。(
GOOS=js GOARCH=wasm go test
など)
(js_test.go | js | syscall | src @ golang.org より筆者訳)
TS; DR (Go で wasm のテストを完全に理解した気になっている俺様プラクティス)
- WebAssembly 用のソースとはいえ、テストの書き方は通常でおk。
- 問題は
syscall/js
パッケージ。js,wasm
でビルドする必要がある。(GOOS=js GOARCH=wasm go test
など) -
js,wasm
でビルドだけでなく Node.js も必要。
Golang で WebAssembly 用の開発をする場合でもテストは行いたいものです。Golang 初心者ゆえ、テストを書くだけで最低限のことをしている気になれるので、安心できるのです。
しかし、ふいんきで Golang を触っているものだから、コンパイルしたものは動くものの、いつも通りの Golang のテストを書いてもエラーが出るのです。
後述するように、落ち着いて考えればわかることなのですが、Golang 初心者に輪をかけて、せっかちなものだから「(えっ?あっ!WebAssembly だからブラウザでテストしないといけないの?)」と焦ってしまいました。
さらに、ガバガバなくせに Go でのカバレッジ(テストの網羅率)が脳裏をよぎります。
3 歩ですぐ忘れるのですが、おそらく大事なのは wasm
を提供する側でのテストと、それを利用する側でのテストは別物であるということです。つまり、Golang 側でのテストと、Javascript 側でのテストです。
この記事では(自分への戒めも含めて)Golang 側でのテストに注力し、気づいた点や学んだ点を反芻するためと、未来の自分のために残したいと思います。
テストが実行される流れを思い出す。ゆっくりと
Golang はコンパイル型の言語です。そのため、テストの場合でもソースを temp
ディレクトリに作成して実行します。
このことは「知ってはいる」のですが、wasm
という目新しい用語に翻弄されてテストできないと早とちりしてしまいました。
ポイントは「テストがビルドしたバイナリを『誰が』実行するのか」ということです。この「誰」とはアーキテクチャ(CPU)のことです。
環境変数が GOOS=darwin
GOARCH=amd64
の場合は go test ./...
すると、OS が macOS で CPU が AMD/Intel 64bit 向けのバイナリがビルドされ、テストが実行されます。
逆に macOS 上で GOOS=linux GOARCH=amd64 go test ./...
すると exec format error
が出ます。当然です。CPU は合っていても、OS が違うためです。
しかし、よく考えもせず「(あっ!となると wasm
を作るときは GOOS=js GOARCH=wasm go build
とするから、このマシン(Mac)ではテストできないんジャマイカ!)」となってしまったのです。
Golang で WebAssembly (wasm
)を考える場合の 2 つのポイント
-
syscall/js
パッケージの存在 -
wasm
バイナリの実行者
Golang で WebAssembly 用の "Hello, world!" が動いたので喜んでいたのも束の間、自分の既存のアプリを WebAssembly 対応させようとサイトを練り歩いたりサンプル・プログラムを見ていると syscall/js
のパッケージを import
しないといけないことに気づきます。
この syscall/js.go
パッケージは、ブラウザの Javascript が wasm
バイナリにアクセスした際のブリッジ(橋渡し)もしくはゲート(出入口)となるパッケージです。
Javascript の型と Golang 用の型を変換してくれたり、Javascript 側からのリクエスト(処理依頼)で Golang 側から POST された画像などを取得するなど、交換手のような仕組みを提供してくれています。
問題は syscall/js.go
パッケージは GOOS=js GOARCH=wasm
でないと動かないことです。
なぜなら、syscall/js.go
パッケージのソース・コードのヘッダを見ると # +build js,wasm
が指定されているためです。
- js.go | js | syscall | src @ golang.org
ソースコードの package
宣言の前のコメント行に # +build
という記載があった場合は、マッチした GOOS
や GOARCH
の場合のみビルドの対象にすることができます。
これが、通常通り go test ./...
と実行しても imports syscall/js
エラーが出る原因です。GOOS/GOARCH
が js/wasm
ではないためです。
この仕組みは 1 つのリポジトリで複数 OS に対応したい場合に力を発揮します。特にシステム・コールがらみ(OS 独自の API やコマンドを利用)の場合です。
例えば、とある関数のコードを「Windows 用」「Linux 用」「macOS(darwin)用」と分けておき、各々のソースコードのヘッダに # +build darwin
などと限定させることで、他のパッケージは同じ関数名を使うことができます。
🐒 この OS 互換について、以下のパッケージのリポジトリが、とても参考になります。ターミナル/コマンドプロンプトからの入力を OS ごとにわけることで一元化してくれる、CUI アプリを作る場合に、たいへんありがたいパッケージです。
ここで少し整理します。
-
syscall/js
パッケージを使う場合はGOOS=js GOARCH=wasm
でテストやビルドを実行しないとパッケージ足らずでエラーになる。 -
GOOS=js GOARCH=wasm
でテストやビルドすると、GOOS
とGOARCH
が実行環境と合わないためエラーになる。
この 2 つの矛盾を何とかしないといけません。
ビルドした wasm
を Web サーバに設置した時のことを思い出す
Hello, world!
をブラウザで動かした時のことを思い出しましょう。必要なファイルは以下ような感じだったと思います。
$ tree ./docs
./docs
├── index.html # <- 元 wasm_exec.html
├── test.wasm
└── wasm_exec.js
ここで重要なのが wasm_exec.js
です。Javascript が *.wasm
バイナリを読み込んで関数を実行するのに必要なものだからです。
つまり、Golang 側で言うところの syscall/js
と似た役割を、Javascript 側では wasm_exec.js
が担ってくれているのです。
wasm_exec.js
の注意点として、*.wasm
を作った(ビルドした)コンパイラによって中身が変わることです。
つまり、コンパイラが変わった場合は、wasm_exec.js
もコンパイラに合わせてたものに変えないといけません。
これを読み違えたのか、ネットの記事では「公式リポジトリから最新版をダウンロードしろ」的なことも書いてあったりするのですが、その公式リポジトリの Wiki ではシンプルに「ローカルからコピーする」と書いてあります。
Copy the JavaScript support file:
$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
(WebAssembly | Wiki | go | golang @ GitHub より)
案の定、ネット記事をベースに試して、動かないので Issue を立てた人がいて、ドキュメント嫁から叱られてました。自分も叱られた思いがします。
I then copied the js / html fiels for running wasm in browser via:
https://github.com/golang/go/tree/master/misc/wasmDo not copy from master branch. Use the files from your distribution as mentioned in the wiki page.
$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
and same for the html file.
(issue コメント | Issue #29827 | go | golang @ GitHub より)
以下、筆者訳。
次に、wasm を走らせる js と html ファイルをブラウザ経由で以下からピーコピーコしてきました。
https://github.com/golang/go/tree/master/misc/wasmマスターブランチからコピペピピックしないでください。Wiki にもあるように、自分に配布された(Go にバンドルしてきた)ものを使ってください。html ファイルについても同様です。
$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
つまり、wasm
をコンパイルしたものが提供している wasm_exec.*
を利用しないといけないということです。
例えば、Go の標準コンパイラより小さいバイナリが作成できる TinyGo コンパイラを使う場合も同様です。
この場合は、TinyGo と同じ環境にある Go が提供しているものではなく、実際にコンパイルする TinyGo が提供している wasm_exec.js
を使います。具体的には、TinyGo の Docker の場合は以下のディレクトリにあります。
-
wasm_exec.js
:/usr/local/tinygo/targets/wasm_exec.js
-
wasm_exec.html
:/usr/local/tinygo/src/examples/wasm/main/index.html
さて、コンパイラに合った wasm_exec.js
を使うことは理解できました。違うものだと Javascript がコンパイラの API を理解できないためです。
では、本題の「Golang 側で wasm
バイナリをテストする」にはどうすればいいでしょう。
Node.js は OS みたいなもの
復習ですが、wasm
のバイナリは OS が js
(Javascript)、アーキテクチャが wasm
でビルドされています。(GOOS=js GOARCH=wasm go build ./main.go -o ./doc/mywasm.wasm
)
そして、実行する時は Javascript 環境で wasm
の構文を解釈できるプロセッサー(処理系統)でないといけません。つまり、Javascript + WebAssembly 対応のブラウザなら実行できるということです。
問題は「テストを実行する場合はどうなるのか」というところです。
まず、アーキテクチャの問題ですが、先の「wasm_exec.js
はコンパイラと合ったものを使う」ことで Javascript 側から wasm
を処理することができます。となると、残る問題は OS である js
(Javascript)をどうするかです。
もぅ、すでにお気づきだと思いますが、Node.js を介して実行すればいいのです。
Node.js(node
)は、Javascript のランタイムです。つまり、php ./index.php
や python ./index.py
のように、node ./index.js
と実行すると Javascript を実行できるのです。
Golang のテスト(go test
)の場合、まずテスト用にバイナリをビルドしてから、直接バイナリを実行して、その結果を取得することでテストを行います。
つまり、go
が $ /path/to/mybuiltapp_test ...(テスト用の引数)...
のように実行するのです。
となると、この時に go
が $ node /path/to/mybuiltapp_test ...(テスト用の引数)...
と実行出来れば、その実行結果をテストに反映できることになります。
何か見えてきたでしょうか。
そう、コンパイルは go
にさせて、バイナリの実行は node
にまかせればいいのです。
具体的には go run
の -exec
オプションが使われます。
ここで、-exec
オプションの詳細の前に確認したいことがあります。
先の「ドキュメント嫁に叱られる」件で「配布されたものを使え」と cp $(go env GOROOT)/misc/wasm/wasm_exec.js
光線を浴びました。そのディレクトリにあるファイルを見てみましょう。
$ ls -lah "$(go env GOROOT)/misc/wasm"
total 36K
drwxr-xr-x 2 root root 4.0K Dec 4 2020 .
drwxr-xr-x 12 root root 4.0K Dec 4 2020 ..
-rwxr-xr-x 1 root root 441 Dec 4 2020 go_js_wasm_exec
-rw-r--r-- 1 root root 1.3K Dec 4 2020 wasm_exec.html
-rw-r--r-- 1 root root 16.7K Dec 4 2020 wasm_exec.js
「変なところからコピペピピックせずに、ここからコピーしろ」と言われた wasm_exec.js
と wasm_exec.html
が確認できます。
ここで注目してもらいたいのが go_js_wasm_exec
ファイルです。初めて出て来た名前ですが、これが -exec
に使われます。
Golang は、go run
実行時に -exec
オプションが指定されていない場合、GOOS
と GOARCH
が現在の環境とマッチしているか否かで挙動が変わります。
マッチしている場合は、テスト用に作成されたバイナリはそのまま実行されます。
$ ./path/to/mybuiltapp_test ...(テスト用の引数)...
マッチしない(異なる)場合は go_<GOOS>_<GOARCH>_exec
という書式のファイルを、パス(環境変数の PATH
にあるディレクトリ)から探し、それを使ってビルドされたテスト用バイナリを実行します。
この時、go run -exec=go_<GOOS>_<GOARCH>_exec ./...
として実行するのと同じ挙動になります。
つまり GOOS=js
GOARCH=wasm
の場合、ビルド後、go_js_wasm_exec
を探して以下のようにテスト用に作成されたバイナリを実行します。
$ go_js_wasm_exec /path/to/mybuiltapp_test ...(テスト用の引数)...
そして、go_js_wasm_exec
の中身ですが、同ディレクトリにある wasm_exec.js
を使った Javascript で書かれたテストを作成して node
に渡し、その実行結果を Go が解釈できる形で返します。パーサーのような役割をします。
ここで大事なのが go_js_wasm_exec
を見つけられるようにパスを通さないといけないということと、node
がインストールされていないといけないことです。
まとめ
- HTML で使う
wasm_exec.js
は、コンパイラー(Go もしくは TinyGo)が提供しているものを使う。 -
wasm_exec.js
などがあったディレクトリを、OS の検索パス(環境変数PATH
など)に追加する。 -
node
コマンドが実行できるように Node.js を入れておく(npm
などのパッケージ・マネージャーは基本的に不要) -
go test ./...
する時にGOOS=js GOARCH=wasm go test ./...
と OS とアーキテクチャを指定する。
Author And Source
この問題について(【Golang】【wasm】テストの仕方と注意点 @ Go 1.16+), 我々は、より多くの情報をここで見つけました https://qiita.com/KEINOS/items/0ab42c53dcebc5a925f0著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .