你正在使用的Go版本是什么(go version
)?
$ go version
go version go1.13.4 darwin/amd64
这个问题在最新的发布版本中是否重现?
应该会;我觉得没有理由不会,因为我高亮的代码在主分支上。
你正在使用什么操作系统和处理器架构(go env
)?go env
输出
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/jamesjohnston/Library/Caches/go-build"
GOENV="/Users/jamesjohnston/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/jamesjohnston/Thumbtack/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go/1.13.4/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.13.4/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS="-I/usr/local/include"
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-L/usr/local/lib"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/4_/s_mp89t54_b8xbgwclmf2scw0000gp/T/go-build586602208=/tmp/go-build -gno-record-gcc-switches -fno-common"
你做了什么?
创建一个覆盖os.Stdout的测试,然后使用go test -json
运行它。
https://play.golang.org/p/zeonvI5FdUJ
func TestWithOverride(t *testing.T) {
_, stdOutPipe, _ := os.Pipe()
os.Stdout = stdOutPipe
}
然后使用go test -json
运行测试。
你期望看到什么?
这个测试应该成功:退出代码都应该是0,JSON输出应该表明测试成功。
你看到了什么?
JSON输出表明测试失败,但退出代码仍然是0,表示成功:
jamesjohnston-mac:testcase jamesjohnston$ go test -json
{"Time":"2020-02-19T15:27:30.928348-08:00","Action":"run","Package":"github.com/thumbtack/go/testcase","Test":"TestWithOverride"}
{"Time":"2020-02-19T15:27:30.928623-08:00","Action":"output","Package":"github.com/thumbtack/go/testcase","Test":"TestWithOverride","Output":"=== RUN TestWithOverride\n"}
{"Time":"2020-02-19T15:27:30.928661-08:00","Action":"output","Package":"github.com/thumbtack/go/testcase","Test":"TestWithOverride","Output":"--- PASS: TestWithOverride (0.00s)\n"}
{"Time":"2020-02-19T15:27:30.92872-08:00","Action":"output","Package":"github.com/thumbtack/go/testcase","Test":"TestWithOverride","Output":"ok \tgithub.com/thumbtack/go/testcase\t0.006s\n"}
{"Time":"2020-02-19T15:27:30.928732-08:00","Action":"fail","Package":"github.com/thumbtack/go/testcase","Test":"TestWithOverride","Elapsed":0.007}
jamesjohnston-mac:testcase jamesjohnston$ echo $?
0
请注意在JSON中报告的失败:"Action":"fail"
。这非常模糊:测试是通过还是失败了?退出代码说了一个,但JSON输出说另一个。
进一步调查
注意,如果我们运行上面的play链接,我们得到这样的输出:
=== RUN TestWithOverride
--- PASS: TestWithOverride (0.00s)
All tests passed.
然而,如果我们写一个“正常”的测试,不篡改Stdout,我们会得到额外的输出:最后的“PASS”: https://play.golang.org/p/3Z8hY3rAfhj
package main
import (
"testing"
)
func TestWithoutOverride(t *testing.T) {
// do nothing
}
输出:
=== RUN TestWithoutOverride
--- PASS: TestWithoutOverride (0.00s)
PASS
All tests passed.
看起来test2json将缺乏最终的“PASS”解释为转换为JSON时的失败。然而,主要的“go test”命令并没有这样做,因此以“成功”的退出代码退出。因此,我们最终得到了一个同时“通过”和“失败”的测试。
问题似乎是:testing.go
文件完全被写入,假设终端用户永远不会写入任何os.StdXYZ
变量。例如:
go/src/testing/testing.go
第1211行 in c4c73ce
| | fmt.Println("PASS") |
fmt.Println("PASS")
是写入最终“PASS”注解的代码。
几个可能的修复方法可能是:
- 更新
testing.go
,使其在测试开始时读取os.StdXYZ
变量,然后再从它们中读取一次。例如,上面的代码可以更新为fmt.Fprintln(originalStdOut, "PASS")
。这可以保护代码免受篡改标准文件的测试的影响。 - 更新go test和/或test2json,使得如果由于这个问题(或其他任何原因)测试二进制输出被截断,它将一致地通过或失败测试,并且不会在JSON和退出代码之间留下分歧。
有趣的是,这个问题仅限于-json
标志。如果我们在不使用该标志的情况下运行go test
,测试将通过且不会有任何出错的迹象。
理由/背景
这个测试用例是从我老板那里编写的一个测试中衍生出来的。它暂时替换了Stdout,以便捕获并与预期输出进行比较。问题是,对于一个测试来说,这个人忘记了恢复原始Stdout,直到我开始引入使用JSON输出的工作工具,如使用 gotestsum
将JSON转换为JUnit XML,然后将其传递给Jenkins JUnit插件......Jenkins令人惊讶地报告了失败的测试,尽管测试套件“通过了”。
虽然用os.Stdout
替换可能不是测试命令行工具的最佳方法,但有人确实写了这样一个测试,在这种场景下,我认为当面对像这样的“有趣”测试时,测试运行器应该表现出可预测的行为。
3条答案
按热度按时间pw136qt21#
/cc @mpvl@josharian
uyto3xhc2#
虽然用
os.Stdout
替换可能不是测试命令行工具的最佳方法,但确实有人用这种方式编写了测试。在这种情况下,我认为当遇到像这样的“有趣”测试时,测试运行器应该表现出可预测的行为。我绝对同意你不应该像那样替换
os.Stdout
。类似的问题是调用os.Exit(0)
如何使整个包 pass its tests 在它绝对不应该的情况下发生。我不知道我们能在这里做什么,但我认为将
go test -json
适应这样的测试并不是一个好主意。mbjcgjjk3#
testing
包本身可以在每次测试结束时保存os.Stdout
和os.Stderr
并恢复它们。任何试图重新分配这些资源的并行测试都会被报告为竞争,但无论如何这似乎都是一个好消息。