github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/store/cache/store_test.go (about) 1 package cache_test 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/require" 8 9 dbm "github.com/gnolang/gno/tm2/pkg/db" 10 "github.com/gnolang/gno/tm2/pkg/db/memdb" 11 "github.com/gnolang/gno/tm2/pkg/random" 12 "github.com/gnolang/gno/tm2/pkg/store/cache" 13 "github.com/gnolang/gno/tm2/pkg/store/dbadapter" 14 "github.com/gnolang/gno/tm2/pkg/store/types" 15 ) 16 17 func newCacheStore() types.Store { 18 mem := dbadapter.Store{memdb.NewMemDB()} 19 return cache.New(mem) 20 } 21 22 func keyFmt(i int) []byte { return bz(fmt.Sprintf("key%0.8d", i)) } 23 func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) } 24 25 func TestCacheStore(t *testing.T) { 26 t.Parallel() 27 28 mem := dbadapter.Store{memdb.NewMemDB()} 29 st := cache.New(mem) 30 31 require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") 32 33 // put something in mem and in cache 34 mem.Set(keyFmt(1), valFmt(1)) 35 st.Set(keyFmt(1), valFmt(1)) 36 require.Equal(t, valFmt(1), st.Get(keyFmt(1))) 37 38 // update it in cache, shouldn't change mem 39 st.Set(keyFmt(1), valFmt(2)) 40 require.Equal(t, valFmt(2), st.Get(keyFmt(1))) 41 require.Equal(t, valFmt(1), mem.Get(keyFmt(1))) 42 43 // write it. should change mem 44 st.Write() 45 require.Equal(t, valFmt(2), mem.Get(keyFmt(1))) 46 require.Equal(t, valFmt(2), st.Get(keyFmt(1))) 47 48 // more writes and checks 49 st.Write() 50 st.Write() 51 require.Equal(t, valFmt(2), mem.Get(keyFmt(1))) 52 require.Equal(t, valFmt(2), st.Get(keyFmt(1))) 53 54 // make a new one, check it 55 st = cache.New(mem) 56 require.Equal(t, valFmt(2), st.Get(keyFmt(1))) 57 58 // make a new one and delete - should not be removed from mem 59 st = cache.New(mem) 60 st.Delete(keyFmt(1)) 61 require.Empty(t, st.Get(keyFmt(1))) 62 require.Equal(t, mem.Get(keyFmt(1)), valFmt(2)) 63 64 // Write. should now be removed from both 65 st.Write() 66 require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") 67 require.Empty(t, mem.Get(keyFmt(1)), "Expected `key1` to be empty") 68 } 69 70 func TestCacheStoreNoNilSet(t *testing.T) { 71 t.Parallel() 72 73 mem := dbadapter.Store{memdb.NewMemDB()} 74 st := cache.New(mem) 75 require.Panics(t, func() { st.Set([]byte("key"), nil) }, "setting a nil value should panic") 76 } 77 78 func TestCacheStoreNested(t *testing.T) { 79 t.Parallel() 80 81 mem := dbadapter.Store{memdb.NewMemDB()} 82 st := cache.New(mem) 83 84 // set. check its there on st and not on mem. 85 st.Set(keyFmt(1), valFmt(1)) 86 require.Empty(t, mem.Get(keyFmt(1))) 87 require.Equal(t, valFmt(1), st.Get(keyFmt(1))) 88 89 // make a new from st and check 90 st2 := cache.New(st) 91 require.Equal(t, valFmt(1), st2.Get(keyFmt(1))) 92 93 // update the value on st2, check it only effects st2 94 st2.Set(keyFmt(1), valFmt(3)) 95 require.Equal(t, []byte(nil), mem.Get(keyFmt(1))) 96 require.Equal(t, valFmt(1), st.Get(keyFmt(1))) 97 require.Equal(t, valFmt(3), st2.Get(keyFmt(1))) 98 99 // st2 writes to its parent, st. doesn't effect mem 100 st2.Write() 101 require.Equal(t, []byte(nil), mem.Get(keyFmt(1))) 102 require.Equal(t, valFmt(3), st.Get(keyFmt(1))) 103 104 // updates mem 105 st.Write() 106 require.Equal(t, valFmt(3), mem.Get(keyFmt(1))) 107 } 108 109 func TestCacheKVIteratorBounds(t *testing.T) { 110 t.Parallel() 111 112 st := newCacheStore() 113 114 // set some items 115 nItems := 5 116 for i := 0; i < nItems; i++ { 117 st.Set(keyFmt(i), valFmt(i)) 118 } 119 120 // iterate over all of them 121 itr := st.Iterator(nil, nil) 122 i := 0 123 for ; itr.Valid(); itr.Next() { 124 k, v := itr.Key(), itr.Value() 125 require.Equal(t, keyFmt(i), k) 126 require.Equal(t, valFmt(i), v) 127 i++ 128 } 129 require.Equal(t, nItems, i) 130 131 // iterate over none 132 itr = st.Iterator(bz("money"), nil) 133 i = 0 134 for ; itr.Valid(); itr.Next() { 135 i++ 136 } 137 require.Equal(t, 0, i) 138 139 // iterate over lower 140 itr = st.Iterator(keyFmt(0), keyFmt(3)) 141 i = 0 142 for ; itr.Valid(); itr.Next() { 143 k, v := itr.Key(), itr.Value() 144 require.Equal(t, keyFmt(i), k) 145 require.Equal(t, valFmt(i), v) 146 i++ 147 } 148 require.Equal(t, 3, i) 149 150 // iterate over upper 151 itr = st.Iterator(keyFmt(2), keyFmt(4)) 152 i = 2 153 for ; itr.Valid(); itr.Next() { 154 k, v := itr.Key(), itr.Value() 155 require.Equal(t, keyFmt(i), k) 156 require.Equal(t, valFmt(i), v) 157 i++ 158 } 159 require.Equal(t, 4, i) 160 } 161 162 func TestCacheKVReverseIteratorBounds(t *testing.T) { 163 t.Parallel() 164 165 st := newCacheStore() 166 167 // set some items 168 nItems := 5 169 for i := 0; i < nItems; i++ { 170 st.Set(keyFmt(i), valFmt(i)) 171 } 172 173 // iterate over all of them in reverse 174 i := nItems - 1 175 for itr := st.ReverseIterator(nil, nil); itr.Valid(); itr.Next() { 176 require.Equal(t, keyFmt(i), itr.Key()) 177 require.Equal(t, valFmt(i), itr.Value()) 178 i-- 179 } 180 require.Equal(t, -1, i) 181 182 // iterate over none 183 i = 0 184 for itr := st.ReverseIterator(bz("money"), nil); itr.Valid(); itr.Next() { 185 i++ 186 } 187 require.Equal(t, 0, i) 188 189 // iterate over lower 190 i = 2 191 for itr := st.ReverseIterator(keyFmt(0), keyFmt(3)); itr.Valid(); itr.Next() { 192 require.Equal(t, keyFmt(i), itr.Key()) 193 require.Equal(t, valFmt(i), itr.Value()) 194 i-- 195 } 196 require.Equal(t, -1, i) 197 198 // iterate over upper 199 i = 3 200 for itr := st.ReverseIterator(keyFmt(2), keyFmt(4)); itr.Valid(); itr.Next() { 201 require.Equal(t, keyFmt(i), itr.Key()) 202 require.Equal(t, valFmt(i), itr.Value()) 203 i-- 204 } 205 require.Equal(t, 1, i) 206 } 207 208 func TestCacheKVMergeIteratorBasics(t *testing.T) { 209 t.Parallel() 210 211 st := newCacheStore() 212 213 // set and delete an item in the cache, iterator should be empty 214 k, v := keyFmt(0), valFmt(0) 215 st.Set(k, v) 216 st.Delete(k) 217 assertIterateDomain(t, st, 0) 218 219 // now set it and assert its there 220 st.Set(k, v) 221 assertIterateDomain(t, st, 1) 222 223 // write it and assert its there 224 st.Write() 225 assertIterateDomain(t, st, 1) 226 227 // remove it in cache and assert its not 228 st.Delete(k) 229 assertIterateDomain(t, st, 0) 230 231 // write the delete and assert its not there 232 st.Write() 233 assertIterateDomain(t, st, 0) 234 235 // add two keys and assert they're there 236 k1, v1 := keyFmt(1), valFmt(1) 237 st.Set(k, v) 238 st.Set(k1, v1) 239 assertIterateDomain(t, st, 2) 240 241 // write it and assert they're there 242 st.Write() 243 assertIterateDomain(t, st, 2) 244 245 // remove one in cache and assert its not 246 st.Delete(k1) 247 assertIterateDomain(t, st, 1) 248 249 // write the delete and assert its not there 250 st.Write() 251 assertIterateDomain(t, st, 1) 252 253 // delete the other key in cache and asserts its empty 254 st.Delete(k) 255 assertIterateDomain(t, st, 0) 256 } 257 258 func TestCacheKVMergeIteratorDeleteLast(t *testing.T) { 259 t.Parallel() 260 261 st := newCacheStore() 262 263 // set some items and write them 264 nItems := 5 265 for i := 0; i < nItems; i++ { 266 st.Set(keyFmt(i), valFmt(i)) 267 } 268 st.Write() 269 270 // set some more items and leave dirty 271 for i := nItems; i < nItems*2; i++ { 272 st.Set(keyFmt(i), valFmt(i)) 273 } 274 275 // iterate over all of them 276 assertIterateDomain(t, st, nItems*2) 277 278 // delete them all 279 for i := 0; i < nItems*2; i++ { 280 last := nItems*2 - 1 - i 281 st.Delete(keyFmt(last)) 282 assertIterateDomain(t, st, last) 283 } 284 } 285 286 func TestCacheKVMergeIteratorDeletes(t *testing.T) { 287 t.Parallel() 288 289 st := newCacheStore() 290 truth := memdb.NewMemDB() 291 292 // set some items and write them 293 nItems := 10 294 for i := 0; i < nItems; i++ { 295 doOp(st, truth, opSet, i) 296 } 297 st.Write() 298 299 // delete every other item, starting from 0 300 for i := 0; i < nItems; i += 2 { 301 doOp(st, truth, opDel, i) 302 assertIterateDomainCompare(t, st, truth) 303 } 304 305 // reset 306 st = newCacheStore() 307 truth = memdb.NewMemDB() 308 309 // set some items and write them 310 for i := 0; i < nItems; i++ { 311 doOp(st, truth, opSet, i) 312 } 313 st.Write() 314 315 // delete every other item, starting from 1 316 for i := 1; i < nItems; i += 2 { 317 doOp(st, truth, opDel, i) 318 assertIterateDomainCompare(t, st, truth) 319 } 320 } 321 322 func TestCacheKVMergeIteratorChunks(t *testing.T) { 323 t.Parallel() 324 325 st := newCacheStore() 326 327 // Use the truth to check values on the merge iterator 328 truth := memdb.NewMemDB() 329 330 // sets to the parent 331 setRange(st, truth, 0, 20) 332 setRange(st, truth, 40, 60) 333 st.Write() 334 335 // sets to the cache 336 setRange(st, truth, 20, 40) 337 setRange(st, truth, 60, 80) 338 assertIterateDomainCheck(t, st, truth, []keyRange{{0, 80}}) 339 340 // remove some parents and some cache 341 deleteRange(st, truth, 15, 25) 342 assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 80}}) 343 344 // remove some parents and some cache 345 deleteRange(st, truth, 35, 45) 346 assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 35}, {45, 80}}) 347 348 // write, add more to the cache, and delete some cache 349 st.Write() 350 setRange(st, truth, 38, 42) 351 deleteRange(st, truth, 40, 43) 352 assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 35}, {38, 40}, {45, 80}}) 353 } 354 355 func TestCacheKVMergeIteratorRandom(t *testing.T) { 356 t.Parallel() 357 358 st := newCacheStore() 359 truth := memdb.NewMemDB() 360 361 start, end := 25, 975 362 max := 1000 363 setRange(st, truth, start, end) 364 365 // do an op, test the iterator 366 for i := 0; i < 2000; i++ { 367 doRandomOp(st, truth, max) 368 assertIterateDomainCompare(t, st, truth) 369 } 370 } 371 372 // ------------------------------------------------------------------------------------------- 373 // do some random ops 374 375 const ( 376 opSet = 0 377 opSetRange = 1 378 opDel = 2 379 opDelRange = 3 380 opWrite = 4 381 382 totalOps = 5 // number of possible operations 383 ) 384 385 func randInt(n int) int { 386 return random.RandInt() % n 387 } 388 389 // useful for replaying a error case if we find one 390 func doOp(st types.Store, truth dbm.DB, op int, args ...int) { 391 switch op { 392 case opSet: 393 k := args[0] 394 st.Set(keyFmt(k), valFmt(k)) 395 truth.Set(keyFmt(k), valFmt(k)) 396 case opSetRange: 397 start := args[0] 398 end := args[1] 399 setRange(st, truth, start, end) 400 case opDel: 401 k := args[0] 402 st.Delete(keyFmt(k)) 403 truth.Delete(keyFmt(k)) 404 case opDelRange: 405 start := args[0] 406 end := args[1] 407 deleteRange(st, truth, start, end) 408 case opWrite: 409 st.Write() 410 } 411 } 412 413 func doRandomOp(st types.Store, truth dbm.DB, maxKey int) { 414 r := randInt(totalOps) 415 switch r { 416 case opSet: 417 k := randInt(maxKey) 418 st.Set(keyFmt(k), valFmt(k)) 419 truth.Set(keyFmt(k), valFmt(k)) 420 case opSetRange: 421 start := randInt(maxKey - 2) 422 end := randInt(maxKey-start) + start 423 setRange(st, truth, start, end) 424 case opDel: 425 k := randInt(maxKey) 426 st.Delete(keyFmt(k)) 427 truth.Delete(keyFmt(k)) 428 case opDelRange: 429 start := randInt(maxKey - 2) 430 end := randInt(maxKey-start) + start 431 deleteRange(st, truth, start, end) 432 case opWrite: 433 st.Write() 434 } 435 } 436 437 // ------------------------------------------------------------------------------------------- 438 439 // iterate over whole domain 440 func assertIterateDomain(t *testing.T, st types.Store, expectedN int) { 441 t.Helper() 442 443 itr := st.Iterator(nil, nil) 444 i := 0 445 for ; itr.Valid(); itr.Next() { 446 k, v := itr.Key(), itr.Value() 447 require.Equal(t, keyFmt(i), k) 448 require.Equal(t, valFmt(i), v) 449 i++ 450 } 451 require.Equal(t, expectedN, i) 452 } 453 454 func assertIterateDomainCheck(t *testing.T, st types.Store, mem dbm.DB, r []keyRange) { 455 t.Helper() 456 457 // iterate over each and check they match the other 458 itr := st.Iterator(nil, nil) 459 itr2 := mem.Iterator(nil, nil) // ground truth 460 461 krc := newKeyRangeCounter(r) 462 i := 0 463 464 for ; krc.valid(); krc.next() { 465 require.True(t, itr.Valid()) 466 require.True(t, itr2.Valid()) 467 468 // check the key/val matches the ground truth 469 k, v := itr.Key(), itr.Value() 470 k2, v2 := itr2.Key(), itr2.Value() 471 require.Equal(t, k, k2) 472 require.Equal(t, v, v2) 473 474 // check they match the counter 475 require.Equal(t, k, keyFmt(krc.key())) 476 477 itr.Next() 478 itr2.Next() 479 i++ 480 } 481 482 require.False(t, itr.Valid()) 483 require.False(t, itr2.Valid()) 484 } 485 486 func assertIterateDomainCompare(t *testing.T, st types.Store, mem dbm.DB) { 487 t.Helper() 488 489 // iterate over each and check they match the other 490 itr := st.Iterator(nil, nil) 491 itr2 := mem.Iterator(nil, nil) // ground truth 492 checkIterators(t, itr, itr2) 493 checkIterators(t, itr2, itr) 494 } 495 496 func checkIterators(t *testing.T, itr, itr2 types.Iterator) { 497 t.Helper() 498 499 for ; itr.Valid(); itr.Next() { 500 require.True(t, itr2.Valid()) 501 k, v := itr.Key(), itr.Value() 502 k2, v2 := itr2.Key(), itr2.Value() 503 require.Equal(t, k, k2) 504 require.Equal(t, v, v2) 505 itr2.Next() 506 } 507 require.False(t, itr.Valid()) 508 require.False(t, itr2.Valid()) 509 } 510 511 // -------------------------------------------------------- 512 513 func setRange(st types.Store, mem dbm.DB, start, end int) { 514 for i := start; i < end; i++ { 515 st.Set(keyFmt(i), valFmt(i)) 516 mem.Set(keyFmt(i), valFmt(i)) 517 } 518 } 519 520 func deleteRange(st types.Store, mem dbm.DB, start, end int) { 521 for i := start; i < end; i++ { 522 st.Delete(keyFmt(i)) 523 mem.Delete(keyFmt(i)) 524 } 525 } 526 527 // -------------------------------------------------------- 528 529 type keyRange struct { 530 start int 531 end int 532 } 533 534 func (kr keyRange) len() int { 535 return kr.end - kr.start 536 } 537 538 func newKeyRangeCounter(kr []keyRange) *keyRangeCounter { 539 return &keyRangeCounter{keyRanges: kr} 540 } 541 542 // we can iterate over this and make sure our real iterators have all the right keys 543 type keyRangeCounter struct { 544 rangeIdx int 545 idx int 546 keyRanges []keyRange 547 } 548 549 func (krc *keyRangeCounter) valid() bool { 550 maxRangeIdx := len(krc.keyRanges) - 1 551 maxRange := krc.keyRanges[maxRangeIdx] 552 553 // if we're not in the max range, we're valid 554 if krc.rangeIdx <= maxRangeIdx && 555 krc.idx < maxRange.len() { 556 return true 557 } 558 559 return false 560 } 561 562 func (krc *keyRangeCounter) next() { 563 thisKeyRange := krc.keyRanges[krc.rangeIdx] 564 if krc.idx == thisKeyRange.len()-1 { 565 krc.rangeIdx++ 566 krc.idx = 0 567 } else { 568 krc.idx++ 569 } 570 } 571 572 func (krc *keyRangeCounter) key() int { 573 thisKeyRange := krc.keyRanges[krc.rangeIdx] 574 return thisKeyRange.start + krc.idx 575 } 576 577 // -------------------------------------------------------- 578 579 func bz(s string) []byte { return []byte(s) } 580 581 func BenchmarkCacheStoreGetNoKeyFound(b *testing.B) { 582 st := newCacheStore() 583 b.ResetTimer() 584 // assumes b.N < 2**24 585 for i := 0; i < b.N; i++ { 586 st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}) 587 } 588 } 589 590 func BenchmarkCacheStoreGetKeyFound(b *testing.B) { 591 if testing.Short() { 592 b.Skip("skipping testing in short mode") 593 } 594 595 st := newCacheStore() 596 for i := 0; i < b.N; i++ { 597 arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} 598 st.Set(arr, arr) 599 } 600 b.ResetTimer() 601 // assumes b.N < 2**24 602 for i := 0; i < b.N; i++ { 603 st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}) 604 } 605 }