github.com/maypok86/otter@v1.2.1/internal/hashtable/map_test.go (about) 1 // Copyright (c) 2023 Alexey Mayshev. All rights reserved. 2 // Copyright (c) 2021 Andrey Pechkurov 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 // 16 // Copyright notice. This code is a fork of tests for xsync.MapOf from this file with some changes: 17 // https://github.com/puzpuzpuz/xsync/blob/main/mapof_test.go 18 // 19 // Use of this source code is governed by a MIT license that can be found 20 // at https://github.com/puzpuzpuz/xsync/blob/main/LICENSE 21 22 package hashtable 23 24 import ( 25 "math/rand" 26 "strconv" 27 "sync" 28 "sync/atomic" 29 "testing" 30 "time" 31 "unsafe" 32 33 "github.com/maypok86/otter/internal/generated/node" 34 "github.com/maypok86/otter/internal/xruntime" 35 ) 36 37 func TestMap_PaddedBucketSize(t *testing.T) { 38 size := unsafe.Sizeof(paddedBucket{}) 39 if size != xruntime.CacheLineSize { 40 t.Fatalf("size of 64B (one cache line) is expected, got: %d", size) 41 } 42 } 43 44 func TestMap_EmptyStringKey(t *testing.T) { 45 nm := node.NewManager[string, string](node.Config{}) 46 m := New(nm) 47 m.Set(nm.Create("", "foobar", 0, 1)) 48 n, ok := m.Get("") 49 if !ok { 50 t.Fatal("value was expected") 51 } 52 if n.Value() != "foobar" { 53 t.Fatalf("value does not match: %v", n.Value()) 54 } 55 } 56 57 func TestMap_SetNilValue(t *testing.T) { 58 nm := node.NewManager[string, *struct{}](node.Config{}) 59 m := New(nm) 60 m.Set(nm.Create("foo", nil, 0, 1)) 61 n, ok := m.Get("foo") 62 if !ok { 63 t.Fatal("nil value was expected") 64 } 65 if n.Value() != nil { 66 t.Fatalf("value was not nil: %v", n.Value()) 67 } 68 } 69 70 func TestMap_Set(t *testing.T) { 71 const numberOfNodes = 128 72 nm := node.NewManager[string, int](node.Config{}) 73 m := New(nm) 74 for i := 0; i < numberOfNodes; i++ { 75 m.Set(nm.Create(strconv.Itoa(i), i, 0, 1)) 76 } 77 for i := 0; i < numberOfNodes; i++ { 78 n, ok := m.Get(strconv.Itoa(i)) 79 if !ok { 80 t.Fatalf("value not found for %d", i) 81 } 82 if n.Value() != i { 83 t.Fatalf("values do not match for %d: %v", i, n.Value()) 84 } 85 } 86 } 87 88 func TestMap_SetIfAbsent(t *testing.T) { 89 const numberOfNodes = 128 90 nm := node.NewManager[string, int](node.Config{}) 91 m := New(nm) 92 for i := 0; i < numberOfNodes; i++ { 93 res := m.SetIfAbsent(nm.Create(strconv.Itoa(i), i, 0, 1)) 94 if res != nil { 95 t.Fatalf("set was dropped. got: %+v", res) 96 } 97 } 98 for i := 0; i < numberOfNodes; i++ { 99 n := nm.Create(strconv.Itoa(i), i, 0, 1) 100 res := m.SetIfAbsent(n) 101 if res == nil { 102 t.Fatalf("set was not dropped. node that was set: %+v", res) 103 } 104 } 105 106 for i := 0; i < numberOfNodes; i++ { 107 n, ok := m.Get(strconv.Itoa(i)) 108 if !ok { 109 t.Fatalf("value not found for %d", i) 110 } 111 if n.Value() != i { 112 t.Fatalf("values do not match for %d: %v", i, n.Value()) 113 } 114 } 115 } 116 117 // this code may break if the maphash.Hasher[k] structure changes. 118 type hasher struct { 119 hash func(pointer unsafe.Pointer, seed uintptr) uintptr 120 seed uintptr 121 } 122 123 func TestMap_SetWithCollisions(t *testing.T) { 124 const numNodes = 1000 125 nm := node.NewManager[int, int](node.Config{}) 126 m := NewWithSize(nm, numNodes) 127 table := (*table[int])(atomic.LoadPointer(&m.table)) 128 hasher := (*hasher)((unsafe.Pointer)(&table.hasher)) 129 hasher.hash = func(ptr unsafe.Pointer, seed uintptr) uintptr { 130 // We intentionally use an awful hash function here to make sure 131 // that the map copes with key collisions. 132 return 42 133 } 134 for i := 0; i < numNodes; i++ { 135 m.Set(nm.Create(i, i, 0, 1)) 136 } 137 for i := 0; i < numNodes; i++ { 138 v, ok := m.Get(i) 139 if !ok { 140 t.Fatalf("value not found for %d", i) 141 } 142 if v.Value() != i { 143 t.Fatalf("values do not match for %d: %v", i, v) 144 } 145 } 146 } 147 148 func TestMap_SetThenDelete(t *testing.T) { 149 const numberOfNodes = 1000 150 nm := node.NewManager[string, int](node.Config{}) 151 m := New(nm) 152 for i := 0; i < numberOfNodes; i++ { 153 m.Set(nm.Create(strconv.Itoa(i), i, 0, 1)) 154 } 155 for i := 0; i < numberOfNodes; i++ { 156 m.Delete(strconv.Itoa(i)) 157 if _, ok := m.Get(strconv.Itoa(i)); ok { 158 t.Fatalf("value was not expected for %d", i) 159 } 160 } 161 } 162 163 func TestMap_Range(t *testing.T) { 164 const numNodes = 1000 165 nm := node.NewManager[string, int](node.Config{}) 166 m := New(nm) 167 for i := 0; i < numNodes; i++ { 168 m.Set(nm.Create(strconv.Itoa(i), i, 0, 1)) 169 } 170 iters := 0 171 met := make(map[string]int) 172 m.Range(func(n node.Node[string, int]) bool { 173 if n.Key() != strconv.Itoa(n.Value()) { 174 t.Fatalf("got unexpected key/value for iteration %d: %v/%v", iters, n.Key(), n.Value()) 175 return false 176 } 177 met[n.Key()] += 1 178 iters++ 179 return true 180 }) 181 if iters != numNodes { 182 t.Fatalf("got unexpected number of iterations: %d", iters) 183 } 184 for i := 0; i < numNodes; i++ { 185 if c := met[strconv.Itoa(i)]; c != 1 { 186 t.Fatalf("range did not iterate correctly over %d: %d", i, c) 187 } 188 } 189 } 190 191 func TestMap_RangeFalseReturned(t *testing.T) { 192 nm := node.NewManager[string, int](node.Config{}) 193 m := New(nm) 194 for i := 0; i < 100; i++ { 195 m.Set(nm.Create(strconv.Itoa(i), i, 0, 1)) 196 } 197 iters := 0 198 m.Range(func(n node.Node[string, int]) bool { 199 iters++ 200 return iters != 13 201 }) 202 if iters != 13 { 203 t.Fatalf("got unexpected number of iterations: %d", iters) 204 } 205 } 206 207 func TestMap_RangeNestedDelete(t *testing.T) { 208 const numNodes = 256 209 nm := node.NewManager[string, int](node.Config{}) 210 m := New(nm) 211 for i := 0; i < numNodes; i++ { 212 m.Set(nm.Create(strconv.Itoa(i), i, 0, 1)) 213 } 214 m.Range(func(n node.Node[string, int]) bool { 215 m.Delete(n.Key()) 216 return true 217 }) 218 for i := 0; i < numNodes; i++ { 219 if _, ok := m.Get(strconv.Itoa(i)); ok { 220 t.Fatalf("value found for %d", i) 221 } 222 } 223 } 224 225 func TestMap_Size(t *testing.T) { 226 const numberOfNodes = 1000 227 nm := node.NewManager[string, int](node.Config{}) 228 m := New(nm) 229 size := m.Size() 230 if size != 0 { 231 t.Fatalf("zero size expected: %d", size) 232 } 233 expectedSize := 0 234 for i := 0; i < numberOfNodes; i++ { 235 m.Set(nm.Create(strconv.Itoa(i), i, 0, 1)) 236 expectedSize++ 237 size := m.Size() 238 if size != expectedSize { 239 t.Fatalf("size of %d was expected, got: %d", expectedSize, size) 240 } 241 } 242 for i := 0; i < numberOfNodes; i++ { 243 m.Delete(strconv.Itoa(i)) 244 expectedSize-- 245 size := m.Size() 246 if size != expectedSize { 247 t.Fatalf("size of %d was expected, got: %d", expectedSize, size) 248 } 249 } 250 } 251 252 func TestMap_Clear(t *testing.T) { 253 const numberOfNodes = 1000 254 nm := node.NewManager[string, int](node.Config{}) 255 m := New(nm) 256 for i := 0; i < numberOfNodes; i++ { 257 m.Set(nm.Create(strconv.Itoa(i), i, 0, 1)) 258 } 259 size := m.Size() 260 if size != numberOfNodes { 261 t.Fatalf("size of %d was expected, got: %d", numberOfNodes, size) 262 } 263 m.Clear() 264 size = m.Size() 265 if size != 0 { 266 t.Fatalf("zero size was expected, got: %d", size) 267 } 268 } 269 270 func parallelSeqSetter(t *testing.T, m *Map[string, int], storers, iterations, nodes int, wg *sync.WaitGroup) { 271 t.Helper() 272 273 for i := 0; i < iterations; i++ { 274 for j := 0; j < nodes; j++ { 275 if storers == 0 || j%storers == 0 { 276 m.Set(m.nodeManager.Create(strconv.Itoa(j), j, 0, 1)) 277 n, ok := m.Get(strconv.Itoa(j)) 278 if !ok { 279 t.Errorf("value was not found for %d", j) 280 break 281 } 282 if n.Value() != j { 283 t.Errorf("value was not expected for %d: %d", j, n.Value()) 284 break 285 } 286 } 287 } 288 } 289 wg.Done() 290 } 291 292 func TestMap_ParallelSets(t *testing.T) { 293 const storers = 4 294 const iterations = 10_000 295 const nodes = 100 296 nm := node.NewManager[string, int](node.Config{}) 297 m := New(nm) 298 299 wg := &sync.WaitGroup{} 300 wg.Add(storers) 301 for i := 0; i < storers; i++ { 302 go parallelSeqSetter(t, m, i, iterations, nodes, wg) 303 } 304 wg.Wait() 305 306 for i := 0; i < nodes; i++ { 307 n, ok := m.Get(strconv.Itoa(i)) 308 if !ok { 309 t.Fatalf("value not found for %d", i) 310 } 311 if n.Value() != i { 312 t.Fatalf("values do not match for %d: %v", i, n.Value()) 313 } 314 } 315 } 316 317 func parallelRandSetter(t *testing.T, m *Map[string, int], iteratinos, nodes int, wg *sync.WaitGroup) { 318 t.Helper() 319 320 r := rand.New(rand.NewSource(time.Now().UnixNano())) 321 for i := 0; i < iteratinos; i++ { 322 j := r.Intn(nodes) 323 m.Set(m.nodeManager.Create(strconv.Itoa(j), j, 0, 1)) 324 } 325 wg.Done() 326 } 327 328 func parallelRandDeleter(t *testing.T, m *Map[string, int], iterations, nodes int, wg *sync.WaitGroup) { 329 t.Helper() 330 331 r := rand.New(rand.NewSource(time.Now().UnixNano())) 332 for i := 0; i < iterations; i++ { 333 j := r.Intn(nodes) 334 if v := m.Delete(strconv.Itoa(j)); v != nil && v.Value() != j { 335 t.Errorf("value was not expected for %d: %d", j, v.Value()) 336 } 337 } 338 wg.Done() 339 } 340 341 func parallelGetter(t *testing.T, m *Map[string, int], iterations, nodes int, wg *sync.WaitGroup) { 342 t.Helper() 343 344 for i := 0; i < iterations; i++ { 345 for j := 0; j < nodes; j++ { 346 if n, ok := m.Get(strconv.Itoa(j)); ok && n.Value() != j { 347 t.Errorf("value was not expected for %d: %d", j, n.Value()) 348 } 349 } 350 } 351 wg.Done() 352 } 353 354 func TestMap_ParallelGet(t *testing.T) { 355 const iterations = 100_000 356 const nodes = 100 357 nm := node.NewManager[string, int](node.Config{}) 358 m := New(nm) 359 360 wg := &sync.WaitGroup{} 361 wg.Add(3) 362 go parallelRandSetter(t, m, iterations, nodes, wg) 363 go parallelRandDeleter(t, m, iterations, nodes, wg) 364 go parallelGetter(t, m, iterations, nodes, wg) 365 366 wg.Wait() 367 } 368 369 func TestMap_ParallelSetsAndDeletes(t *testing.T) { 370 const workers = 2 371 const iterations = 100_000 372 const nodes = 1000 373 nm := node.NewManager[string, int](node.Config{}) 374 m := New(nm) 375 wg := &sync.WaitGroup{} 376 wg.Add(2 * workers) 377 for i := 0; i < workers; i++ { 378 go parallelRandSetter(t, m, iterations, nodes, wg) 379 go parallelRandDeleter(t, m, iterations, nodes, wg) 380 } 381 382 wg.Wait() 383 } 384 385 func parallelTypedRangeSetter(t *testing.T, m *Map[int, int], numNodes int, stopFlag *int64, cdone chan bool) { 386 t.Helper() 387 388 for { 389 for i := 0; i < numNodes; i++ { 390 m.Set(m.nodeManager.Create(i, i, 0, 1)) 391 } 392 if atomic.LoadInt64(stopFlag) != 0 { 393 break 394 } 395 } 396 cdone <- true 397 } 398 399 func parallelTypedRangeDeleter(t *testing.T, m *Map[int, int], numNodes int, stopFlag *int64, cdone chan bool) { 400 t.Helper() 401 402 for { 403 for i := 0; i < numNodes; i++ { 404 m.Delete(i) 405 } 406 if atomic.LoadInt64(stopFlag) != 0 { 407 break 408 } 409 } 410 cdone <- true 411 } 412 413 func TestMap_ParallelRange(t *testing.T) { 414 const numNodes = 10_000 415 nm := node.NewManager[int, int](node.Config{}) 416 m := New(nm) 417 for i := 0; i < numNodes; i++ { 418 m.Set(nm.Create(i, i, 0, 1)) 419 } 420 // Start goroutines that would be storing and deleting items in parallel. 421 cdone := make(chan bool) 422 stopFlag := int64(0) 423 go parallelTypedRangeSetter(t, m, numNodes, &stopFlag, cdone) 424 go parallelTypedRangeDeleter(t, m, numNodes, &stopFlag, cdone) 425 // Iterate the map and verify that no duplicate keys were met. 426 met := make(map[int]int) 427 m.Range(func(n node.Node[int, int]) bool { 428 if n.Key() != n.Value() { 429 t.Fatalf("got unexpected value for key %d: %d", n.Key(), n.Value()) 430 return false 431 } 432 met[n.Key()] += 1 433 return true 434 }) 435 if len(met) == 0 { 436 t.Fatal("no nodes were met when iterating") 437 } 438 for k, c := range met { 439 if c != 1 { 440 t.Fatalf("met key %d multiple times: %d", k, c) 441 } 442 } 443 // Make sure that both goroutines finish. 444 atomic.StoreInt64(&stopFlag, 1) 445 <-cdone 446 <-cdone 447 }