Commit fbc6a972 authored by Caleb Spare's avatar Caleb Spare Committed by Brad Fitzpatrick

testing: delay flag registration; move to an Init function

Any code that imports the testing package forces the testing flags to be
defined, even in non-test binaries. People work around this today by
defining a copy of the testing.TB interface just to avoid importing
testing.

Fix this by moving flag registration into a new function, testing.Init.
Delay calling Init until the testing binary begins to run, in
testing.MainStart.

Init is exported for cases where users need the testing flags to be
defined outside of a "go test" context. In particular, this may be
needed where testing.Benchmark is called outside of a test.

Fixes #21051

Change-Id: Ib7e02459e693c26ae1ba71bbae7d455a91118ee3
Reviewed-on: https://go-review.googlesource.com/c/go/+/173722Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 2b8cbc38
......@@ -3176,6 +3176,12 @@ func TestGoTestFooTestWorks(t *testing.T) {
tg.run("test", "testdata/standalone_test.go")
}
func TestGoTestTestMainSeesTestingFlags(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()
tg.run("test", "testdata/standalone_testmain_flag_test.go")
}
// Issue 22388
func TestGoTestMainWithWrongSignature(t *testing.T) {
tg := testgo(t)
......
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package standalone_testmain_flag_test
import (
"flag"
"fmt"
"os"
"testing"
)
func TestMain(m *testing.M) {
// A TestMain should be able to access testing flags if it calls
// flag.Parse without needing to use testing.Init.
flag.Parse()
found := false
flag.VisitAll(func(f *flag.Flag) {
if f.Name == "test.count" {
found = true
}
})
if !found {
fmt.Println("testing flags not registered")
os.Exit(1)
}
os.Exit(m.Run())
}
......@@ -21,14 +21,19 @@ import (
"unicode"
)
var matchBenchmarks = flag.String("test.bench", "", "run only benchmarks matching `regexp`")
var benchTime = benchTimeFlag{d: 1 * time.Second}
var benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks")
func init() {
func initBenchmarkFlags() {
matchBenchmarks = flag.String("test.bench", "", "run only benchmarks matching `regexp`")
benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks")
flag.Var(&benchTime, "test.benchtime", "run each benchmark for duration `d`")
}
var (
matchBenchmarks *string
benchmarkMemory *bool
benchTime = benchTimeFlag{d: 1 * time.Second} // changed during test of testing package
)
type benchTimeFlag struct {
d time.Duration
n int
......@@ -755,6 +760,9 @@ func (b *B) SetParallelism(p int) {
// Benchmark benchmarks a single function. It is useful for creating
// custom benchmarks that do not use the "go test" command.
//
// If f depends on testing flags, then Init must be used to register
// those flags before calling Benchmark and before calling flag.Parse.
//
// If f calls Run, the result will be an estimate of running all its
// subbenchmarks that don't call Run in sequence in a single benchmark.
func Benchmark(f func(b *B)) BenchmarkResult {
......
......@@ -16,7 +16,7 @@ import (
)
func init() {
// Make benchmark tests run 10* faster.
// Make benchmark tests run 10x faster.
benchTime.d = 100 * time.Millisecond
}
......
......@@ -249,7 +249,18 @@ import (
"time"
)
var (
var initRan bool
// Init registers testing flags. These flags are automatically registered by
// the "go test" command before running test functions, so Init is only needed
// when calling functions such as Benchmark without using "go test".
//
// Init has no effect if it was already called.
func Init() {
if initRan {
return
}
initRan = true
// The short flag requests that tests run more quickly, but its functionality
// is provided by test writers themselves. The testing package is just its
// home. The all.bash installation script sets it to make installation more
......@@ -265,7 +276,6 @@ var (
// this flag lets "go test" tell the binary to write the files in the directory where
// the "go test" command is run.
outputDir = flag.String("test.outputdir", "", "write profiles to `dir`")
// Report as tests are run; default is silent for success.
chatty = flag.Bool("test.v", false, "verbose: print additional output")
count = flag.Uint("test.count", 1, "run tests and benchmarks `n` times")
......@@ -285,6 +295,32 @@ var (
parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "run at most `n` tests in parallel")
testlog = flag.String("test.testlogfile", "", "write test action log to `file` (for use only by cmd/go)")
initBenchmarkFlags()
}
var (
// Flags, registered during Init.
short *bool
failFast *bool
outputDir *string
chatty *bool
count *uint
coverProfile *string
matchList *string
match *string
memProfile *string
memProfileRate *int
cpuProfile *string
blockProfile *string
blockProfileRate *int
mutexProfile *string
mutexProfileFraction *int
traceFile *string
timeout *time.Duration
cpuListStr *string
parallel *int
testlog *string
haveExamples bool // are there examples?
cpuList []int
......@@ -328,10 +364,13 @@ type common struct {
// Short reports whether the -test.short flag is set.
func Short() bool {
if short == nil {
panic("testing: Short called before Init")
}
// Catch code that calls this from TestMain without first
// calling flag.Parse. This shouldn't really be a panic
// calling flag.Parse. This shouldn't really be a panic.
if !flag.Parsed() {
fmt.Fprintf(os.Stderr, "testing: testing.Short called before flag.Parse\n")
fmt.Fprintf(os.Stderr, "testing: Short called before flag.Parse\n")
os.Exit(2)
}
......@@ -347,6 +386,14 @@ func CoverMode() string {
// Verbose reports whether the -test.v flag is set.
func Verbose() bool {
if chatty == nil {
panic("testing: Verbose called before Init")
}
// Same as in Short.
if !flag.Parsed() {
fmt.Fprintf(os.Stderr, "testing: Verbose called before flag.Parse\n")
os.Exit(2)
}
return *chatty
}
......@@ -1031,6 +1078,7 @@ type testDeps interface {
// It is not meant to be called directly and is not subject to the Go 1 compatibility document.
// It may change signature from release to release.
func MainStart(deps testDeps, tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) *M {
Init()
return &M{
deps: deps,
tests: tests,
......
......@@ -29,6 +29,7 @@ func BenchmarkSlowNonASCII(b *testing.B) {
}
func main() {
testing.Init()
os.Args = []string{os.Args[0], "-test.benchtime=100ms"}
flag.Parse()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment