Yes it is possible. In the test file ( xxx_test.go ) create your own function TestMain() and inside it after assembling dynamic tests (values โโof struct testing.InternalBenchmark ), call testing.Main() , which correctly analyzes command line flags, creates and installs testing.M and prepares and calls testing.RunBenchmarks() . That way your dynamic tests will still be run with go test .
Notes: testing.Main() will never return as it calls os.Exit() . If you want to do further logging and calculations based on test results, you can also call testing.MainStart() .Run() (this is what testing.Main() ), and you can pass the exit code that M.Run() on os.Exit() .
Below is the full test file, which you can simply run with go test -bench . .
Conclusion: test results of dynamically generated tests (different implementations with different parameters):
testing: warning: no tests to run PASS main.EngineA[impl=0, n=50, x=300]-4 100000 16716 ns/op main.EngineB[impl=1, n=50, x=300]-4 100000 24788 ns/op main.EngineA[impl=0, n=50, x=200]-4 100000 10764 ns/op main.EngineB[impl=1, n=50, x=200]-4 100000 16415 ns/op main.EngineA[impl=0, n=100, x=300]-4 50000 33426 ns/op main.EngineB[impl=1, n=100, x=300]-4 30000 48466 ns/op main.EngineA[impl=0, n=100, x=200]-4 50000 20452 ns/op main.EngineB[impl=1, n=100, x=200]-4 50000 33134 ns/op main.EngineA[impl=0, n=500, x=300]-4 10000 163087 ns/op main.EngineB[impl=1, n=500, x=300]-4 5000 238043 ns/op main.EngineA[impl=0, n=500, x=200]-4 10000 102662 ns/op main.EngineB[impl=1, n=500, x=200]-4 10000 163113 ns/op main.EngineA[impl=0, n=1000, x=300]-4 5000 319744 ns/op main.EngineB[impl=1, n=1000, x=300]-4 3000 512077 ns/op main.EngineA[impl=0, n=1000, x=200]-4 10000 201036 ns/op main.EngineB[impl=1, n=1000, x=200]-4 5000 325714 ns/op ok _/xxx/src/play 27.307s
And the source (test file, for example dynbench_test.go ):
package main import ( "fmt" "testing" ) type Engine interface { Calc() } type EngineA struct{ n, x int } func (e EngineA) Calc() { for i := 0; i < en; i++ { a, b := make([]byte, ex), make([]byte, ex) copy(b, a) } } type EngineB struct{ n, x int } func (e EngineB) Calc() { for i := 0; i < en*2; i++ { a, b := make([]byte, ex/2), make([]byte, ex/2) copy(b, a) } } func TestMain(m *testing.M) { implementations := [](func(n, x int) Engine){ func(n, x int) Engine { return EngineA{n, x} }, func(n, x int) Engine { return EngineB{n, x} }, } benchmarks := []testing.InternalBenchmark{} for _, n := range []int{50, 100, 500, 1000} { for _, x := range []int{300, 200} { for name, gen := range implementations { impl := gen(n, x) bm := testing.InternalBenchmark{ Name: fmt.Sprintf("%T[impl=%d, n=%d, x=%d]", impl, name, n, x)} bm.F = func(b *testing.B) { for i := 0; i < bN; i++ { impl.Calc() } } benchmarks = append(benchmarks, bm) } } } anything := func(pat, str string) (bool, error) { return true, nil } testing.Main(anything, nil, benchmarks, nil) }
Notes # 2:
testing.Main() , testing.MainStart() and testing.InternalBenchmark can be changed (or removed) in the next version of Go:
Internal function / internal type, but exported because it is a cross-package; part or called by running the "go test" command.