Skip to content

Commit 225f484

Browse files
committed
misc, runtime, test: extra tests and benchmarks for defer
Add a bunch of extra tests and benchmarks for defer, in preparation for new low-cost (open-coded) implementation of defers (see #34481), - New file defer_test.go that tests a bunch more unusual defer scenarios, including things that might have problems for open-coded defers. - Additions to callers_test.go actually verifying what the stack trace looks like for various panic or panic-recover scenarios. - Additions to crash_test.go testing several more crash scenarios involving recursive panics. - New benchmark in runtime_test.go measuring speed of panic-recover - New CGo benchmark in cgo_test.go calling from Go to C back to Go that shows defer overhead Updates #34481 Change-Id: I423523f3e05fc0229d4277dd00073289a5526188 Reviewed-on: https://go-review.googlesource.com/c/go/+/197017 Run-TryBot: Dan Scales <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent 8c99e45 commit 225f484

File tree

8 files changed

+384
-11
lines changed

8 files changed

+384
-11
lines changed

misc/cgo/test/cgo_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,6 @@ func TestThreadLock(t *testing.T) { testThreadLockFunc(t) }
9191
func TestUnsignedInt(t *testing.T) { testUnsignedInt(t) }
9292
func TestZeroArgCallback(t *testing.T) { testZeroArgCallback(t) }
9393

94-
func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }
95-
func BenchmarkGoString(b *testing.B) { benchGoString(b) }
94+
func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }
95+
func BenchmarkGoString(b *testing.B) { benchGoString(b) }
96+
func BenchmarkCGoCallback(b *testing.B) { benchCallback(b) }

misc/cgo/test/test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,17 @@ func benchCgoCall(b *testing.B) {
10001000
}
10011001
}
10021002

1003+
// Benchmark measuring overhead from Go to C and back to Go (via a callback)
1004+
func benchCallback(b *testing.B) {
1005+
var x = false
1006+
for i := 0; i < b.N; i++ {
1007+
nestedCall(func() { x = true })
1008+
}
1009+
if !x {
1010+
b.Fatal("nestedCall was not invoked")
1011+
}
1012+
}
1013+
10031014
var sinkString string
10041015

