| // 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 testing_test |
| |
| import ( |
| "flag" |
| "fmt" |
| "internal/testenv" |
| "os" |
| "os/exec" |
| "regexp" |
| "runtime" |
| "strings" |
| "testing" |
| ) |
| |
| var testPanicTest = flag.String("test_panic_test", "", "TestPanic: indicates which test should panic") |
| var testPanicParallel = flag.Bool("test_panic_parallel", false, "TestPanic: run subtests in parallel") |
| var testPanicCleanup = flag.Bool("test_panic_cleanup", false, "TestPanic: indicates whether test should call Cleanup") |
| var testPanicCleanupPanic = flag.String("test_panic_cleanup_panic", "", "TestPanic: indicate whether test should call Cleanup function that panics") |
| |
| func TestPanic(t *testing.T) { |
| testenv.MustHaveExec(t) |
| |
| testCases := []struct { |
| desc string |
| flags []string |
| want string |
| }{{ |
| desc: "root test panics", |
| flags: []string{"-test_panic_test=TestPanicHelper"}, |
| want: ` |
| --- FAIL: TestPanicHelper (N.NNs) |
| panic_test.go:NNN: TestPanicHelper |
| `, |
| }, { |
| desc: "subtest panics", |
| flags: []string{"-test_panic_test=TestPanicHelper/1"}, |
| want: ` |
| --- FAIL: TestPanicHelper (N.NNs) |
| panic_test.go:NNN: TestPanicHelper |
| --- FAIL: TestPanicHelper/1 (N.NNs) |
| panic_test.go:NNN: TestPanicHelper/1 |
| `, |
| }, { |
| desc: "subtest panics with cleanup", |
| flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup"}, |
| want: ` |
| ran inner cleanup 1 |
| ran middle cleanup 1 |
| ran outer cleanup |
| --- FAIL: TestPanicHelper (N.NNs) |
| panic_test.go:NNN: TestPanicHelper |
| --- FAIL: TestPanicHelper/1 (N.NNs) |
| panic_test.go:NNN: TestPanicHelper/1 |
| `, |
| }, { |
| desc: "subtest panics with outer cleanup panic", |
| flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=outer"}, |
| want: ` |
| ran inner cleanup 1 |
| ran middle cleanup 1 |
| ran outer cleanup |
| --- FAIL: TestPanicHelper (N.NNs) |
| panic_test.go:NNN: TestPanicHelper |
| `, |
| }, { |
| desc: "subtest panics with middle cleanup panic", |
| flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=middle"}, |
| want: ` |
| ran inner cleanup 1 |
| ran middle cleanup 1 |
| ran outer cleanup |
| --- FAIL: TestPanicHelper (N.NNs) |
| panic_test.go:NNN: TestPanicHelper |
| --- FAIL: TestPanicHelper/1 (N.NNs) |
| panic_test.go:NNN: TestPanicHelper/1 |
| `, |
| }, { |
| desc: "subtest panics with inner cleanup panic", |
| flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=inner"}, |
| want: ` |
| ran inner cleanup 1 |
| ran middle cleanup 1 |
| ran outer cleanup |
| --- FAIL: TestPanicHelper (N.NNs) |
| panic_test.go:NNN: TestPanicHelper |
| --- FAIL: TestPanicHelper/1 (N.NNs) |
| panic_test.go:NNN: TestPanicHelper/1 |
| `, |
| }, { |
| desc: "parallel subtest panics with cleanup", |
| flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_parallel"}, |
| want: ` |
| ran inner cleanup 1 |
| ran middle cleanup 1 |
| ran outer cleanup |
| --- FAIL: TestPanicHelper (N.NNs) |
| panic_test.go:NNN: TestPanicHelper |
| --- FAIL: TestPanicHelper/1 (N.NNs) |
| panic_test.go:NNN: TestPanicHelper/1 |
| `, |
| }, { |
| desc: "parallel subtest panics with outer cleanup panic", |
| flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=outer", "-test_panic_parallel"}, |
| want: ` |
| ran inner cleanup 1 |
| ran middle cleanup 1 |
| ran outer cleanup |
| --- FAIL: TestPanicHelper (N.NNs) |
| panic_test.go:NNN: TestPanicHelper |
| `, |
| }, { |
| desc: "parallel subtest panics with middle cleanup panic", |
| flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=middle", "-test_panic_parallel"}, |
| want: ` |
| ran inner cleanup 1 |
| ran middle cleanup 1 |
| ran outer cleanup |
| --- FAIL: TestPanicHelper (N.NNs) |
| panic_test.go:NNN: TestPanicHelper |
| --- FAIL: TestPanicHelper/1 (N.NNs) |
| panic_test.go:NNN: TestPanicHelper/1 |
| `, |
| }, { |
| desc: "parallel subtest panics with inner cleanup panic", |
| flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=inner", "-test_panic_parallel"}, |
| want: ` |
| ran inner cleanup 1 |
| ran middle cleanup 1 |
| ran outer cleanup |
| --- FAIL: TestPanicHelper (N.NNs) |
| panic_test.go:NNN: TestPanicHelper |
| --- FAIL: TestPanicHelper/1 (N.NNs) |
| panic_test.go:NNN: TestPanicHelper/1 |
| `, |
| }} |
| for _, tc := range testCases { |
| t.Run(tc.desc, func(t *testing.T) { |
| cmd := exec.Command(os.Args[0], "-test.run=TestPanicHelper") |
| cmd.Args = append(cmd.Args, tc.flags...) |
| cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") |
| b, _ := cmd.CombinedOutput() |
| got := string(b) |
| want := strings.TrimSpace(tc.want) |
| re := makeRegexp(want) |
| if ok, err := regexp.MatchString(re, got); !ok || err != nil { |
| t.Errorf("output:\ngot:\n%s\nwant:\n%s", got, want) |
| } |
| }) |
| } |
| } |
| |
| func makeRegexp(s string) string { |
| s = regexp.QuoteMeta(s) |
| s = strings.ReplaceAll(s, ":NNN:", `:\d+:`) |
| s = strings.ReplaceAll(s, "N\\.NNs", `\d*\.\d*s`) |
| return s |
| } |
| |
| func TestPanicHelper(t *testing.T) { |
| if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { |
| return |
| } |
| t.Log(t.Name()) |
| if t.Name() == *testPanicTest { |
| panic("panic") |
| } |
| switch *testPanicCleanupPanic { |
| case "", "outer", "middle", "inner": |
| default: |
| t.Fatalf("bad -test_panic_cleanup_panic: %s", *testPanicCleanupPanic) |
| } |
| t.Cleanup(func() { |
| fmt.Println("ran outer cleanup") |
| if *testPanicCleanupPanic == "outer" { |
| panic("outer cleanup") |
| } |
| }) |
| for i := 0; i < 3; i++ { |
| i := i |
| t.Run(fmt.Sprintf("%v", i), func(t *testing.T) { |
| chosen := t.Name() == *testPanicTest |
| if chosen && *testPanicCleanup { |
| t.Cleanup(func() { |
| fmt.Printf("ran middle cleanup %d\n", i) |
| if *testPanicCleanupPanic == "middle" { |
| panic("middle cleanup") |
| } |
| }) |
| } |
| if chosen && *testPanicParallel { |
| t.Parallel() |
| } |
| t.Log(t.Name()) |
| if chosen { |
| if *testPanicCleanup { |
| t.Cleanup(func() { |
| fmt.Printf("ran inner cleanup %d\n", i) |
| if *testPanicCleanupPanic == "inner" { |
| panic("inner cleanup") |
| } |
| }) |
| } |
| panic("panic") |
| } |
| }) |
| } |
| } |
| |
| func TestMorePanic(t *testing.T) { |
| testenv.MustHaveExec(t) |
| |
| testCases := []struct { |
| desc string |
| flags []string |
| want string |
| }{ |
| { |
| desc: "Issue 48502: call runtime.Goexit in t.Cleanup after panic", |
| flags: []string{"-test.run=TestGoexitInCleanupAfterPanicHelper"}, |
| want: `panic: die |
| panic: test executed panic(nil) or runtime.Goexit`, |
| }, |
| { |
| desc: "Issue 48515: call t.Run in t.Cleanup should trigger panic", |
| flags: []string{"-test.run=TestCallRunInCleanupHelper"}, |
| want: `panic: testing: t.Run called during t.Cleanup`, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| cmd := exec.Command(os.Args[0], tc.flags...) |
| cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") |
| b, _ := cmd.CombinedOutput() |
| got := string(b) |
| want := tc.want |
| re := makeRegexp(want) |
| if ok, err := regexp.MatchString(re, got); !ok || err != nil { |
| t.Errorf("output:\ngot:\n%s\nwant:\n%s", got, want) |
| } |
| } |
| } |
| |
| func TestCallRunInCleanupHelper(t *testing.T) { |
| if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { |
| return |
| } |
| |
| t.Cleanup(func() { |
| t.Run("in-cleanup", func(t *testing.T) { |
| t.Log("must not be executed") |
| }) |
| }) |
| } |
| |
| func TestGoexitInCleanupAfterPanicHelper(t *testing.T) { |
| if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { |
| return |
| } |
| |
| t.Cleanup(func() { runtime.Goexit() }) |
| t.Parallel() |
| panic("die") |
| } |