github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/persistent/hashmap/hashmap_test.go (about) 1 package hashmap 2 3 import ( 4 "math/rand" 5 "reflect" 6 "strconv" 7 "testing" 8 "time" 9 10 "github.com/markusbkk/elvish/pkg/persistent/hash" 11 ) 12 13 const ( 14 NSequential = 0x1000 15 NCollision = 0x100 16 NRandom = 0x4000 17 NReplace = 0x200 18 19 SmallRandomPass = 0x100 20 NSmallRandom = 0x400 21 SmallRandomHighBound = 0x50 22 SmallRandomLowBound = 0x200 23 24 NArrayNode = 0x100 25 26 NIneffectiveDissoc = 0x200 27 28 N1 = nodeCap + 1 29 N2 = nodeCap*nodeCap + 1 30 N3 = nodeCap*nodeCap*nodeCap + 1 31 ) 32 33 type testKey uint64 34 type anotherTestKey uint32 35 36 func equalFunc(k1, k2 interface{}) bool { 37 switch k1 := k1.(type) { 38 case testKey: 39 t2, ok := k2.(testKey) 40 return ok && k1 == t2 41 case anotherTestKey: 42 return false 43 default: 44 return k1 == k2 45 } 46 } 47 48 func hashFunc(k interface{}) uint32 { 49 switch k := k.(type) { 50 case uint32: 51 return k 52 case string: 53 return hash.String(k) 54 case testKey: 55 // Return the lower 32 bits for testKey. This is intended so that hash 56 // collisions can be easily constructed. 57 return uint32(k & 0xffffffff) 58 case anotherTestKey: 59 return uint32(k) 60 default: 61 return 0 62 } 63 } 64 65 var empty = New(equalFunc, hashFunc) 66 67 type refEntry struct { 68 k testKey 69 v string 70 } 71 72 func hex(i uint64) string { 73 return "0x" + strconv.FormatUint(i, 16) 74 } 75 76 func init() { 77 rand.Seed(time.Now().UTC().UnixNano()) 78 } 79 80 var randomStrings []string 81 82 // getRandomStrings returns a slice of N3 random strings. It builds the slice 83 // once and caches it. If the slice is built for the first time, it stops the 84 // timer of the benchmark. 85 func getRandomStrings(b *testing.B) []string { 86 if randomStrings == nil { 87 b.StopTimer() 88 defer b.StartTimer() 89 randomStrings = make([]string, N3) 90 for i := 0; i < N3; i++ { 91 randomStrings[i] = makeRandomString() 92 } 93 } 94 return randomStrings 95 } 96 97 // makeRandomString builds a random string consisting of n bytes (randomized 98 // between 0 and 99) and each byte is randomized between 0 and 255. The string 99 // need not be valid UTF-8. 100 func makeRandomString() string { 101 bytes := make([]byte, rand.Intn(100)) 102 for i := range bytes { 103 bytes[i] = byte(rand.Intn(256)) 104 } 105 return string(bytes) 106 } 107 108 func TestHashMap(t *testing.T) { 109 var refEntries []refEntry 110 add := func(k testKey, v string) { 111 refEntries = append(refEntries, refEntry{k, v}) 112 } 113 114 for i := 0; i < NSequential; i++ { 115 add(testKey(i), hex(uint64(i))) 116 } 117 for i := 0; i < NCollision; i++ { 118 add(testKey(uint64(i+1)<<32), "collision "+hex(uint64(i))) 119 } 120 for i := 0; i < NRandom; i++ { 121 // Avoid rand.Uint64 for compatibility with pre 1.8 Go 122 k := uint64(rand.Int63())>>31 | uint64(rand.Int63())<<32 123 add(testKey(k), "random "+hex(k)) 124 } 125 for i := 0; i < NReplace; i++ { 126 k := uint64(rand.Int31n(NSequential)) 127 add(testKey(k), "replace "+hex(k)) 128 } 129 130 testHashMapWithRefEntries(t, refEntries) 131 } 132 133 func TestHashMapSmallRandom(t *testing.T) { 134 for p := 0; p < SmallRandomPass; p++ { 135 var refEntries []refEntry 136 add := func(k testKey, v string) { 137 refEntries = append(refEntries, refEntry{k, v}) 138 } 139 140 for i := 0; i < NSmallRandom; i++ { 141 k := uint64(uint64(rand.Int31n(SmallRandomHighBound))<<32 | 142 uint64(rand.Int31n(SmallRandomLowBound))) 143 add(testKey(k), "random "+hex(k)) 144 } 145 146 testHashMapWithRefEntries(t, refEntries) 147 } 148 } 149 150 var marshalJSONTests = []struct { 151 in Map 152 wantOut string 153 wantErr bool 154 }{ 155 {makeHashMap(uint32(1), "a", "2", "b"), `{"1":"a","2":"b"}`, false}, 156 // Invalid key type 157 {makeHashMap([]interface{}{}, "x"), "", true}, 158 } 159 160 func TestMarshalJSON(t *testing.T) { 161 for i, test := range marshalJSONTests { 162 out, err := test.in.MarshalJSON() 163 if string(out) != test.wantOut { 164 t.Errorf("m%d.MarshalJSON -> out %s, want %s", i, out, test.wantOut) 165 } 166 if (err != nil) != test.wantErr { 167 var wantErr string 168 if test.wantErr { 169 wantErr = "non-nil" 170 } else { 171 wantErr = "nil" 172 } 173 t.Errorf("m%d.MarshalJSON -> err %v, want %s", i, err, wantErr) 174 } 175 } 176 } 177 178 func makeHashMap(data ...interface{}) Map { 179 m := empty 180 for i := 0; i+1 < len(data); i += 2 { 181 k, v := data[i], data[i+1] 182 m = m.Assoc(k, v) 183 } 184 return m 185 } 186 187 // testHashMapWithRefEntries tests the operations of a Map. It uses the supplied 188 // list of entries to build the map, and then test all its operations. 189 func testHashMapWithRefEntries(t *testing.T, refEntries []refEntry) { 190 m := empty 191 // Len of Empty should be 0. 192 if m.Len() != 0 { 193 t.Errorf("m.Len = %d, want %d", m.Len(), 0) 194 } 195 196 // Assoc and Len, test by building a map simultaneously. 197 ref := make(map[testKey]string, len(refEntries)) 198 for _, e := range refEntries { 199 ref[e.k] = e.v 200 m = m.Assoc(e.k, e.v) 201 if m.Len() != len(ref) { 202 t.Errorf("m.Len = %d, want %d", m.Len(), len(ref)) 203 } 204 } 205 206 // Index. 207 testMapContent(t, m, ref) 208 got, in := m.Index(anotherTestKey(0)) 209 if in { 210 t.Errorf("m.Index <bad key> returns entry %v", got) 211 } 212 // Iterator. 213 testIterator(t, m, ref) 214 215 // Dissoc. 216 // Ineffective ones. 217 for i := 0; i < NIneffectiveDissoc; i++ { 218 k := anotherTestKey(uint32(rand.Int31())>>15 | uint32(rand.Int31())<<16) 219 m = m.Dissoc(k) 220 if m.Len() != len(ref) { 221 t.Errorf("m.Dissoc removes item when it shouldn't") 222 } 223 } 224 225 // Effective ones. 226 for x := 0; x < len(refEntries); x++ { 227 i := rand.Intn(len(refEntries)) 228 k := refEntries[i].k 229 delete(ref, k) 230 m = m.Dissoc(k) 231 if m.Len() != len(ref) { 232 t.Errorf("m.Len() = %d after removing, should be %v", m.Len(), len(ref)) 233 } 234 _, in := m.Index(k) 235 if in { 236 t.Errorf("m.Index(%v) still returns item after removal", k) 237 } 238 // Checking all elements is expensive. Only do this 1% of the time. 239 if rand.Float64() < 0.01 { 240 testMapContent(t, m, ref) 241 testIterator(t, m, ref) 242 } 243 } 244 } 245 246 func testMapContent(t *testing.T, m Map, ref map[testKey]string) { 247 for k, v := range ref { 248 got, in := m.Index(k) 249 if !in { 250 t.Errorf("m.Index 0x%x returns no entry", k) 251 } 252 if got != v { 253 t.Errorf("m.Index(0x%x) = %v, want %v", k, got, v) 254 } 255 } 256 } 257 258 func testIterator(t *testing.T, m Map, ref map[testKey]string) { 259 ref2 := map[interface{}]interface{}{} 260 for k, v := range ref { 261 ref2[k] = v 262 } 263 for it := m.Iterator(); it.HasElem(); it.Next() { 264 k, v := it.Elem() 265 if ref2[k] != v { 266 t.Errorf("iterator yields unexpected pair %v, %v", k, v) 267 } 268 delete(ref2, k) 269 } 270 if len(ref2) != 0 { 271 t.Errorf("iterating was not exhaustive") 272 } 273 } 274 275 func TestNilKey(t *testing.T) { 276 m := empty 277 278 testLen := func(l int) { 279 if m.Len() != l { 280 t.Errorf(".Len -> %d, want %d", m.Len(), l) 281 } 282 } 283 testIndex := func(wantVal interface{}, wantOk bool) { 284 val, ok := m.Index(nil) 285 if val != wantVal { 286 t.Errorf(".Index -> %v, want %v", val, wantVal) 287 } 288 if ok != wantOk { 289 t.Errorf(".Index -> ok %v, want %v", ok, wantOk) 290 } 291 } 292 293 testLen(0) 294 testIndex(nil, false) 295 296 m = m.Assoc(nil, "nil value") 297 testLen(1) 298 testIndex("nil value", true) 299 300 m = m.Assoc(nil, "nil value 2") 301 testLen(1) 302 testIndex("nil value 2", true) 303 304 m = m.Dissoc(nil) 305 testLen(0) 306 testIndex(nil, false) 307 } 308 309 func TestIterateMapWithNilKey(t *testing.T) { 310 m := empty.Assoc("k", "v").Assoc(nil, "nil value") 311 var collected []interface{} 312 for it := m.Iterator(); it.HasElem(); it.Next() { 313 k, v := it.Elem() 314 collected = append(collected, k, v) 315 } 316 wantCollected := []interface{}{nil, "nil value", "k", "v"} 317 if !reflect.DeepEqual(collected, wantCollected) { 318 t.Errorf("collected %v, want %v", collected, wantCollected) 319 } 320 } 321 322 func BenchmarkSequentialConjNative1(b *testing.B) { nativeSequentialAdd(b.N, N1) } 323 func BenchmarkSequentialConjNative2(b *testing.B) { nativeSequentialAdd(b.N, N2) } 324 func BenchmarkSequentialConjNative3(b *testing.B) { nativeSequentialAdd(b.N, N3) } 325 326 // nativeSequntialAdd starts with an empty native map and adds elements 0...n-1 327 // to the map, using the same value as the key, repeating for N times. 328 func nativeSequentialAdd(N int, n uint32) { 329 for r := 0; r < N; r++ { 330 m := make(map[uint32]uint32) 331 for i := uint32(0); i < n; i++ { 332 m[i] = i 333 } 334 } 335 } 336 337 func BenchmarkSequentialConjPersistent1(b *testing.B) { sequentialConj(b.N, N1) } 338 func BenchmarkSequentialConjPersistent2(b *testing.B) { sequentialConj(b.N, N2) } 339 func BenchmarkSequentialConjPersistent3(b *testing.B) { sequentialConj(b.N, N3) } 340 341 // sequentialConj starts with an empty hash map and adds elements 0...n-1 to the 342 // map, using the same value as the key, repeating for N times. 343 func sequentialConj(N int, n uint32) { 344 for r := 0; r < N; r++ { 345 m := empty 346 for i := uint32(0); i < n; i++ { 347 m = m.Assoc(i, i) 348 } 349 } 350 } 351 352 func BenchmarkRandomStringsConjNative1(b *testing.B) { nativeRandomStringsAdd(b, N1) } 353 func BenchmarkRandomStringsConjNative2(b *testing.B) { nativeRandomStringsAdd(b, N2) } 354 func BenchmarkRandomStringsConjNative3(b *testing.B) { nativeRandomStringsAdd(b, N3) } 355 356 // nativeSequntialAdd starts with an empty native map and adds n random strings 357 // to the map, using the same value as the key, repeating for b.N times. 358 func nativeRandomStringsAdd(b *testing.B, n int) { 359 ss := getRandomStrings(b) 360 for r := 0; r < b.N; r++ { 361 m := make(map[string]string) 362 for i := 0; i < n; i++ { 363 s := ss[i] 364 m[s] = s 365 } 366 } 367 } 368 369 func BenchmarkRandomStringsConjPersistent1(b *testing.B) { randomStringsConj(b, N1) } 370 func BenchmarkRandomStringsConjPersistent2(b *testing.B) { randomStringsConj(b, N2) } 371 func BenchmarkRandomStringsConjPersistent3(b *testing.B) { randomStringsConj(b, N3) } 372 373 func randomStringsConj(b *testing.B, n int) { 374 ss := getRandomStrings(b) 375 for r := 0; r < b.N; r++ { 376 m := empty 377 for i := 0; i < n; i++ { 378 s := ss[i] 379 m = m.Assoc(s, s) 380 } 381 } 382 }