github.com/searKing/golang/go@v1.2.117/runtime/panic_test.go (about) 1 package runtime_test 2 3 import ( 4 "bytes" 5 "io" 6 "log" 7 "net/http" 8 "os" 9 "regexp" 10 "strings" 11 "testing" 12 13 "github.com/searKing/golang/go/runtime" 14 ) 15 16 func TestPanic_Recover(t *testing.T) { 17 defer func() { 18 if x := recover(); x == nil { 19 t.Errorf("Expected a panic to recover from") 20 } 21 }() 22 defer runtime.DefaultPanic.Recover() 23 panic("Test Panic") 24 } 25 26 func TestPanicWith(t *testing.T) { 27 var result any 28 func() { 29 defer func() { 30 if x := recover(); x == nil { 31 t.Errorf("Expected a panic to recover from") 32 } 33 }() 34 defer runtime.HandlePanicWith(func(r any) { 35 result = r 36 }).Recover() 37 panic("test") 38 }() 39 if result != "test" { 40 t.Errorf("did not receive custom handler") 41 } 42 } 43 44 func TestPanic_Recover_LogPanic(t *testing.T) { 45 log, err := captureStderr(func() { 46 defer func() { 47 if r := recover(); r == nil { 48 t.Fatalf("expected a panic to recover from") 49 } 50 }() 51 defer runtime.LogPanic.Recover() 52 panic("test panic") 53 }) 54 if err != nil { 55 t.Fatalf("%v", err) 56 } 57 // Example log: 58 // 59 // ...] Observed a panic: test panic 60 // goroutine 6 [running]: 61 // github.com/searKing/golang/go/runtime.logPanic(0x12a8b80, 0x130e590) 62 // .../src/github.com/searKing/golang/go/runtime/panic.go:86 +0xda 63 lines := strings.Split(log, "\n") 64 if len(lines) < 4 { 65 t.Fatalf("panic log should have 1 line of message, 1 line per goroutine and 2 lines per function call") 66 } 67 if match, _ := regexp.MatchString("Observed a panic: test panic", lines[0]); !match { 68 t.Errorf("mismatch panic message: %s", lines[0]) 69 } 70 // The following regexp's verify that Kubernetes panic log matches Golang stdlib 71 // stacktrace pattern. We need to update these regexp's if stdlib changes its pattern. 72 if match, _ := regexp.MatchString(`goroutine [0-9]+ \[.+\]:`, lines[1]); !match { 73 t.Errorf("mismatch goroutine: %s", lines[1]) 74 } 75 if match, _ := regexp.MatchString(`logPanic(.*)`, lines[2]); !match { 76 t.Errorf("mismatch symbolized function name: %s", lines[2]) 77 } 78 if match, _ := regexp.MatchString(`panic\.go:[0-9]+ \+0x`, lines[3]); !match { 79 t.Errorf("mismatch file/line/offset information: %s", lines[3]) 80 } 81 } 82 83 func TestPanic_Recover_LogPanicSilenceHTTPErrAbortHandler(t *testing.T) { 84 log, err := captureStderr(func() { 85 defer func() { 86 if r := recover(); r != http.ErrAbortHandler { 87 t.Fatalf("expected to recover from http.ErrAbortHandler") 88 } 89 }() 90 defer runtime.LogPanic.Recover() 91 panic(http.ErrAbortHandler) 92 }) 93 if err != nil { 94 t.Fatalf("%v", err) 95 } 96 if len(log) > 0 { 97 t.Fatalf("expected no stderr log, got: %s", log) 98 } 99 } 100 101 // captureStderr redirects stderr to result string, and then restore stderr from backup 102 func captureStderr(f func()) (string, error) { 103 r, w, err := os.Pipe() 104 if err != nil { 105 return "", err 106 } 107 bak := os.Stderr 108 os.Stderr = w 109 log.SetOutput(os.Stderr) 110 defer func() { 111 os.Stderr = bak 112 log.SetOutput(os.Stderr) 113 }() 114 115 resultCh := make(chan string) 116 // copy the output in a separate goroutine so printing can't block indefinitely 117 go func() { 118 var buf bytes.Buffer 119 _, _ = io.Copy(&buf, r) 120 resultCh <- buf.String() 121 }() 122 123 f() 124 _ = w.Close() 125 126 return <-resultCh, nil 127 }