github.com/lingyao2333/mo-zero@v1.4.1/core/syncx/singleflight_test.go (about) 1 package syncx 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "sync/atomic" 8 "testing" 9 "time" 10 ) 11 12 func TestExclusiveCallDo(t *testing.T) { 13 g := NewSingleFlight() 14 v, err := g.Do("key", func() (interface{}, error) { 15 return "bar", nil 16 }) 17 if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want { 18 t.Errorf("Do = %v; want %v", got, want) 19 } 20 if err != nil { 21 t.Errorf("Do error = %v", err) 22 } 23 } 24 25 func TestExclusiveCallDoErr(t *testing.T) { 26 g := NewSingleFlight() 27 someErr := errors.New("some error") 28 v, err := g.Do("key", func() (interface{}, error) { 29 return nil, someErr 30 }) 31 if err != someErr { 32 t.Errorf("Do error = %v; want someErr", err) 33 } 34 if v != nil { 35 t.Errorf("unexpected non-nil value %#v", v) 36 } 37 } 38 39 func TestExclusiveCallDoDupSuppress(t *testing.T) { 40 g := NewSingleFlight() 41 c := make(chan string) 42 var calls int32 43 fn := func() (interface{}, error) { 44 atomic.AddInt32(&calls, 1) 45 return <-c, nil 46 } 47 48 const n = 10 49 var wg sync.WaitGroup 50 for i := 0; i < n; i++ { 51 wg.Add(1) 52 go func() { 53 v, err := g.Do("key", fn) 54 if err != nil { 55 t.Errorf("Do error: %v", err) 56 } 57 if v.(string) != "bar" { 58 t.Errorf("got %q; want %q", v, "bar") 59 } 60 wg.Done() 61 }() 62 } 63 time.Sleep(100 * time.Millisecond) // let goroutines above block 64 c <- "bar" 65 wg.Wait() 66 if got := atomic.LoadInt32(&calls); got != 1 { 67 t.Errorf("number of calls = %d; want 1", got) 68 } 69 } 70 71 func TestExclusiveCallDoDiffDupSuppress(t *testing.T) { 72 g := NewSingleFlight() 73 broadcast := make(chan struct{}) 74 var calls int32 75 tests := []string{"e", "a", "e", "a", "b", "c", "b", "a", "c", "d", "b", "c", "d"} 76 77 var wg sync.WaitGroup 78 for _, key := range tests { 79 wg.Add(1) 80 go func(k string) { 81 <-broadcast // get all goroutines ready 82 _, err := g.Do(k, func() (interface{}, error) { 83 atomic.AddInt32(&calls, 1) 84 time.Sleep(10 * time.Millisecond) 85 return nil, nil 86 }) 87 if err != nil { 88 t.Errorf("Do error: %v", err) 89 } 90 wg.Done() 91 }(key) 92 } 93 94 time.Sleep(100 * time.Millisecond) // let goroutines above block 95 close(broadcast) 96 wg.Wait() 97 98 if got := atomic.LoadInt32(&calls); got != 5 { 99 // five letters 100 t.Errorf("number of calls = %d; want 5", got) 101 } 102 } 103 104 func TestExclusiveCallDoExDupSuppress(t *testing.T) { 105 g := NewSingleFlight() 106 c := make(chan string) 107 var calls int32 108 fn := func() (interface{}, error) { 109 atomic.AddInt32(&calls, 1) 110 return <-c, nil 111 } 112 113 const n = 10 114 var wg sync.WaitGroup 115 var freshes int32 116 for i := 0; i < n; i++ { 117 wg.Add(1) 118 go func() { 119 v, fresh, err := g.DoEx("key", fn) 120 if err != nil { 121 t.Errorf("Do error: %v", err) 122 } 123 if fresh { 124 atomic.AddInt32(&freshes, 1) 125 } 126 if v.(string) != "bar" { 127 t.Errorf("got %q; want %q", v, "bar") 128 } 129 wg.Done() 130 }() 131 } 132 time.Sleep(100 * time.Millisecond) // let goroutines above block 133 c <- "bar" 134 wg.Wait() 135 if got := atomic.LoadInt32(&calls); got != 1 { 136 t.Errorf("number of calls = %d; want 1", got) 137 } 138 if got := atomic.LoadInt32(&freshes); got != 1 { 139 t.Errorf("freshes = %d; want 1", got) 140 } 141 }