github.com/bazelbuild/remote-apis-sdks@v0.0.0-20240425170053-8a36686a6350/go/pkg/cache/singleflightcache_test.go (about) 1 package cache 2 3 import ( 4 "errors" 5 "sync" 6 "sync/atomic" 7 "testing" 8 ) 9 10 const ( 11 key1 = "key1" 12 key2 = "key2" 13 val1 = "val1" 14 val2 = "val2" 15 key3 = "key3" 16 val3 = "val3" 17 ) 18 19 func TestSimpleValueStore(t *testing.T) { 20 s := &SingleFlight{} 21 val, err := s.LoadOrStore(key1, func() (interface{}, error) { return val1, nil }) 22 if err != nil { 23 t.Errorf("LoadOrStore(%v) failed: %v", key1, err) 24 } 25 if val != val1 { 26 t.Errorf("LoadOrStore(%v) loaded wrong value: got %v, want %v", key1, val, val1) 27 } 28 val, err, loaded := s.Load(key1) 29 if !loaded { 30 t.Errorf("expected to load value") 31 } 32 if err != nil { 33 t.Errorf("Load(%v) failed: %v", key1, err) 34 } 35 if val != val1 { 36 t.Errorf("Load(%v) loaded wrong value: got %v, want %v", key1, val, val1) 37 } 38 39 val, err = s.LoadOrStore(key2, func() (interface{}, error) { return val2, nil }) 40 if err != nil { 41 t.Errorf("LoadOrStore(%v) failed: %v", key2, err) 42 } 43 if val != val2 { 44 t.Errorf("LoadOrStore(%v) loaded wrong value: got %v, want %v", key2, val, val2) 45 } 46 val, err, loaded = s.Load(key2) 47 if !loaded { 48 t.Errorf("expected to load value") 49 } 50 if err != nil { 51 t.Errorf("Load(%v) failed: %v", key2, err) 52 } 53 if val != val2 { 54 t.Errorf("Load(%v) loaded wrong value: got %v, want %v", key2, val, val1) 55 } 56 } 57 58 func TestSingleFlightStore(t *testing.T) { 59 s := &SingleFlight{} 60 var ops uint64 61 loadFn := func() (interface{}, error) { 62 atomic.AddUint64(&ops, 1) 63 return val1, nil 64 } 65 wg := &sync.WaitGroup{} 66 load := func() { 67 val, err := s.LoadOrStore(key1, loadFn) 68 if err != nil { 69 t.Errorf("LoadOrStore(%v) failed: %v", key1, err) 70 } 71 if val != val1 { 72 t.Errorf("LoadOrStore(%v) loaded wrong value: got %v, want %v", key1, val, val1) 73 } 74 wg.Done() 75 } 76 wg.Add(50) 77 for i := 0; i < 50; i++ { 78 go load() 79 } 80 wg.Wait() 81 82 if ops != 1 { 83 t.Errorf("Wrong number of loads executed: got %v, want 1", ops) 84 } 85 } 86 87 func TestValFnFailure(t *testing.T) { 88 s := &SingleFlight{} 89 fnErr := errors.New("error") 90 val, err := s.LoadOrStore(key1, func() (interface{}, error) { return nil, fnErr }) 91 if err == nil { 92 t.Errorf("LoadOrStore(%v) failed: val is %v, err is nil", key1, val) 93 } 94 95 val, err = s.LoadOrStore(key1, func() (interface{}, error) { return val1, nil }) 96 if err != fnErr { 97 t.Errorf("LoadOrStore(%v) didn't fail: (%v, %v)", key1, val, err) 98 } 99 } 100 101 func TestDelete(t *testing.T) { 102 s := &SingleFlight{} 103 val, err := s.LoadOrStore(key1, func() (interface{}, error) { return val1, nil }) 104 if err != nil { 105 t.Fatalf("LoadOrStore(%v) failed: %v", key1, err) 106 } 107 if val != val1 { 108 t.Fatalf("LoadOrStore(%v) loaded wrong value: got %v, want %v", key1, val, val1) 109 } 110 111 s.Delete(key1) 112 113 val, err = s.LoadOrStore(key1, func() (interface{}, error) { return val2, nil }) 114 if err != nil { 115 t.Errorf("LoadOrStore(%v) failed: %v", key1, err) 116 } 117 if val != val2 { 118 t.Errorf("LoadOrStore(%v) loaded wrong value: got %v, want %v", key1, val, val2) 119 } 120 } 121 122 // This test launches several concurrent goroutines to load/store and delete keys from the map. The 123 // purpose of the test is to expose whether a race condition would result in an inconsistent state 124 // of the internal maps of the cache, which would result in an error reported by LoadOrStore. 125 func TestLoadDelete(t *testing.T) { 126 s := &SingleFlight{} 127 wg := &sync.WaitGroup{} 128 load := func() { 129 val, err := s.LoadOrStore(key1, func() (interface{}, error) { return val1, nil }) 130 if err != nil { 131 t.Errorf("LoadOrStore(%v) failed: %v", key1, err) 132 } 133 if val != val1 { 134 t.Errorf("LoadOrStore(%v) loaded wrong value: got %v, want %v", key1, val, val1) 135 } 136 val, err = s.LoadOrStore(key2, func() (interface{}, error) { return val2, nil }) 137 if err != nil { 138 t.Errorf("LoadOrStore(%v) failed: %v", key2, err) 139 } 140 if val != val2 { 141 t.Errorf("LoadOrStore(%v) loaded wrong value: got %v, want %v", key2, val, val2) 142 } 143 wg.Done() 144 } 145 del := func() { 146 s.Delete(key1) 147 s.Delete(key2) 148 wg.Done() 149 } 150 wg.Add(100) 151 for i := 0; i < 50; i++ { 152 go load() 153 go del() 154 } 155 wg.Wait() 156 } 157 158 func TestStore(t *testing.T) { 159 s := &SingleFlight{} 160 wg := &sync.WaitGroup{} 161 var mu sync.Mutex 162 load := func() { 163 mu.Lock() 164 s.Store(key3, val3) 165 // LoadOrStore should load the already loaded value "val3" and shouldn't 166 // overwrite "val1" to "key3". 167 val, err := s.LoadOrStore(key3, func() (interface{}, error) { return val1, nil }) 168 if err != nil { 169 t.Errorf("LoadOrStore(%v) failed: %v", key3, err) 170 } 171 mu.Unlock() 172 if val != val3 { 173 t.Errorf("LoadOrStore(%v) = %v, want %v", key3, val, val3) 174 } 175 wg.Done() 176 } 177 del := func() { 178 mu.Lock() 179 s.Delete(key3) 180 mu.Unlock() 181 wg.Done() 182 } 183 wg.Add(100) 184 for i := 0; i < 50; i++ { 185 go load() 186 go del() 187 } 188 wg.Wait() 189 } 190 191 func TestStoreOverwrite(t *testing.T) { 192 s := &SingleFlight{} 193 194 val, err := s.LoadOrStore(key3, func() (interface{}, error) { return val1, nil }) 195 if err != nil { 196 t.Errorf("LoadOrStore(%v) failed: %v", key3, err) 197 } 198 if val != val1 { 199 t.Errorf("LoadOrStore(%v) = %v, want %v", key3, val, val1) 200 } 201 202 s.Store(key3, val3) 203 204 // LoadOrStore should load the already loaded value "val3" and shouldn't 205 // overwrite "val1" to "key3". 206 val, err = s.LoadOrStore(key3, func() (interface{}, error) { return val1, nil }) 207 if err != nil { 208 t.Errorf("LoadOrStore(%v) failed: %v", key3, err) 209 } 210 if val != val3 { 211 t.Errorf("LoadOrStore(%v) = %v, want %v", key3, val, val3) 212 } 213 } 214 215 // This test purposefully tests if there is a race condition in concurrent 216 // go-routines writing / deleting to the same key value - it is not expected 217 // that the end result of the key in the cache is a specific value which is why 218 // the output of LoadOrStore is not tested for correctness. You can run the race 219 // detector using: "bazelisk test --features race //go/pkg/cache/..." 220 func TestRacedValueStore(t *testing.T) { 221 s := &SingleFlight{} 222 wg := &sync.WaitGroup{} 223 load := func() { 224 if _, err := s.LoadOrStore(key1, func() (interface{}, error) { return val1, nil }); err != nil { 225 t.Errorf("LoadOrStore(%v) failed: %v", key1, err) 226 } 227 wg.Done() 228 } 229 load2 := func() { 230 if _, err := s.LoadOrStore(key1, func() (interface{}, error) { return val2, nil }); err != nil { 231 t.Errorf("LoadOrStore(%v) failed: %v", key1, err) 232 } 233 wg.Done() 234 } 235 del := func() { 236 s.Delete(key1) 237 wg.Done() 238 } 239 wg.Add(150) 240 for i := 0; i < 50; i++ { 241 go load() 242 go load2() 243 go del() 244 } 245 wg.Wait() 246 }