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  }