// 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") }