10051016
func benchGoString(b *testing.B) {

src/runtime/callers_test.go

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,26 @@
55
package runtime_test
66

77
import (
8+
"reflect"
89
"runtime"
910
"strings"
1011
"testing"
1112
)
1213

1314
func f1(pan bool) []uintptr {
14-
return f2(pan) // line 14
15+
return f2(pan) // line 15
1516
}
1617

1718
func f2(pan bool) []uintptr {
18-
return f3(pan) // line 18
19+
return f3(pan) // line 19
1920
}
2021

2122
func f3(pan bool) []uintptr {
2223
if pan {
23-
panic("f3") // line 23
24+
panic("f3") // line 24
2425
}
2526
ret := make([]uintptr, 20)
26-
return ret[:runtime.Callers(0, ret)] // line 26
27+
return ret[:runtime.Callers(0, ret)] // line 27
2728
}
2829

2930
func testCallers(t *testing.T, pcs []uintptr, pan bool) {
@@ -47,16 +48,16 @@ func testCallers(t *testing.T, pcs []uintptr, pan bool) {
4748

4849
var f3Line int
4950
if pan {
50-
f3Line = 23
51+
f3Line = 24
5152
} else {
52-
f3Line = 26
53+
f3Line = 27
5354
}
5455
want := []struct {
5556
name string
5657
line int
5758
}{
58-
{"f1", 14},
59-
{"f2", 18},
59+
{"f1", 15},
60+
{"f2", 19},
6061
{"f3", f3Line},
6162
}
6263
for _, w := range want {
@@ -66,18 +67,124 @@ func testCallers(t *testing.T, pcs []uintptr, pan bool) {
6667
}
6768
}
6869

70+
func testCallersEqual(t *testing.T, pcs []uintptr, want []string) {
71+
got := make([]string, 0, len(want))
72+
73+
frames := runtime.CallersFrames(pcs)
74+
for {
75+
frame, more := frames.Next()
76+
if !more || len(got) >= len(want) {
77+
break
78+
}
79+
got = append(got, frame.Function)
80+
}
81+
if !reflect.DeepEqual(want, got) {
82+
t.Fatalf("wanted %v, got %v", want, got)
83+
}
84+
}
85+
6986
func TestCallers(t *testing.T) {
7087
testCallers(t, f1(false), false)
7188
}
7289

7390
func TestCallersPanic(t *testing.T) {
91+
// Make sure we don't have any extra frames on the stack (due to
92+
// open-coded defer processing)
93+
want := []string{"runtime.Callers", "runtime_test.TestCallersPanic.func1",
94+
"runtime.gopanic", "runtime_test.f3", "runtime_test.f2", "runtime_test.f1",
95+
"runtime_test.TestCallersPanic"}
96+
7497
defer func() {
7598
if r := recover(); r == nil {
7699
t.Fatal("did not panic")
77100
}
78101
pcs := make([]uintptr, 20)
79102
pcs = pcs[:runtime.Callers(0, pcs)]
80103
testCallers(t, pcs, true)
104+
testCallersEqual(t, pcs, want)
81105
}()
82106
f1(true)
83107
}
108+
109+
func TestCallersDoublePanic(t *testing.T) {
110+
// Make sure we don't have any extra frames on the stack (due to
111+
// open-coded defer processing)
112+
want := []string{"runtime.Callers", "runtime_test.TestCallersDoublePanic.func1.1",
113+
"runtime.gopanic", "runtime_test.TestCallersDoublePanic.func1", "runtime.gopanic", "runtime_test.TestCallersDoublePanic"}
114+
115+
defer func() {
116+
defer func() {
117+
pcs := make([]uintptr, 20)
118+
pcs = pcs[:runtime.Callers(0, pcs)]
119+
if recover() == nil {
120+
t.Fatal("did not panic")
121+
}
122+
testCallersEqual(t, pcs, want)
123+
}()
124+
if recover() == nil {
125+
t.Fatal("did not panic")
126+
}
127+
panic(2)
128+
}()
129+
panic(1)
130+
}
131+
132+
// Test that a defer after a successful recovery looks like it is called directly
133+
// from the function with the defers.
134+
func TestCallersAfterRecovery(t *testing.T) {
135+
want := []string{"runtime.Callers", "runtime_test.TestCallersAfterRecovery.func1", "runtime_test.TestCallersAfterRecovery"}
136+
137+
defer func() {
138+
pcs := make([]uintptr, 20)
139+
pcs = pcs[:runtime.Callers(0, pcs)]
140+
testCallersEqual(t, pcs, want)
141+
}()
142+
defer func() {
143+
if recover() == nil {
144+
t.Fatal("did not recover from panic")
145+
}
146+
}()
147+
panic(1)
148+
}
149+
150+
func TestCallersNilPointerPanic(t *testing.T) {
151+
// Make sure we don't have any extra frames on the stack (due to
152+
// open-coded defer processing)
153+
want := []string{"runtime.Callers", "runtime_test.TestCallersNilPointerPanic.func1",
154+
"runtime.gopanic", "runtime.panicmem", "runtime.sigpanic",
155+
"runtime_test.TestCallersNilPointerPanic"}
156+
157+
defer func() {
158+
if r := recover(); r == nil {
159+
t.Fatal("did not panic")
160+
}
161+
pcs := make([]uintptr, 20)
162+
pcs = pcs[:runtime.Callers(0, pcs)]
163+
testCallersEqual(t, pcs, want)
164+
}()
165+
var p *int
166+
if *p == 3 {
167+
t.Fatal("did not see nil pointer panic")
168+
}
169+
}
170+
171+
func TestCallersDivZeroPanic(t *testing.T) {
172+
// Make sure we don't have any extra frames on the stack (due to
173+
// open-coded defer processing)
174+
want := []string{"runtime.Callers", "runtime_test.TestCallersDivZeroPanic.func1",
175+
"runtime.gopanic", "runtime.panicdivide",
176+
"runtime_test.TestCallersDivZeroPanic"}
177+
178+
defer func() {
179+
if r := recover(); r == nil {
180+
t.Fatal("did not panic")
181+
}
182+
pcs := make([]uintptr, 20)
183+
pcs = pcs[:runtime.Callers(0, pcs)]
184+
testCallersEqual(t, pcs, want)
185+
}()
186+
var n int
187+
if 5/n == 1 {
188+
t.Fatal("did not see divide-by-sizer panic")
189+
}
190+
}

src/runtime/crash_test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,30 @@ panic: again
260260

261261
}
262262

263+
func TestRecursivePanic2(t *testing.T) {
264+
output := runTestProg(t, "testprog", "RecursivePanic2")
265+
want := `first panic
266+
second panic
267+
panic: third panic
268+
269+
`
270+
if !strings.HasPrefix(output, want) {
271+
t.Fatalf("output does not start with %q:\n%s", want, output)
272+
}
273+
274+
}
275+
276+
func TestRecursivePanic3(t *testing.T) {
277+
output := runTestProg(t, "testprog", "RecursivePanic3")
278+
want := `panic: first panic
279+
280+
`
281+
if !strings.HasPrefix(output, want) {
282+
t.Fatalf("output does not start with %q:\n%s", want, output)
283+
}
284+
285+
}
286+
263287
func TestGoexitCrash(t *testing.T) {
264288
output := runTestProg(t, "testprog", "GoexitExit")
265289
want := "no goroutines (main called runtime.Goexit) - deadlock!"
@@ -422,7 +446,7 @@ func TestNetpollDeadlock(t *testing.T) {
422446
func TestPanicTraceback(t *testing.T) {
423447
t.Parallel()
424448
output := runTestProg(t, "testprog", "PanicTraceback")
425-
want := "panic: hello"
449+
want := "panic: hello\n\tpanic: panic pt2\n\tpanic: panic pt1\n"
426450
if !strings.HasPrefix(output, want) {
427451
t.Fatalf("output does not start with %q:\n%s", want, output)
428452
}

0 commit comments

Comments
 (0)