【Golang】【wasm】在 Go 1.16+ 中测试的方法和注意事项
我想要在WebAssembly应用中进行测试。
尽管使用 Go 语言编写的 WebAssembly(wasm)可以运行,但在使用 go test 运行测试时会出现 exec format error 和 imports syscall/js 错误。
fork/exec /tmp/go-buildxxxxxxxxx/xxxx/my_pkg_hoge.test: exec format errorimports syscall/js: 构建约束在 /usr/local/go/src/syscall/js 中排除了所有的 Go 文件。
- 検証環境: Go 1.15.6(golang:1.15-alpine), 1.16.5(tinygo/tinygo:latest), 1.16.6(golang:alpine)(いずれも Docker 環境)
太长不读(今北产业)
-
- 在本地环境安装Node.js。
由于wasm是一个执行环境(GOARCH),所以在执行/构建应用程序时,无论是测试还是其他,都需要指定操作系统和架构(VSCode用户请参考下面的“错误调试”)。
GOOS=js GOARCH=wasm go test ./…
GOOS=js GOARCH=wasm go build ./… -o myWasm
将go_js_wasm_exec所在的目录添加到环境变量的PATH中。如果没有此项配置,将会出现“exec format error”的错误。通常为$(go env GOROOT)/misc/wasm(示例设置:export PATH=”$(go env GOROOT)/misc/wasm:${PATH}”)。如果无法找到go_js_wasm_exec的位置,可以使用find / -name go_js_wasm_exec等进行搜索。
- 参考文献: https://golang.org/src/syscall/js/js_test.go | js | syscall | src @ golang.org
错误时的排除故障
在存储库的./.vscode/settings.json文件中,添加以下VSCode附加设置,并重新启动VSCode。.vscode/settings.json
{
“go”: {
“toolsEnvVars”: {
“GOARCH”: “wasm”,
“GOOS”: “js”
},
“installDependenciesWhenBuilding”: false
}
}
如果没有”installDependenciesWhenBuilding”: false,可以在VSCode中运行测试,但源代码中的错误标记不会消失。
因为VSCode使用gopls工具来分析源代码是否有问题。
虽然TS; DR也有解释,但若GOOS不是“js”,GOARCH不是“wasm”,则无法加载syscall/js包,仅通过在本地环境变量中添加$(go env GOROOT)/misc/wasm也无法加载syscall/js包。在import “syscall/js”的位置会出现以下错误。
错误内容
无法导入syscall/js(在/usr/local/Cellar/go/1.**.**/libexec/src/syscall/js中找不到软件包”syscall/js”(来自$ GOROOT)
/Users/admin/go/src/syscall/js(来自$ GOPATH))compilerBrokenImport
导入syscall/js时出错:生成限制中排除了所有Go文件
/usr/local/Cellar/go/1.**.**/libexec/src/syscall/jscompiler
错误内容
无法导入syscall/js(没有所需的模块提供软件包”syscall/js”)(compilerBrokenImport)
导入syscall/js时出错:生成限制中排除了所有Go文件
/usr/local/Cellar/go/1.**.**/libexec/src/syscall/jscompiler
要执行构建的wasm二进制文件,通常需要通过浏览器或Node.js。但是,有时我们希望在Golang内读取wasm二进制文件,并像外部模块一样使用它,而不是创建wasm应用程序。一个典型的例子是想在Go中使用由Rust等其他语言创建的wasm二进制文件,这是一个非常特定的情况。在这种情况下,Golang实现中有一个名为wasmer-go的运行时,并且可以使用绑定gowasmer,从Golang源代码中使用它,类似于Javascript的node.js和wasm_exec.js。 wasmer-go和gowasmer就像是node.js和wasm_exec.js的Golang版本。
我创建了一个可以在wasmer中执行Go的WASM的包@zenn.dev
在GitHub上的github.com/mattn/gowasmer
尽管我们希望使用wasmer-go而不是node.js来执行测试,但是wasmer-go和gowasmer都假定我们使用“已构建”的wasm文件。考虑到测试覆盖率等因素,我认为将node.js环境安装在本地会提高可维护性。
TL; DR(我自以为完全理解了在Go中进行WebAssembly测试的自我实践)
-
- WebAssembly 用のソースとはいえ、テストの書き方は通常でおk。
-
- 問題は syscall/js パッケージ。OS をjs、アーキテクチャを wasm でテストビルドする必要がある。(GOOS=js GOARCH=wasm go test など)
js, wasm 指定でビルドするだけでなく Node.js も必要。
即使在Golang中开发WebAssembly,也希望进行测试。由于是Golang初学者,仅编写单元测试就可以满足最低需求,并且感到放心。
然而,由于环境的问题,我正在使用 Golang 进行开发。虽然编译后的程序能够正常运行,但我发现写了通常可正常运行的 Golang 测试时却会出现错误。
如果冷静地思考一下,就会明白这一点,特别是对于Go语言初学者来说,因为他们往往迫不及待地想知道是否需要通过浏览器来测试WebAssembly,所以我也感到焦虑。
此外,尽管它松散不严格,但我脑海中仍然会浮现出在Go中的覆盖率(测试覆盖率)的概念。
在提供wasm的一方面和使用wasm的一方面,测试是两回事情。也就是说,Go语言方面的测试和JavaScript方面的测试是不同的。
在这篇文章中,我想要集中精力在 Golang 的测试方面,包括对自己的警示,以及思考我所发现和学到的内容,希望能够为未来的自己留下记录。
回想起考试进行的流程,慢慢地。
Golang 是一种编译型语言。因此,即使在测试中,也会将源代码构建到临时目录中。然后,调试器会执行构建的临时二进制文件,并进行各种测试的测量。
而且,Golang 的一个重要特点是可以指定操作系统和CPU类型,然后它会为您生成相应的二进制文件。换句话说,即使在Windows上无法运行macOS或Linux版本的二进制文件,但您可以构建它们。这就是所谓的交叉编译。
我对这一点虽然“知道”,但被这个新词汇“wasm”搞得很困惑,以至于错误地认为“无法在本地测试wasm源代码”。
关键问题在于“谁”将执行构建的二进制文件进行测试。这里的“谁”指的是处理器的架构。
如果 go env 的环境变量为 GOOS=darwin GOARCH=amd64,则运行 go test ./… 将会构建适用于 macOS 上的 Intel 64位处理器的二进制文件。在这种情况下,如果在 Intel Mac 的 macOS 上,将会执行测试。
如果在 macOS 上执行 GOOS=linux GOARCH=amd64 go test ./…,会出现“exec format error”错误。这是很自然的,因为尽管 CPU 是匹配的,但是操作系统是不同的。
“啊!我在制作 wasm 的时候需要使用 GOOS=js GOARCH=wasm go build 命令,所以无法在这台机器上(Mac)进行测试,真是麻烦啊!?” 我毫无考虑地慌张了起来。
然而,我相信许多人已经注意到了。正确的写法是「牙买加?」。
考虑使用Golang来处理WebAssembly(简称wasm)时,有两个要点。
-
- syscall/js包的存在。
wasm二进制执行者。
由于我成功运行了用于WebAssembly的“Hello,world!”的Golang代码,所以我感到非常高兴。然而,当我试图将现有的自制应用程序适配为WebAssembly时,我在浏览网站和查看示例程序时意识到我需要导入syscall/js包。
这个 syscall/js.go 包是一个用于在浏览器的 Javascript 访问 wasm 二进制文件时充当桥梁或者入口的包。
它提供了一种类似于转换JavaScript类型和Golang类型,接收和返回JavaScript(浏览器端)发来的请求(执行请求)等功能的交互机制。
问题在于,该 syscall/js.go 包只能在 GOOS=js GOARCH=wasm 环境下运行。
如果您在VSCode上安装了Go扩展,然后在import “syscall/js”处遇到以下错误消息,我认为会出现以下问题。
could not import syscall/js (no required module provides package "syscall/js") compiler (BrokenImport)
error while importing syscall/js: build constraints exclude all Go files in /usr/local/go/src/syscall/jscompiler
错误内容是,在对Go扩展进行代码静态分析时,无法加载syscall/js包。
如果在源代码的顶部添加以下注释,将可以解决这个错误。
//go:build js && wasm
// +build js,wasm
为什么加载失败呢?原因是因为查看syscall/js.go包的源代码头部,发现指定了与上述相同的 // +build js,wasm。
js.go | js | syscall | src @ golang.org
如果在 Golang 中,在 package 声明之前的注释行中有 // +build 的说明,那么只有在匹配的 GOOS 或 GOARCH 的情况下,才能将其作为构建的目标。
因此,syscall/js.go包只在GOOS/GOARCH为js/wasm时才能工作。这就是为什么通常情况下运行go test ./…命令会导致导入syscall/js出现错误的原因。
如果想要在一个仓库中支持多个操作系统,那么使用这个 // +build … 的机制会派上用场。特别是在涉及系统调用(使用操作系统自身的 API 或命令)的情况下。
假设我们将某个函数的代码根据”Windows”、”Linux”和”macOS(darwin)”进行区分,并在每个源代码文件的头部添加 # +build darwin 等限制,这样其他包就可以使用相同的函数名了。
? 以下这些软件包的仓库对于此操作系统的兼容性是非常有参考价值的,如果需要创建用于命令行界面(CUI)应用的统一化输入方式,通过在终端/命令提示符上根据不同的操作系统进行分别处理,可以使用非常便利的包go-tty | mattn @ GitHub。
在这里稍作整理。
-
- 使用syscall/js 包时,必须使用 GOOS=js GOARCH=wasm 进行测试和构建,否则会出现缺少包的错误。
如果使用 GOOS=js GOARCH=wasm 进行测试和构建,则由于 GOOS 和 GOARCH 与执行环境不匹配,会导致错误。
必须解决这两个矛盾。
想起在Web服务器上安装构建的wasm文件的情景
大家好,让我们回想一下在浏览器中运行“Hello, world!”时的情景。我记得所需文件大致是这样的。
$ tree ./docs
./docs
├── index.html # <- 元 wasm_exec.html
├── test.wasm
└── wasm_exec.js
在这里重要的是 wasm_exec.js。这是因为它是用来加载 *.wasm 二进制文件并执行函数所必需的 Javascript 文件。
简而言之,wasm_exec.js 在 JavaScript 一侧扮演了类似 Golang 中的 syscall/js 的角色。
作为 wasm_exec.js 的注意事项,即 *.wasm 文件的内容会受到创建(构建)编译器的影响而发生变化。
换句话说,如果编译器发生变化,必须将 wasm_exec.js 也调整为与编译器相适应的版本。
而且,关键的 wasm_exec.js 已经与 go 一起捆绑在一起。在官方存储库的维基页面上,简单地写着“从本地复制”。
复制 JavaScript 支持文件:
$ cp “$(go env GOROOT)/misc/wasm/wasm_exec.js” .(来自 WebAssembly | Wiki | go | golang @ GitHub)
我不知道是不是读错了,但是网上的文章中也写着一些类似“从官方仓库下载最新版本”的内容。果不其然,有人根据网上的文章尝试了一下,结果不能正常运行,他们就提了一个问题。然后被“文档贱妻”批评了一番。我也有被批评的感觉。
我随后通过以下方式将js / html文件复制到浏览器中运行wasm:
https://github.com/golang/go/tree/master/misc/wasm请勿从主分支复制。请使用您的分发版本中的文件,如维基页面中所述。
$ cp “$(go env GOROOT)/misc/wasm/wasm_exec.js” . 同样适用于html文件。
(来自GitHub的问题评论 | Issue #29827 | go | golang)
【以下是本人的翻译】
换句话说,这意味着我们必须使用编译后的 wasm 文件提供的 wasm_exec.* 文件。
举个例子,如果你使用 TinyGo 编译器,你可以创建比 Go 标准编译器更小的二进制文件。
在这种情况下,使用的是TinyGo提供的wasm_exec.js,而不是与TinyGo在同一环境下提供的Go。具体来说,在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就像操作系统一样。
这是一个复习,wasm的二进制文件是在一个操作系统(js(Javascript))和架构(wasm)上进行构建和编译的。(GOOS=js GOARCH=wasm go build ./main.go -o ./doc/mywasm.wasm)
然后,在执行时,必须使用能够在JavaScript环境中解释wasm语法的处理器。换句话说,只有在支持JavaScript + WebAssembly的浏览器上才能执行。
问题在于“如果执行测试,会发生什么情况”。
首先,关于体系结构的问题,通过使用与编译器匹配的 “wasm_exec.js “,我们可以从 JavaScript 方面处理 WebAssembly。因此,剩下的问题是如何处理操作系统上的 JavaScript(js)部分。
我想您已经注意到了,只需要通过Node.js执行即可。
Node.js(Node)是JavaScript的运行时环境。换句话说,当我们执行node ./index.js时,就像执行php ./index.php或python ./index.py一样,我们可以执行JavaScript。
在Golang中进行测试(go test)时,首先需要构建测试用的二进制文件,然后直接运行该二进制文件并获取结果来进行测试。
简而言之,go 是这样运行的:$ /path/to/mybuiltapp_test …(测试参数)…。
如果是这样,那么在这个时候,如果 go 可以执行 $ node /path/to/mybuiltapp_test …(用于测试的参数)…,那么就可以将执行结果应用到测试中。
你有看到什么东西吗?
是的,让 Go 来编译,将二进制文件的执行交由 Node 处理即可。
具体而言,会使用go run命令的-exec选项。
在讲解-exec选项的细节之前,我想确认一件事。
让我们来看看先前“被文件怼了”的事情,光线照射到了“使用分发的内容”这个问题上。我们来看看该目录中的文件。
$ 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。
如果在运行go run时没有指定-exec选项,Golang的行为将取决于当前环境是否与GOOS和GOARCH匹配。
如果有匹配的情况,为测试而创建的二进制文件将直接执行。
$ ./path/to/mybuiltapp_test ...(テスト用の引数)...
如果不匹配(不同),则通过搜索路径(在环境变量PATH指定的目录)中的文件以格式go___exec,执行构建的测试二进制文件。
在这种情况下,执行 “go run -exec=go___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等文件的目录添加到操作系统的搜索路径(如环境变量PATH)中。
-
- 确保已安装Node.js以使node命令可用(通常不需要npm等包管理器)。
- 在运行go test ./…时,使用GOOS=js GOARCH=wasm指定操作系统和架构。