| // Copyright 2020 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 runtime_test | |
| import ( | |
| "runtime" | |
| "slices" | |
| "strings" | |
| "testing" | |
| "time" | |
| ) | |
| // Test that panics print out the underlying value | |
| // when the underlying kind is directly printable. | |
| // Issue: https://golang.org/issues/37531 | |
| func TestPanicWithDirectlyPrintableCustomTypes(t *testing.T) { | |
| tests := []struct { | |
| name string | |
| wantPanicPrefix string | |
| }{ | |
| {"panicCustomBool", `panic: main.MyBool(true)`}, | |
| {"panicCustomComplex128", `panic: main.MyComplex128(32.1+10i)`}, | |
| {"panicCustomComplex64", `panic: main.MyComplex64(0.11+3i)`}, | |
| {"panicCustomFloat32", `panic: main.MyFloat32(-93.7)`}, | |
| {"panicCustomFloat64", `panic: main.MyFloat64(-93.7)`}, | |
| {"panicCustomInt", `panic: main.MyInt(93)`}, | |
| {"panicCustomInt8", `panic: main.MyInt8(93)`}, | |
| {"panicCustomInt16", `panic: main.MyInt16(93)`}, | |
| {"panicCustomInt32", `panic: main.MyInt32(93)`}, | |
| {"panicCustomInt64", `panic: main.MyInt64(93)`}, | |
| {"panicCustomString", `panic: main.MyString("Panic` + "\n\t" + `line two")`}, | |
| {"panicCustomUint", `panic: main.MyUint(93)`}, | |
| {"panicCustomUint8", `panic: main.MyUint8(93)`}, | |
| {"panicCustomUint16", `panic: main.MyUint16(93)`}, | |
| {"panicCustomUint32", `panic: main.MyUint32(93)`}, | |
| {"panicCustomUint64", `panic: main.MyUint64(93)`}, | |
| {"panicCustomUintptr", `panic: main.MyUintptr(93)`}, | |
| {"panicDeferFatal", "panic: runtime.errorString(\"invalid memory address or nil pointer dereference\")\n\tfatal error: sync: unlock of unlocked mutex"}, | |
| {"panicDoublieDeferFatal", "panic: runtime.errorString(\"invalid memory address or nil pointer dereference\") [recovered, repanicked]\n\tfatal error: sync: unlock of unlocked mutex"}, | |
| } | |
| for _, tt := range tests { | |
| t := t | |
| t.Run(tt.name, func(t *testing.T) { | |
| output := runTestProg(t, "testprog", tt.name) | |
| if !strings.HasPrefix(output, tt.wantPanicPrefix) { | |
| t.Fatalf("%q\nis not present in\n%s", tt.wantPanicPrefix, output) | |
| } | |
| }) | |
| } | |
| } | |
| func TestPanicRecoverSpeed(t *testing.T) { | |
| // For issue 77062. | |
| t.Skip("This test is too flaky at the moment. But it does normally pass. Suggestions for making it less flaky are welcome.") | |
| // Recursive function that does defer/recover/repanic. | |
| var f func(int) | |
| f = func(n int) { | |
| if n == 0 { | |
| panic("done") | |
| } | |
| defer func() { | |
| err := recover() | |
| panic(err) | |
| }() | |
| f(n - 1) | |
| } | |
| time := func(f func()) time.Duration { | |
| var times []time.Duration | |
| for range 10 { | |
| start := time.Now() | |
| f() | |
| times = append(times, time.Since(start)) | |
| } | |
| slices.Sort(times) | |
| times = times[1 : len(times)-1] // skip high and low, to reduce noise | |
| var avg time.Duration | |
| for _, v := range times { | |
| avg += v / time.Duration(len(times)) | |
| } | |
| return avg | |
| } | |
| a := time(func() { | |
| defer func() { recover() }() | |
| f(1024) | |
| }) | |
| b := time(func() { | |
| defer func() { recover() }() | |
| f(2048) | |
| }) | |
| m := b.Seconds() / a.Seconds() | |
| t.Logf("a: %v, b: %v, m: %v", a, b, m) | |
| if m > 3.5 { | |
| t.Errorf("more than 2x time increase: %v", m) | |
| } | |
| } | |
| // Test that panics with nil arguments produce the prefix | |
| // "runtime error:" per https://golang.org/issues/63813. | |
| func TestPanicNilErrorPrefix(t *testing.T) { | |
| tests := []struct { | |
| name string | |
| wantPanic string | |
| fn func() | |
| }{ | |
| { | |
| name: "panic(nil)", | |
| wantPanic: "runtime error: panic with nil argument", | |
| fn: func() { | |
| panic(nil) | |
| }, | |
| }, | |
| { | |
| name: "panic((any)(nil))", | |
| wantPanic: "runtime error: panic with nil argument", | |
| fn: func() { | |
| var foo any = nil | |
| panic(foo) | |
| }, | |
| }, | |
| { | |
| name: "panic((error)(nil))", | |
| wantPanic: "runtime error: panic with nil argument", | |
| fn: func() { | |
| var err error | |
| panic(err) | |
| }, | |
| }, | |
| } | |
| for _, tt := range tests { | |
| tt := tt | |
| t.Run(tt.name, func(t *testing.T) { | |
| defer func() { | |
| r := recover() | |
| if r == nil { | |
| t.Fatal("expected a panic") | |
| } | |
| re, ok := r.(runtime.Error) | |
| if !ok { | |
| t.Fatalf("wrong panic type: got %T, want runtime.Error", r) | |
| } | |
| if !strings.Contains(re.Error(), "runtime error: panic called with nil argument") { | |
| t.Fatalf("mismatched message, missing `runtime error: panic with nil`, : got:\n%s", re.Error()) | |
| } | |
| }() | |
| tt.fn() | |
| }) | |
| } | |
| } |