diff --git a/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go b/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go index 9dff17ea534..8ae21db4109 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go @@ -17,7 +17,12 @@ limitations under the License. package runtime import ( + "bytes" "fmt" + "io" + "os" + "regexp" + "strings" "testing" ) @@ -69,3 +74,66 @@ func TestCustomHandleError(t *testing.T) { t.Errorf("did not receive custom handler") } } + +func TestHandleCrashLog(t *testing.T) { + log, err := captureStderr(func() { + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected a panic to recover from") + } + }() + defer HandleCrash() + panic("test panic") + }) + if err != nil { + t.Fatalf("%v", err) + } + // Example log: + // + // ...] Observed a panic: test panic + // goroutine 6 [running]: + // command-line-arguments.logPanic(0x..., 0x...) + // .../src/k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime.go:69 +0x... + lines := strings.Split(log, "\n") + if len(lines) < 4 { + t.Fatalf("panic log should have 1 line of message, 1 line per goroutine and 2 lines per function call") + } + if match, _ := regexp.MatchString("Observed a panic: test panic", lines[0]); !match { + t.Errorf("mismatch panic message: %s", lines[0]) + } + // The following regexp's verify that Kubernetes panic log matches Golang stdlib + // stacktrace pattern. We need to update these regexp's if stdlib changes its pattern. + if match, _ := regexp.MatchString(`goroutine [0-9]+ \[.+\]:`, lines[1]); !match { + t.Errorf("mismatch goroutine: %s", lines[1]) + } + if match, _ := regexp.MatchString(`logPanic(.*)`, lines[2]); !match { + t.Errorf("mismatch symbolized function name: %s", lines[2]) + } + if match, _ := regexp.MatchString(`runtime\.go:[0-9]+ \+0x`, lines[3]); !match { + t.Errorf("mismatch file/line/offset information: %s", lines[3]) + } +} + +// captureStderr redirects stderr to result string, and then restore stderr from backup +func captureStderr(f func()) (string, error) { + r, w, err := os.Pipe() + if err != nil { + return "", err + } + bak := os.Stderr + os.Stderr = w + defer func() { os.Stderr = bak }() + + resultCh := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + resultCh <- buf.String() + }() + + f() + w.Close() + + return <-resultCh, nil +}