github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/sync/oncefunc_test.go (about) 1 // Copyright 2022 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 sync_test 6 7 import ( 8 "bytes" 9 "runtime" 10 "runtime/debug" 11 "sync" 12 "testing" 13 ) 14 15 // We assume that the Once.Do tests have already covered parallelism. 16 17 func TestOnceFunc(t *testing.T) { 18 calls := 0 19 f := sync.OnceFunc(func() { calls++ }) 20 allocs := testing.AllocsPerRun(10, f) 21 if calls != 1 { 22 t.Errorf("want calls==1, got %d", calls) 23 } 24 if allocs != 0 { 25 t.Errorf("want 0 allocations per call, got %v", allocs) 26 } 27 } 28 29 func TestOnceValue(t *testing.T) { 30 calls := 0 31 f := sync.OnceValue(func() int { 32 calls++ 33 return calls 34 }) 35 allocs := testing.AllocsPerRun(10, func() { f() }) 36 value := f() 37 if calls != 1 { 38 t.Errorf("want calls==1, got %d", calls) 39 } 40 if value != 1 { 41 t.Errorf("want value==1, got %d", value) 42 } 43 if allocs != 0 { 44 t.Errorf("want 0 allocations per call, got %v", allocs) 45 } 46 } 47 48 func TestOnceValues(t *testing.T) { 49 calls := 0 50 f := sync.OnceValues(func() (int, int) { 51 calls++ 52 return calls, calls + 1 53 }) 54 allocs := testing.AllocsPerRun(10, func() { f() }) 55 v1, v2 := f() 56 if calls != 1 { 57 t.Errorf("want calls==1, got %d", calls) 58 } 59 if v1 != 1 || v2 != 2 { 60 t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2) 61 } 62 if allocs != 0 { 63 t.Errorf("want 0 allocations per call, got %v", allocs) 64 } 65 } 66 67 func testOncePanicX(t *testing.T, calls *int, f func()) { 68 testOncePanicWith(t, calls, f, func(label string, p any) { 69 if p != "x" { 70 t.Fatalf("%s: want panic %v, got %v", label, "x", p) 71 } 72 }) 73 } 74 75 func testOncePanicWith(t *testing.T, calls *int, f func(), check func(label string, p any)) { 76 // Check that the each call to f panics with the same value, but the 77 // underlying function is only called once. 78 for _, label := range []string{"first time", "second time"} { 79 var p any 80 panicked := true 81 func() { 82 defer func() { 83 p = recover() 84 }() 85 f() 86 panicked = false 87 }() 88 if !panicked { 89 t.Fatalf("%s: f did not panic", label) 90 } 91 check(label, p) 92 } 93 if *calls != 1 { 94 t.Errorf("want calls==1, got %d", *calls) 95 } 96 } 97 98 func TestOnceFuncPanic(t *testing.T) { 99 calls := 0 100 f := sync.OnceFunc(func() { 101 calls++ 102 panic("x") 103 }) 104 testOncePanicX(t, &calls, f) 105 } 106 107 func TestOnceValuePanic(t *testing.T) { 108 calls := 0 109 f := sync.OnceValue(func() int { 110 calls++ 111 panic("x") 112 }) 113 testOncePanicX(t, &calls, func() { f() }) 114 } 115 116 func TestOnceValuesPanic(t *testing.T) { 117 calls := 0 118 f := sync.OnceValues(func() (int, int) { 119 calls++ 120 panic("x") 121 }) 122 testOncePanicX(t, &calls, func() { f() }) 123 } 124 125 func TestOnceFuncPanicNil(t *testing.T) { 126 calls := 0 127 f := sync.OnceFunc(func() { 128 calls++ 129 panic(nil) 130 }) 131 testOncePanicWith(t, &calls, f, func(label string, p any) { 132 switch p.(type) { 133 case nil, *runtime.PanicNilError: 134 return 135 } 136 t.Fatalf("%s: want nil panic, got %v", label, p) 137 }) 138 } 139 140 func TestOnceFuncGoexit(t *testing.T) { 141 // If f calls Goexit, the results are unspecified. But check that f doesn't 142 // get called twice. 143 calls := 0 144 f := sync.OnceFunc(func() { 145 calls++ 146 runtime.Goexit() 147 }) 148 var wg sync.WaitGroup 149 for i := 0; i < 2; i++ { 150 wg.Add(1) 151 go func() { 152 defer wg.Done() 153 defer func() { recover() }() 154 f() 155 }() 156 wg.Wait() 157 } 158 if calls != 1 { 159 t.Errorf("want calls==1, got %d", calls) 160 } 161 } 162 163 func TestOnceFuncPanicTraceback(t *testing.T) { 164 // Test that on the first invocation of a OnceFunc, the stack trace goes all 165 // the way to the origin of the panic. 166 f := sync.OnceFunc(onceFuncPanic) 167 168 defer func() { 169 if p := recover(); p != "x" { 170 t.Fatalf("want panic %v, got %v", "x", p) 171 } 172 stack := debug.Stack() 173 want := "sync_test.onceFuncPanic" 174 if !bytes.Contains(stack, []byte(want)) { 175 t.Fatalf("want stack containing %v, got:\n%s", want, string(stack)) 176 } 177 }() 178 f() 179 } 180 181 func onceFuncPanic() { 182 panic("x") 183 } 184 185 var ( 186 onceFunc = sync.OnceFunc(func() {}) 187 188 onceFuncOnce sync.Once 189 ) 190 191 func doOnceFunc() { 192 onceFuncOnce.Do(func() {}) 193 } 194 195 func BenchmarkOnceFunc(b *testing.B) { 196 b.Run("v=Once", func(b *testing.B) { 197 b.ReportAllocs() 198 for i := 0; i < b.N; i++ { 199 // The baseline is direct use of sync.Once. 200 doOnceFunc() 201 } 202 }) 203 b.Run("v=Global", func(b *testing.B) { 204 b.ReportAllocs() 205 for i := 0; i < b.N; i++ { 206 // As of 3/2023, the compiler doesn't recognize that onceFunc is 207 // never mutated and is a closure that could be inlined. 208 // Too bad, because this is how OnceFunc will usually be used. 209 onceFunc() 210 } 211 }) 212 b.Run("v=Local", func(b *testing.B) { 213 b.ReportAllocs() 214 // As of 3/2023, the compiler *does* recognize this local binding as an 215 // inlinable closure. This is the best case for OnceFunc, but probably 216 // not typical usage. 217 f := sync.OnceFunc(func() {}) 218 for i := 0; i < b.N; i++ { 219 f() 220 } 221 }) 222 } 223 224 var ( 225 onceValue = sync.OnceValue(func() int { return 42 }) 226 227 onceValueOnce sync.Once 228 onceValueValue int 229 ) 230 231 func doOnceValue() int { 232 onceValueOnce.Do(func() { 233 onceValueValue = 42 234 }) 235 return onceValueValue 236 } 237 238 func BenchmarkOnceValue(b *testing.B) { 239 // See BenchmarkOnceFunc 240 b.Run("v=Once", func(b *testing.B) { 241 b.ReportAllocs() 242 for i := 0; i < b.N; i++ { 243 if want, got := 42, doOnceValue(); want != got { 244 b.Fatalf("want %d, got %d", want, got) 245 } 246 } 247 }) 248 b.Run("v=Global", func(b *testing.B) { 249 b.ReportAllocs() 250 for i := 0; i < b.N; i++ { 251 if want, got := 42, onceValue(); want != got { 252 b.Fatalf("want %d, got %d", want, got) 253 } 254 } 255 }) 256 b.Run("v=Local", func(b *testing.B) { 257 b.ReportAllocs() 258 onceValue := sync.OnceValue(func() int { return 42 }) 259 for i := 0; i < b.N; i++ { 260 if want, got := 42, onceValue(); want != got { 261 b.Fatalf("want %d, got %d", want, got) 262 } 263 } 264 }) 265 }