github.com/aristanetworks/gomap@v0.0.0-20240103001659-f6b0e31fb1a7/map_test.go (about) 1 // Modifications copyright (c) Arista Networks, Inc. 2022 2 // Underlying 3 // Copyright 2014 The Go Authors. All rights reserved. 4 // Use of this source code is governed by a BSD-style 5 // license that can be found in the LICENSE file. 6 7 package gomap 8 9 import ( 10 "encoding/binary" 11 "fmt" 12 "hash/maphash" 13 "strings" 14 "sync" 15 "testing" 16 17 "golang.org/x/exp/slices" 18 ) 19 20 func (m *Map[K, E]) debugString() string { 21 var buf strings.Builder 22 fmt.Fprintf(&buf, "count: %d, buckets: %d, overflows: %d growing: %t\n", 23 m.count, len(m.buckets), m.noverflow, m.growing()) 24 25 for i, b := range m.buckets { 26 fmt.Fprintf(&buf, "bucket: %d\n", i) 27 b := &b 28 for { 29 for i := uintptr(0); i < bucketCnt; i++ { 30 seen := map[uint8]struct{}{} 31 switch b.tophash[i] { 32 case emptyRest: 33 buf.WriteString(" emptyRest\n") 34 case emptyOne: 35 buf.WriteString(" emptyOne\n") 36 case evacuatedX: 37 buf.WriteString(" evacuatedX?\n") 38 case evacuatedY: 39 buf.WriteString(" evacuatedY?\n") 40 case evacuatedEmpty: 41 buf.WriteString(" evacuatedEmpty?\n") 42 default: 43 var s string 44 if _, ok := seen[b.tophash[i]]; ok { 45 s = " duplicate" 46 } else { 47 seen[b.tophash[i]] = struct{}{} 48 } 49 fmt.Fprintf(&buf, " 0x%02x"+s+"\n", b.tophash[i]) 50 } 51 } 52 if b.overflow == nil { 53 break 54 } 55 buf.WriteString("overflow->\n") 56 b = b.overflow 57 } 58 } 59 60 return buf.String() 61 } 62 63 func intHash(seed maphash.Seed, a int) uint64 { 64 var buf [8]byte 65 binary.LittleEndian.PutUint64(buf[:], uint64(a)) 66 return maphash.Bytes(seed, buf[:]) 67 } 68 69 func TestSetGetDelete(t *testing.T) { 70 const count = 1000 71 t.Run("nohint", func(t *testing.T) { 72 m := New[int, int](func(a int, b int) bool { return a == b }, intHash) 73 t.Logf("Buckets: %d Unused-overflow: %d", len(m.buckets), cap(m.buckets)-len(m.buckets)) 74 for i := 0; i < count; i++ { 75 m.Set(i, i) 76 if v, ok := m.Get(i); !ok { 77 t.Errorf("got not ok for %d", i) 78 } else if v != i { 79 t.Errorf("unexpected value for %d: %d", i, v) 80 } 81 if m.Len() != i+1 { 82 t.Errorf("expected len: %d got: %d", i+1, m.Len()) 83 } 84 } 85 t.Logf("Buckets: %d Unused-overflow: %d", len(m.buckets), cap(m.buckets)-len(m.buckets)) 86 t.Log("Overflow:", m.noverflow) 87 for i := 0; i < count; i++ { 88 if v, ok := m.Get(i); !ok { 89 t.Errorf("got not ok for %d", i) 90 } else if v != i { 91 t.Errorf("unexpected value for %d: %d", i, v) 92 } 93 if m.Len() != count { 94 t.Errorf("expected len: %d got: %d", count, m.Len()) 95 } 96 97 } 98 for i := 0; i < count; i++ { 99 if v, ok := m.Get(i); !ok { 100 t.Errorf("got not ok for %d", i) 101 } else if v != i { 102 t.Errorf("unexpected value for %d: %d", i, v) 103 } 104 105 m.Delete(i) 106 107 if v, ok := m.Get(i); ok { 108 t.Errorf("found %d: %d, but it should have been deleted", i, v) 109 } 110 if m.Len() != count-i-1 { 111 t.Errorf("expected len: %d got: %d", count, m.Len()) 112 } 113 } 114 }) 115 t.Run("hint", func(t *testing.T) { 116 m := NewHint[int, int](count, func(a int, b int) bool { return a == b }, intHash) 117 t.Logf("Buckets: %d Unused-overflow: %d", len(m.buckets), cap(m.buckets)-len(m.buckets)) 118 for i := 0; i < count; i++ { 119 m.Set(i, i) 120 if v, ok := m.Get(i); !ok { 121 t.Errorf("got not ok for %d", i) 122 } else if v != i { 123 t.Errorf("unexpected value for %d: %d", i, v) 124 } 125 if m.Len() != i+1 { 126 t.Errorf("expected len: %d got: %d", i+1, m.Len()) 127 } 128 } 129 t.Logf("Buckets: %d Unused-overflow: %d", len(m.buckets), cap(m.buckets)-len(m.buckets)) 130 t.Log("Overflow:", m.noverflow) 131 for i := 0; i < count; i++ { 132 if v, ok := m.Get(i); !ok { 133 t.Errorf("got not ok for %d", i) 134 } else if v != i { 135 t.Errorf("unexpected value for %d: %d", i, v) 136 } 137 if m.Len() != count { 138 t.Errorf("expected len: %d got: %d", count, m.Len()) 139 } 140 141 } 142 for i := 0; i < count; i++ { 143 if v, ok := m.Get(i); !ok { 144 t.Errorf("got not ok for %d", i) 145 } else if v != i { 146 t.Errorf("unexpected value for %d: %d", i, v) 147 } 148 149 m.Delete(i) 150 151 if v, ok := m.Get(i); ok { 152 t.Errorf("found %d: %d, but it should have been deleted", i, v) 153 } 154 if m.Len() != count-i-1 { 155 t.Errorf("expected len: %d got: %d", count, m.Len()) 156 } 157 } 158 }) 159 } 160 161 func BenchmarkGrow(b *testing.B) { 162 b.Run("hint", func(b *testing.B) { 163 b.ReportAllocs() 164 m := NewHint[int, int](b.N, func(a int, b int) bool { return a == b }, intHash) 165 for i := 0; i < b.N; i++ { 166 m.Set(i, i) 167 } 168 }) 169 b.Run("nohint", func(b *testing.B) { 170 b.ReportAllocs() 171 m := New[int, int](func(a int, b int) bool { return a == b }, intHash) 172 for i := 0; i < b.N; i++ { 173 m.Set(i, i) 174 } 175 }) 176 177 b.Run("std:hint", func(b *testing.B) { 178 b.ReportAllocs() 179 m := make(map[int]int, b.N) 180 for i := 0; i < b.N; i++ { 181 m[i] = i 182 } 183 }) 184 b.Run("std:nohint", func(b *testing.B) { 185 b.ReportAllocs() 186 m := map[int]int{} 187 for i := 0; i < b.N; i++ { 188 m[i] = i 189 } 190 }) 191 } 192 193 func TestGetIterateRace(t *testing.T) { 194 m := NewHint[int, int](100, func(a int, b int) bool { return a == b }, intHash) 195 for i := 0; i < 100; i++ { 196 m.Set(i, i) 197 } 198 var wg sync.WaitGroup 199 wg.Add(1) 200 go func() { 201 for i := 0; i < 100; i++ { 202 v, ok := m.Get(i) 203 if !ok || v != i { 204 t.Errorf("expected: %d got: %d, %t", i, v, ok) 205 } 206 } 207 wg.Done() 208 }() 209 wg.Add(1) 210 go func() { 211 for i := 0; i < 100; i++ { 212 v, ok := m.Get(i) 213 if !ok || v != i { 214 t.Errorf("expected: %d got: %d, %t", i, v, ok) 215 } 216 } 217 wg.Done() 218 }() 219 220 wg.Add(1) 221 go func() { 222 for i := 0; i < 100; i++ { 223 iter := m.Iter() 224 if !iter.Next() { 225 t.Error("unexpected end of iter") 226 } 227 } 228 wg.Done() 229 }() 230 wg.Add(1) 231 go func() { 232 for i := 0; i < 100; i++ { 233 iter := m.Iter() 234 if !iter.Next() { 235 t.Error("unexpected end of iter") 236 } 237 } 238 wg.Done() 239 }() 240 wg.Wait() 241 } 242 243 // badIntHash is a bad hash function that gives simple deterministic 244 // hash to give control over which bucket a key lands in. 245 func badIntHash(seed maphash.Seed, a uint64) uint64 { 246 return uint64(a) 247 } 248 249 func TestIter(t *testing.T) { 250 m := New[uint64, uint64]( 251 func(a, b uint64) bool { return a == b }, 252 badIntHash, 253 ) 254 expected := make(map[uint64]uint64, 9) 255 for i := uint64(0); i < 9; i++ { 256 expected[i] = i 257 m.Set(i, i) 258 } 259 for i := m.Iter(); i.Next(); { 260 e, ok := expected[i.Key()] 261 if !ok { 262 t.Errorf("unexpected value in m: [%d: %d]", i.Key(), i.Elem()) 263 continue 264 } 265 if e != i.Elem() { 266 t.Errorf("wrong value for key %d. Expected: %d Got: %d", i.Key(), e, i.Elem()) 267 continue 268 } 269 delete(expected, i.Key()) 270 } 271 if len(expected) > 0 { 272 t.Errorf("Values not found in m: %v", expected) 273 } 274 } 275 276 func TestClear(t *testing.T) { 277 m := New( 278 func(a, b string) bool { return a == b }, 279 maphash.String, 280 KeyElem[string, string]{"a", "a"}, 281 KeyElem[string, string]{"b", "b"}, 282 KeyElem[string, string]{"c", "c"}, 283 KeyElem[string, string]{"d", "d"}, 284 ) 285 if m.Len() != 4 { 286 t.Fatalf("Unexpected size after New (%d): %s", m.Len(), m.debugString()) 287 } 288 m.Clear() 289 if m.Len() != 0 { 290 t.Errorf("expected empty map: %s", m.debugString()) 291 } 292 for i := m.Iter(); i.Next(); { 293 t.Errorf("unexpected entry in map: [%s: %s]", i.Key(), i.Elem()) 294 } 295 } 296 297 func TestIterResize(t *testing.T) { 298 m := New[uint64, uint64]( 299 func(a, b uint64) bool { return a == b }, 300 badIntHash, 301 ) 302 303 // insert numbers that initially hash to the same bucket, but will 304 // be split into different buckets on resize. 305 initial := map[uint64]uint64{0: 0, 1: 1, 2: 2, 3: 3} 306 for k, e := range initial { 307 m.Set(k, e) 308 } 309 // create the iter 310 i := m.Iter() 311 // Add some additional data to cause a resize 312 additional := map[uint64]uint64{100: 100, 101: 101, 102: 102, 103: 103, 104: 104} 313 for k, e := range additional { 314 m.Set(k, e) 315 } 316 317 // Remove 1 value that in each of the initial and split buckets 318 m.Delete(0) 319 delete(initial, 0) 320 m.Delete(1) 321 delete(initial, 1) 322 for i.Next() { 323 if i.Key() != i.Elem() { 324 t.Errorf("expected key == elem, but got: %d != %d", i.Key(), i.Elem()) 325 t.Error(m.debugString()) 326 } 327 if _, ok := initial[i.Key()]; ok { 328 delete(initial, i.Key()) 329 continue 330 } 331 if _, ok := additional[i.Key()]; ok { 332 t.Logf("Saw key from additional: %d", i.Key()) 333 continue 334 } 335 t.Errorf("Unexpected value from iter: %d", i.Key()) 336 } 337 for k := range initial { 338 t.Errorf("iter missing key: %d", k) 339 } 340 } 341 342 func TestIterDuringGrow(t *testing.T) { 343 m := New[uint64, uint64]( 344 func(a, b uint64) bool { return a == b }, 345 badIntHash, 346 ) 347 348 // Insert exactly 27 numbers so we end up in the middle of a grow. 349 expected := make(map[uint64]uint64, 27) 350 for i := uint64(0); i < 27; i++ { 351 expected[i] = i 352 m.Set(i, i) 353 } 354 // create the iter while Map is growing 355 i := m.Iter() 356 357 for i.Next() { 358 t.Logf("Key: %d", i.Key()) 359 if i.Key() != i.Elem() { 360 t.Errorf("expected key == elem, but got: %d != %d", i.Key(), i.Elem()) 361 t.Error(m.debugString()) 362 } 363 364 if _, ok := expected[i.Key()]; ok { 365 delete(expected, i.Key()) 366 continue 367 } 368 t.Errorf("Unexpected value from iter: %d", i.Key()) 369 } 370 for k := range expected { 371 t.Errorf("iter missing key: %d", k) 372 } 373 } 374 375 func TestUpdate(t *testing.T) { 376 m := New[int, []int]( 377 func(a, b int) bool { return a == b }, 378 intHash) 379 for key := 0; key < 10; key++ { 380 var expected []int 381 for i := 0; i < 3; i++ { 382 m.Update(key, func(cur []int) []int { return append(cur, 1) }) 383 expected = append(expected, 1) 384 got, ok := m.Get(key) 385 if !ok { 386 t.Errorf("m missing key: %v", key) 387 } else if !slices.Equal(got, expected) { 388 t.Errorf("Got: %v Expected: %v", got, expected) 389 } 390 } 391 } 392 } 393 394 func BenchmarkIter(b *testing.B) { 395 m := New[string, int]( 396 func(a, b string) bool { return a == b }, 397 maphash.String, 398 KeyElem[string, int]{"one", 1}, 399 KeyElem[string, int]{"two", 2}, 400 KeyElem[string, int]{"three", 3}, 401 ) 402 b.ReportAllocs() 403 b.ResetTimer() 404 for i := 0; i < b.N; i++ { 405 for it := m.Iter(); it.Next(); { 406 } 407 } 408 } 409 410 func BenchmarkRand(b *testing.B) { 411 for i := 0; i < b.N; i++ { 412 rand64() 413 } 414 }