Source file
src/sync/oncefunc_test.go
1
2
3
4
5 package sync_test
6
7 import (
8 "bytes"
9 "math"
10 "runtime"
11 "runtime/debug"
12 "sync"
13 "sync/atomic"
14 "testing"
15 _ "unsafe"
16 )
17
18
19
20 func TestOnceFunc(t *testing.T) {
21 calls := 0
22 f := sync.OnceFunc(func() { calls++ })
23 allocs := testing.AllocsPerRun(10, f)
24 if calls != 1 {
25 t.Errorf("want calls==1, got %d", calls)
26 }
27 if allocs != 0 {
28 t.Errorf("want 0 allocations per call, got %v", allocs)
29 }
30 }
31
32 func TestOnceValue(t *testing.T) {
33 calls := 0
34 f := sync.OnceValue(func() int {
35 calls++
36 return calls
37 })
38 allocs := testing.AllocsPerRun(10, func() { f() })
39 value := f()
40 if calls != 1 {
41 t.Errorf("want calls==1, got %d", calls)
42 }
43 if value != 1 {
44 t.Errorf("want value==1, got %d", value)
45 }
46 if allocs != 0 {
47 t.Errorf("want 0 allocations per call, got %v", allocs)
48 }
49 }
50
51 func TestOnceValues(t *testing.T) {
52 calls := 0
53 f := sync.OnceValues(func() (int, int) {
54 calls++
55 return calls, calls + 1
56 })
57 allocs := testing.AllocsPerRun(10, func() { f() })
58 v1, v2 := f()
59 if calls != 1 {
60 t.Errorf("want calls==1, got %d", calls)
61 }
62 if v1 != 1 || v2 != 2 {
63 t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2)
64 }
65 if allocs != 0 {
66 t.Errorf("want 0 allocations per call, got %v", allocs)
67 }
68 }
69
70 func testOncePanicX(t *testing.T, calls *int, f func()) {
71 testOncePanicWith(t, calls, f, func(label string, p any) {
72 if p != "x" {
73 t.Fatalf("%s: want panic %v, got %v", label, "x", p)
74 }
75 })
76 }
77
78 func testOncePanicWith(t *testing.T, calls *int, f func(), check func(label string, p any)) {
79
80
81 for _, label := range []string{"first time", "second time"} {
82 var p any
83 panicked := true
84 func() {
85 defer func() {
86 p = recover()
87 }()
88 f()
89 panicked = false
90 }()
91 if !panicked {
92 t.Fatalf("%s: f did not panic", label)
93 }
94 check(label, p)
95 }
96 if *calls != 1 {
97 t.Errorf("want calls==1, got %d", *calls)
98 }
99 }
100
101 func TestOnceFuncPanic(t *testing.T) {
102 calls := 0
103 f := sync.OnceFunc(func() {
104 calls++
105 panic("x")
106 })
107 testOncePanicX(t, &calls, f)
108 }
109
110 func TestOnceValuePanic(t *testing.T) {
111 calls := 0
112 f := sync.OnceValue(func() int {
113 calls++
114 panic("x")
115 })
116 testOncePanicX(t, &calls, func() { f() })
117 }
118
119 func TestOnceValuesPanic(t *testing.T) {
120 calls := 0
121 f := sync.OnceValues(func() (int, int) {
122 calls++
123 panic("x")
124 })
125 testOncePanicX(t, &calls, func() { f() })
126 }
127
128 func TestOnceFuncPanicNil(t *testing.T) {
129 calls := 0
130 f := sync.OnceFunc(func() {
131 calls++
132 panic(nil)
133 })
134 testOncePanicWith(t, &calls, f, func(label string, p any) {
135 switch p.(type) {
136 case nil, *runtime.PanicNilError:
137 return
138 }
139 t.Fatalf("%s: want nil panic, got %v", label, p)
140 })
141 }
142
143 func TestOnceFuncGoexit(t *testing.T) {
144
145
146 calls := 0
147 f := sync.OnceFunc(func() {
148 calls++
149 runtime.Goexit()
150 })
151 var wg sync.WaitGroup
152 for i := 0; i < 2; i++ {
153 wg.Add(1)
154 go func() {
155 defer wg.Done()
156 defer func() { recover() }()
157 f()
158 }()
159 wg.Wait()
160 }
161 if calls != 1 {
162 t.Errorf("want calls==1, got %d", calls)
163 }
164 }
165
166 func TestOnceFuncPanicTraceback(t *testing.T) {
167
168
169 f := sync.OnceFunc(onceFuncPanic)
170
171 defer func() {
172 if p := recover(); p != "x" {
173 t.Fatalf("want panic %v, got %v", "x", p)
174 }
175 stack := debug.Stack()
176 want := "sync_test.onceFuncPanic"
177 if !bytes.Contains(stack, []byte(want)) {
178 t.Fatalf("want stack containing %v, got:\n%s", want, string(stack))
179 }
180 }()
181 f()
182 }
183
184 func onceFuncPanic() {
185 panic("x")
186 }
187
188 func TestOnceXGC(t *testing.T) {
189 fns := map[string]func([]byte) func(){
190 "OnceFunc": func(buf []byte) func() {
191 return sync.OnceFunc(func() { buf[0] = 1 })
192 },
193 "OnceValue": func(buf []byte) func() {
194 f := sync.OnceValue(func() any { buf[0] = 1; return nil })
195 return func() { f() }
196 },
197 "OnceValues": func(buf []byte) func() {
198 f := sync.OnceValues(func() (any, any) { buf[0] = 1; return nil, nil })
199 return func() { f() }
200 },
201 }
202 for n, fn := range fns {
203 t.Run(n, func(t *testing.T) {
204 buf := make([]byte, 1024)
205 var gc atomic.Bool
206 runtime.SetFinalizer(&buf[0], func(_ *byte) {
207 gc.Store(true)
208 })
209 f := fn(buf)
210 gcwaitfin()
211 if gc.Load() != false {
212 t.Fatal("wrapped function garbage collected too early")
213 }
214 f()
215 gcwaitfin()
216 if gc.Load() != true {
217
218
219 t.Fatal("wrapped function should be garbage collected, but still live")
220 }
221 f()
222 })
223 }
224 }
225
226
227 func gcwaitfin() {
228 runtime.GC()
229 runtime_blockUntilEmptyFinalizerQueue(math.MaxInt64)
230 }
231
232
233 func runtime_blockUntilEmptyFinalizerQueue(int64) bool
234
235 var (
236 onceFunc = sync.OnceFunc(func() {})
237
238 onceFuncOnce sync.Once
239 )
240
241 func doOnceFunc() {
242 onceFuncOnce.Do(func() {})
243 }
244
245 func BenchmarkOnceFunc(b *testing.B) {
246 b.Run("v=Once", func(b *testing.B) {
247 b.ReportAllocs()
248 for i := 0; i < b.N; i++ {
249
250 doOnceFunc()
251 }
252 })
253 b.Run("v=Global", func(b *testing.B) {
254 b.ReportAllocs()
255 for i := 0; i < b.N; i++ {
256
257
258
259 onceFunc()
260 }
261 })
262 b.Run("v=Local", func(b *testing.B) {
263 b.ReportAllocs()
264
265
266
267 f := sync.OnceFunc(func() {})
268 for i := 0; i < b.N; i++ {
269 f()
270 }
271 })
272 }
273
274 var (
275 onceValue = sync.OnceValue(func() int { return 42 })
276
277 onceValueOnce sync.Once
278 onceValueValue int
279 )
280
281 func doOnceValue() int {
282 onceValueOnce.Do(func() {
283 onceValueValue = 42
284 })
285 return onceValueValue
286 }
287
288 func BenchmarkOnceValue(b *testing.B) {
289
290 b.Run("v=Once", func(b *testing.B) {
291 b.ReportAllocs()
292 for i := 0; i < b.N; i++ {
293 if want, got := 42, doOnceValue(); want != got {
294 b.Fatalf("want %d, got %d", want, got)
295 }
296 }
297 })
298 b.Run("v=Global", func(b *testing.B) {
299 b.ReportAllocs()
300 for i := 0; i < b.N; i++ {
301 if want, got := 42, onceValue(); want != got {
302 b.Fatalf("want %d, got %d", want, got)
303 }
304 }
305 })
306 b.Run("v=Local", func(b *testing.B) {
307 b.ReportAllocs()
308 onceValue := sync.OnceValue(func() int { return 42 })
309 for i := 0; i < b.N; i++ {
310 if want, got := 42, onceValue(); want != got {
311 b.Fatalf("want %d, got %d", want, got)
312 }
313 }
314 })
315 }
316
View as plain text