Source file src/testing/panic_test.go

     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  
     5  package testing_test
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"internal/testenv"
    11  	"os"
    12  	"os/exec"
    13  	"regexp"
    14  	"runtime"
    15  	"strings"
    16  	"testing"
    17  )
    18  
    19  var testPanicTest = flag.String("test_panic_test", "", "TestPanic: indicates which test should panic")
    20  var testPanicParallel = flag.Bool("test_panic_parallel", false, "TestPanic: run subtests in parallel")
    21  var testPanicCleanup = flag.Bool("test_panic_cleanup", false, "TestPanic: indicates whether test should call Cleanup")
    22  var testPanicCleanupPanic = flag.String("test_panic_cleanup_panic", "", "TestPanic: indicate whether test should call Cleanup function that panics")
    23  
    24  func 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: `
    51  ran inner cleanup 1
    52  ran middle cleanup 1
    53  ran 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: `
    63  ran inner cleanup 1
    64  ran middle cleanup 1
    65  ran 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: `
    73  ran inner cleanup 1
    74  ran middle cleanup 1
    75  ran 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: `
    85  ran inner cleanup 1
    86  ran middle cleanup 1
    87  ran 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: `
    97  ran inner cleanup 1
    98  ran middle cleanup 1
    99  ran 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: `
   109  ran inner cleanup 1
   110  ran middle cleanup 1
   111  ran 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: `
   119  ran inner cleanup 1
   120  ran middle cleanup 1
   121  ran 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: `
   131  ran inner cleanup 1
   132  ran middle cleanup 1
   133  ran 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  
   156  func 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  
   163  func 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  
   213  func 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  
   247  func 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  
   259  func 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  

View as plain text