Description
This is a proposal for a change to go test
. The goal is to make it easier to enable conditional compilation of test-only code, which is useful to stub out code during testing, by introducing a build tag that is automatically set during go test
, similarly to how the race
tag is automatically added for builds that use the -race
flag.
For example, let's say we have some code that uses the unix.Setns()
system call, which is a Linux-specific privileged system call (usually requires root
or some elevated privileges like CAP_SYS_ADMIN
). One way to make such code testable independently of the platform and privilege level is to do:
var setNs = func(h handle) error {
return unix.Setns(h.fd(), unix.CLONE_NEWNET)
}
This way unit tests can override the call by changing the layer of indirection that the global variable creates:
func TestNetNs(t *testing.T) {
// Mock setNs
oldSetNs := setNs
setNs = func(fd handle) error {
return nil
}
defer func() {
setNs = oldSetNs
}()
However this is a bit tedious to do, it creates an unnecessary level of indirection in the production code and clutters the test code. Another solution that addresses this is to use a build tag instead:
// in setns_linux.go
// +build !test
func setNs(h handle) error {
return unix.Setns(h.fd(), unix.CLONE_NEWNET)
}
// in setns_test.go
func setNs(h handle) error {
return nil
}
The downside is that now go test ./...
doesn't work anymore, one needs to know to pass -tags test
. In a large codebase, people choose to use different build tags to implement this mechanism, which leads to inconsistencies, unless Makefile
s (or equivalent) are used and go test
is not to be run "manually".
Another place where a built-in test
tag could be useful is when factoring out test libraries. Code in foo_test.go
files is only visible in the current package and can't be imported from one package to another. Therefore it's common to create a test
package to factor out test code re-used across packages. Doing this unfortunately creates room for production code importing test code. By flagging the test code with a build tag, this possibility is significantly reduced. But again, the build tag creates a usability issue.
There are a number of packages that already use a test
tag (see for example the GitHub search for language:go "// +build test" filename:_test.go
). Some of these packages would benefit from the usability improvement (for example golang.org/x/text
does not use a Makefile
but unicode/norm/forminfo_test.go
assumes the test
tag is used), other packages would start behaving differently (for example github.com/netrack/netrack-server would start running both the integration tests and the unit tests when make integration-test
is run, instead of just the integration tests as the intent of the Makefile
appears to be).
Alternatively the built-in tag could be gotest
, which doesn't seem to be used on GitHub.
Looking forward to your feedback on this small proposal. Thanks in advance.