diff --git a/doc/go1.15.html b/doc/go1.15.html
index b4319874c9b..aa951eefad2 100644
--- a/doc/go1.15.html
+++ b/doc/go1.15.html
@@ -86,6 +86,13 @@ TODO
that reports the time at which the test binary will have exceeded its
timeout.
+
+ A TestMain
function is no longer required to call
+ os.Exit
. If a TestMain
function returns,
+ the test binary will call os.Exit
with the value returned
+ by m.Run
.
+
+
Minor changes to the library
diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go
index fefc7d2e307..866e0e567f2 100644
--- a/src/cmd/go/internal/load/test.go
+++ b/src/cmd/go/internal/load/test.go
@@ -26,6 +26,7 @@ import (
var TestMainDeps = []string{
// Dependencies for testmain.
"os",
+ "reflect",
"testing",
"testing/internal/testdeps",
}
@@ -612,8 +613,9 @@ var testmainTmpl = lazytemplate.New("main", `
package main
import (
-{{if not .TestMain}}
"os"
+{{if .TestMain}}
+ "reflect"
{{end}}
"testing"
"testing/internal/testdeps"
@@ -704,6 +706,7 @@ func main() {
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
+ os.Exit(int(reflect.ValueOf(m).Elem().FieldByName("exitCode").Int()))
{{else}}
os.Exit(m.Run())
{{end}}
diff --git a/src/cmd/go/testdata/script/list_importmap.txt b/src/cmd/go/testdata/script/list_importmap.txt
index 52ee6028f5b..f424b9814c6 100644
--- a/src/cmd/go/testdata/script/list_importmap.txt
+++ b/src/cmd/go/testdata/script/list_importmap.txt
@@ -16,7 +16,7 @@ go list -deps -test -f '{{.ImportPath}} MAP: {{.ImportMap}}{{"\n"}}{{.ImportPath
stdout '^flag \[fmt\.test\] MAP: map\[fmt:fmt \[fmt\.test\]\]'
stdout '^fmt\.test MAP: map\[(.* )?testing:testing \[fmt\.test\]'
! stdout '^fmt\.test MAP: map\[(.* )?os:'
-stdout '^fmt\.test IMPORT: \[fmt \[fmt\.test\] fmt_test \[fmt\.test\] os testing \[fmt\.test\] testing/internal/testdeps \[fmt\.test\]\]'
+stdout '^fmt\.test IMPORT: \[fmt \[fmt\.test\] fmt_test \[fmt\.test\] os reflect testing \[fmt\.test\] testing/internal/testdeps \[fmt\.test\]\]'
-- a/b/b.go --
diff --git a/src/cmd/go/testdata/script/list_test_imports.txt b/src/cmd/go/testdata/script/list_test_imports.txt
index b2a6bc45f9e..0342eba8623 100644
--- a/src/cmd/go/testdata/script/list_test_imports.txt
+++ b/src/cmd/go/testdata/script/list_test_imports.txt
@@ -16,6 +16,6 @@ package b_test; import _ "a"
-- imports.txt --
a: b
b:
-b.test: b [b.test], b_test [b.test], os, testing, testing/internal/testdeps
+b.test: b [b.test], b_test [b.test], os, reflect, testing, testing/internal/testdeps
b [b.test]:
b_test [b.test]: a [b.test]
diff --git a/src/cmd/go/testdata/script/test_main.txt b/src/cmd/go/testdata/script/test_main.txt
index e255eab7e11..25d02e4465c 100644
--- a/src/cmd/go/testdata/script/test_main.txt
+++ b/src/cmd/go/testdata/script/test_main.txt
@@ -12,6 +12,10 @@ stdout '^ok.*\[no tests to run\]'
! go test standalone_main_wrong_test.go
stderr 'wrong signature for TestMain, must be: func TestMain\(m \*testing.M\)'
+# Test TestMain does not call os.Exit (Issue #34129)
+! go test standalone_testmain_not_call_os_exit_test.go
+! stdout '^ok'
+
-- standalone_main_normal_test.go --
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
@@ -64,3 +68,25 @@ func TestMain(m *testing.M) {
}
os.Exit(m.Run())
}
+-- standalone_testmain_not_call_os_exit_test.go --
+// Copyright 2020 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_not_call_os_exit_test
+
+import (
+ "testing"
+)
+
+func TestWillFail(t *testing.T) {
+ t.Error("this test will fail.")
+}
+
+func TestMain(m *testing.M) {
+ defer func() {
+ recover()
+ }()
+ exit := m.Run()
+ panic(exit)
+}
diff --git a/src/testing/testing.go b/src/testing/testing.go
index 83cd72fff33..039d3e62090 100644
--- a/src/testing/testing.go
+++ b/src/testing/testing.go
@@ -217,10 +217,11 @@
//
// then the generated test will call TestMain(m) instead of running the tests
// directly. TestMain runs in the main goroutine and can do whatever setup
-// and teardown is necessary around a call to m.Run. It should then call
-// os.Exit with the result of m.Run. When TestMain is called, flag.Parse has
-// not been run. If TestMain depends on command-line flags, including those
-// of the testing package, it should call flag.Parse explicitly.
+// and teardown is necessary around a call to m.Run. m.Run will return an exit
+// status that may be passed to os.Exit. If TestMain returns, the test wrapper
+// will pass the result of m.Run to os.Exit itself. When TestMain is called,
+// flag.Parse has not been run. If TestMain depends on command-line flags,
+// including those of the testing package, it should call flag.Parse explicitly.
//
// A simple implementation of TestMain is:
//
@@ -1148,6 +1149,10 @@ type M struct {
afterOnce sync.Once
numRun int
+
+ // value to pass to os.Exit, the outer test func main
+ // harness calls os.Exit with this code. See #34129.
+ exitCode int
}
// testDeps is an internal interface of functionality that is
@@ -1178,7 +1183,11 @@ func MainStart(deps testDeps, tests []InternalTest, benchmarks []InternalBenchma
}
// Run runs the tests. It returns an exit code to pass to os.Exit.
-func (m *M) Run() int {
+func (m *M) Run() (code int) {
+ defer func() {
+ code = m.exitCode
+ }()
+
// Count the number of calls to m.Run.
// We only ever expected 1, but we didn't enforce that,
// and now there are tests in the wild that call m.Run multiple times.
@@ -1193,12 +1202,14 @@ func (m *M) Run() int {
if *parallel < 1 {
fmt.Fprintln(os.Stderr, "testing: -parallel can only be given a positive integer")
flag.Usage()
- return 2
+ m.exitCode = 2
+ return
}
if len(*matchList) != 0 {
listTests(m.deps.MatchString, m.tests, m.benchmarks, m.examples)
- return 0
+ m.exitCode = 0
+ return
}
parseCpuList()
@@ -1215,11 +1226,13 @@ func (m *M) Run() int {
}
if !testOk || !exampleOk || !runBenchmarks(m.deps.ImportPath(), m.deps.MatchString, m.benchmarks) || race.Errors() > 0 {
fmt.Println("FAIL")
- return 1
+ m.exitCode = 1
+ return
}
fmt.Println("PASS")
- return 0
+ m.exitCode = 0
+ return
}
func (t *T) report() {