Problem
I’ve been working on a Go CLI app to push sensor data to Cloud™ and hit the problem.
In order to read the sensor data I had a very simple piece of code:
package main //analog_read.go
import (
"flag"
"fmt"
"os"
"github.com/hybridgroup/gobot/platforms/intel-iot/edison"
)
var (
pin = flag.String("pin", "", "Sensor's pin-ID to read analog value from. Required.")
)
func main() {
flag.Parse()
if *pin == "" {
flag.PrintDefaults()
os.Exit(1)
}
var ed = edison.NewEdisonAdaptor("edison")
v, err := ed.AnalogRead(*pin)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
fmt.Println(v)
}
If the above code runs without pin
flag specified then it dumps flag defaults prompting to specify the pin.
But instead of expected prompt:
$ go run analog_read.go
-pin string
Sensor's pin-ID to read analog value from. Required.
I got completely unexpected prompt:
$ go run analog_read.go
-pin string
Sensor's pin-ID to read analog value from. Required.
-test.bench string
regular expression to select benchmarks to run
-test.benchmem
print memory allocations for benchmarks
-test.benchtime duration
#
# ... and many more
#
Somehow flags from testing
package got mixed into flags of my app. WTF?
Testing package
testing package is not intended to be used outside of *_test.go
files(unless it’s Ok to have side-effects the package produces).
If you look at its source code, it adds the flags I didn’t expect to show up in my little CLI tool:
var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run")
var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark")
// etc
But why would those flags show up if no code imports the package?
Well, because some other code import
s the package!!?
Investigation
Luckily I had only single suspect to investigate: Gobot, because there’s no other packages imported aside ones from standard library in my app.
Quickly grepping through Gobot’s sources revealed what non-test code imports testing package:
gobot $ grep -nr '"testing"' ./|grep -v "_test.go"
./gobot/generate.go:317: "testing"
./utils.go:13: "testing"
where,
gobot/generate
is not the offender as its "testing"
is just a template string.
So, turns out gobot/utils.go
(which is not a test) was import
ing "testing"
and causing problems.
Apparently just to introduce test helpers Assert
and Refute
and enable other packages to re-use them.
Refactoring
Right solution for sharing code that imports "testing"
package is to extract it into separate package(gobot/gobottest
in this case), so only test code could import it.
Given the fact that a lot of tests depend on Assert
and Refute
helpers it was a good opportunity to use goftm
as a refactor tool:
$ gofmt -r 'gobot.Assert -> gobottest.Assert'
$ gofmt -r 'gobot.Refute -> gobottest.Refute'
Did the job and after moving helper code to the new package everything started working as expected!
Great Success!
Conclusion
Do not import "testing"
package in your non-test code!
Links
- The Gobot PR