1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package testing_test
6
7import (
8	"flag"
9	"fmt"
10	"internal/testenv"
11	"os"
12	"os/exec"
13	"regexp"
14	"runtime"
15	"strings"
16	"testing"
17)
18
19var testPanicTest = flag.String("test_panic_test", "", "TestPanic: indicates which test should panic")
20var testPanicParallel = flag.Bool("test_panic_parallel", false, "TestPanic: run subtests in parallel")
21var testPanicCleanup = flag.Bool("test_panic_cleanup", false, "TestPanic: indicates whether test should call Cleanup")
22var testPanicCleanupPanic = flag.String("test_panic_cleanup_panic", "", "TestPanic: indicate whether test should call Cleanup function that panics")
23
24func TestPanic(t *testing.T) {
25	testenv.MustHaveExec(t)
26
27	testCases := []struct {
28		desc  string
29		flags []string
30		want  string
31	}{{
32		desc:  "root test panics",
33		flags: []string{"-test_panic_test=TestPanicHelper"},
34		want: `
35--- FAIL: TestPanicHelper (N.NNs)
36    panic_test.go:NNN: TestPanicHelper
37`,
38	}, {
39		desc:  "subtest panics",
40		flags: []string{"-test_panic_test=TestPanicHelper/1"},
41		want: `
42--- FAIL: TestPanicHelper (N.NNs)
43    panic_test.go:NNN: TestPanicHelper
44    --- FAIL: TestPanicHelper/1 (N.NNs)
45        panic_test.go:NNN: TestPanicHelper/1
46`,
47	}, {
48		desc:  "subtest panics with cleanup",
49		flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup"},
50		want: `
51ran inner cleanup 1
52ran middle cleanup 1
53ran outer cleanup
54--- FAIL: TestPanicHelper (N.NNs)
55    panic_test.go:NNN: TestPanicHelper
56    --- FAIL: TestPanicHelper/1 (N.NNs)
57        panic_test.go:NNN: TestPanicHelper/1
58`,
59	}, {
60		desc:  "subtest panics with outer cleanup panic",
61		flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=outer"},
62		want: `
63ran inner cleanup 1
64ran middle cleanup 1
65ran outer cleanup
66--- FAIL: TestPanicHelper (N.NNs)
67    panic_test.go:NNN: TestPanicHelper
68`,
69	}, {
70		desc:  "subtest panics with middle cleanup panic",
71		flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=middle"},
72		want: `
73ran inner cleanup 1
74ran middle cleanup 1
75ran outer cleanup
76--- FAIL: TestPanicHelper (N.NNs)
77    panic_test.go:NNN: TestPanicHelper
78    --- FAIL: TestPanicHelper/1 (N.NNs)
79        panic_test.go:NNN: TestPanicHelper/1
80`,
81	}, {
82		desc:  "subtest panics with inner cleanup panic",
83		flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=inner"},
84		want: `
85ran inner cleanup 1
86ran middle cleanup 1
87ran outer cleanup
88--- FAIL: TestPanicHelper (N.NNs)
89    panic_test.go:NNN: TestPanicHelper
90    --- FAIL: TestPanicHelper/1 (N.NNs)
91        panic_test.go:NNN: TestPanicHelper/1
92`,
93	}, {
94		desc:  "parallel subtest panics with cleanup",
95		flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_parallel"},
96		want: `
97ran inner cleanup 1
98ran middle cleanup 1
99ran outer cleanup
100--- FAIL: TestPanicHelper (N.NNs)
101    panic_test.go:NNN: TestPanicHelper
102    --- FAIL: TestPanicHelper/1 (N.NNs)
103        panic_test.go:NNN: TestPanicHelper/1
104`,
105	}, {
106		desc:  "parallel subtest panics with outer cleanup panic",
107		flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=outer", "-test_panic_parallel"},
108		want: `
109ran inner cleanup 1
110ran middle cleanup 1
111ran outer cleanup
112--- FAIL: TestPanicHelper (N.NNs)
113    panic_test.go:NNN: TestPanicHelper
114`,
115	}, {
116		desc:  "parallel subtest panics with middle cleanup panic",
117		flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=middle", "-test_panic_parallel"},
118		want: `
119ran inner cleanup 1
120ran middle cleanup 1
121ran outer cleanup
122--- FAIL: TestPanicHelper (N.NNs)
123    panic_test.go:NNN: TestPanicHelper
124    --- FAIL: TestPanicHelper/1 (N.NNs)
125        panic_test.go:NNN: TestPanicHelper/1
126`,
127	}, {
128		desc:  "parallel subtest panics with inner cleanup panic",
129		flags: []string{"-test_panic_test=TestPanicHelper/1", "-test_panic_cleanup", "-test_panic_cleanup_panic=inner", "-test_panic_parallel"},
130		want: `
131ran inner cleanup 1
132ran middle cleanup 1
133ran outer cleanup
134--- FAIL: TestPanicHelper (N.NNs)
135    panic_test.go:NNN: TestPanicHelper
136    --- FAIL: TestPanicHelper/1 (N.NNs)
137        panic_test.go:NNN: TestPanicHelper/1
138`,
139	}}
140	for _, tc := range testCases {
141		t.Run(tc.desc, func(t *testing.T) {
142			cmd := exec.Command(os.Args[0], "-test.run=^TestPanicHelper$")
143			cmd.Args = append(cmd.Args, tc.flags...)
144			cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
145			b, _ := cmd.CombinedOutput()
146			got := string(b)
147			want := strings.TrimSpace(tc.want)
148			re := makeRegexp(want)
149			if ok, err := regexp.MatchString(re, got); !ok || err != nil {
150				t.Errorf("output:\ngot:\n%s\nwant:\n%s", got, want)
151			}
152		})
153	}
154}
155
156func makeRegexp(s string) string {
157	s = regexp.QuoteMeta(s)
158	s = strings.ReplaceAll(s, ":NNN:", `:\d+:`)
159	s = strings.ReplaceAll(s, "N\\.NNs", `\d*\.\d*s`)
160	return s
161}
162
163func TestPanicHelper(t *testing.T) {
164	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
165		return
166	}
167	t.Log(t.Name())
168	if t.Name() == *testPanicTest {
169		panic("panic")
170	}
171	switch *testPanicCleanupPanic {
172	case "", "outer", "middle", "inner":
173	default:
174		t.Fatalf("bad -test_panic_cleanup_panic: %s", *testPanicCleanupPanic)
175	}
176	t.Cleanup(func() {
177		fmt.Println("ran outer cleanup")
178		if *testPanicCleanupPanic == "outer" {
179			panic("outer cleanup")
180		}
181	})
182	for i := 0; i < 3; i++ {
183		i := i
184		t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
185			chosen := t.Name() == *testPanicTest
186			if chosen && *testPanicCleanup {
187				t.Cleanup(func() {
188					fmt.Printf("ran middle cleanup %d\n", i)
189					if *testPanicCleanupPanic == "middle" {
190						panic("middle cleanup")
191					}
192				})
193			}
194			if chosen && *testPanicParallel {
195				t.Parallel()
196			}
197			t.Log(t.Name())
198			if chosen {
199				if *testPanicCleanup {
200					t.Cleanup(func() {
201						fmt.Printf("ran inner cleanup %d\n", i)
202						if *testPanicCleanupPanic == "inner" {
203							panic("inner cleanup")
204						}
205					})
206				}
207				panic("panic")
208			}
209		})
210	}
211}
212
213func TestMorePanic(t *testing.T) {
214	testenv.MustHaveExec(t)
215
216	testCases := []struct {
217		desc  string
218		flags []string
219		want  string
220	}{
221		{
222			desc:  "Issue 48502: call runtime.Goexit in t.Cleanup after panic",
223			flags: []string{"-test.run=^TestGoexitInCleanupAfterPanicHelper$"},
224			want: `panic: die
225	panic: test executed panic(nil) or runtime.Goexit`,
226		},
227		{
228			desc:  "Issue 48515: call t.Run in t.Cleanup should trigger panic",
229			flags: []string{"-test.run=^TestCallRunInCleanupHelper$"},
230			want:  `panic: testing: t.Run called during t.Cleanup`,
231		},
232	}
233
234	for _, tc := range testCases {
235		cmd := exec.Command(os.Args[0], tc.flags...)
236		cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
237		b, _ := cmd.CombinedOutput()
238		got := string(b)
239		want := tc.want
240		re := makeRegexp(want)
241		if ok, err := regexp.MatchString(re, got); !ok || err != nil {
242			t.Errorf("output:\ngot:\n%s\nwant:\n%s", got, want)
243		}
244	}
245}
246
247func TestCallRunInCleanupHelper(t *testing.T) {
248	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
249		return
250	}
251
252	t.Cleanup(func() {
253		t.Run("in-cleanup", func(t *testing.T) {
254			t.Log("must not be executed")
255		})
256	})
257}
258
259func TestGoexitInCleanupAfterPanicHelper(t *testing.T) {
260	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
261		return
262	}
263
264	t.Cleanup(func() { runtime.Goexit() })
265	t.Parallel()
266	panic("die")
267}
268