github.com/susy-go/susy-graviton@v0.0.0-20190614130430-36cddae42305/swarm/shed/index_test.go (about) 1 // Copyleft 2018 The susy-graviton Authors 2 // This file is part of the susy-graviton library. 3 // 4 // The susy-graviton library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The susy-graviton library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MSRCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the susy-graviton library. If not, see <http://www.gnu.org/licenses/>. 16 17 package shed 18 19 import ( 20 "bytes" 21 "encoding/binary" 22 "fmt" 23 "sort" 24 "testing" 25 "time" 26 27 "github.com/syndtr/goleveldb/leveldb" 28 ) 29 30 // Index functions for the index that is used in tests in this file. 31 var retrievalIndexFuncs = IndexFuncs{ 32 EncodeKey: func(fields Item) (key []byte, err error) { 33 return fields.Address, nil 34 }, 35 DecodeKey: func(key []byte) (e Item, err error) { 36 e.Address = key 37 return e, nil 38 }, 39 EncodeValue: func(fields Item) (value []byte, err error) { 40 b := make([]byte, 8) 41 binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp)) 42 value = append(b, fields.Data...) 43 return value, nil 44 }, 45 DecodeValue: func(keyItem Item, value []byte) (e Item, err error) { 46 e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8])) 47 e.Data = value[8:] 48 return e, nil 49 }, 50 } 51 52 // TestIndex validates put, get and delete functions of the Index implementation. 53 func TestIndex(t *testing.T) { 54 db, cleanupFunc := newTestDB(t) 55 defer cleanupFunc() 56 57 index, err := db.NewIndex("retrieval", retrievalIndexFuncs) 58 if err != nil { 59 t.Fatal(err) 60 } 61 62 t.Run("put", func(t *testing.T) { 63 want := Item{ 64 Address: []byte("put-hash"), 65 Data: []byte("DATA"), 66 StoreTimestamp: time.Now().UTC().UnixNano(), 67 } 68 69 err := index.Put(want) 70 if err != nil { 71 t.Fatal(err) 72 } 73 got, err := index.Get(Item{ 74 Address: want.Address, 75 }) 76 if err != nil { 77 t.Fatal(err) 78 } 79 checkItem(t, got, want) 80 81 t.Run("overwrite", func(t *testing.T) { 82 want := Item{ 83 Address: []byte("put-hash"), 84 Data: []byte("New DATA"), 85 StoreTimestamp: time.Now().UTC().UnixNano(), 86 } 87 88 err = index.Put(want) 89 if err != nil { 90 t.Fatal(err) 91 } 92 got, err := index.Get(Item{ 93 Address: want.Address, 94 }) 95 if err != nil { 96 t.Fatal(err) 97 } 98 checkItem(t, got, want) 99 }) 100 }) 101 102 t.Run("put in batch", func(t *testing.T) { 103 want := Item{ 104 Address: []byte("put-in-batch-hash"), 105 Data: []byte("DATA"), 106 StoreTimestamp: time.Now().UTC().UnixNano(), 107 } 108 109 batch := new(leveldb.Batch) 110 index.PutInBatch(batch, want) 111 err := db.WriteBatch(batch) 112 if err != nil { 113 t.Fatal(err) 114 } 115 got, err := index.Get(Item{ 116 Address: want.Address, 117 }) 118 if err != nil { 119 t.Fatal(err) 120 } 121 checkItem(t, got, want) 122 123 t.Run("overwrite", func(t *testing.T) { 124 want := Item{ 125 Address: []byte("put-in-batch-hash"), 126 Data: []byte("New DATA"), 127 StoreTimestamp: time.Now().UTC().UnixNano(), 128 } 129 130 batch := new(leveldb.Batch) 131 index.PutInBatch(batch, want) 132 db.WriteBatch(batch) 133 if err != nil { 134 t.Fatal(err) 135 } 136 got, err := index.Get(Item{ 137 Address: want.Address, 138 }) 139 if err != nil { 140 t.Fatal(err) 141 } 142 checkItem(t, got, want) 143 }) 144 }) 145 146 t.Run("put in batch twice", func(t *testing.T) { 147 // ensure that the last item of items with the same db keys 148 // is actually saved 149 batch := new(leveldb.Batch) 150 address := []byte("put-in-batch-twice-hash") 151 152 // put the first item 153 index.PutInBatch(batch, Item{ 154 Address: address, 155 Data: []byte("DATA"), 156 StoreTimestamp: time.Now().UTC().UnixNano(), 157 }) 158 159 want := Item{ 160 Address: address, 161 Data: []byte("New DATA"), 162 StoreTimestamp: time.Now().UTC().UnixNano(), 163 } 164 // then put the item that will produce the same key 165 // but different value in the database 166 index.PutInBatch(batch, want) 167 db.WriteBatch(batch) 168 if err != nil { 169 t.Fatal(err) 170 } 171 got, err := index.Get(Item{ 172 Address: address, 173 }) 174 if err != nil { 175 t.Fatal(err) 176 } 177 checkItem(t, got, want) 178 }) 179 180 t.Run("delete", func(t *testing.T) { 181 want := Item{ 182 Address: []byte("delete-hash"), 183 Data: []byte("DATA"), 184 StoreTimestamp: time.Now().UTC().UnixNano(), 185 } 186 187 err := index.Put(want) 188 if err != nil { 189 t.Fatal(err) 190 } 191 got, err := index.Get(Item{ 192 Address: want.Address, 193 }) 194 if err != nil { 195 t.Fatal(err) 196 } 197 checkItem(t, got, want) 198 199 err = index.Delete(Item{ 200 Address: want.Address, 201 }) 202 if err != nil { 203 t.Fatal(err) 204 } 205 206 wantErr := leveldb.ErrNotFound 207 got, err = index.Get(Item{ 208 Address: want.Address, 209 }) 210 if err != wantErr { 211 t.Fatalf("got error %v, want %v", err, wantErr) 212 } 213 }) 214 215 t.Run("delete in batch", func(t *testing.T) { 216 want := Item{ 217 Address: []byte("delete-in-batch-hash"), 218 Data: []byte("DATA"), 219 StoreTimestamp: time.Now().UTC().UnixNano(), 220 } 221 222 err := index.Put(want) 223 if err != nil { 224 t.Fatal(err) 225 } 226 got, err := index.Get(Item{ 227 Address: want.Address, 228 }) 229 if err != nil { 230 t.Fatal(err) 231 } 232 checkItem(t, got, want) 233 234 batch := new(leveldb.Batch) 235 index.DeleteInBatch(batch, Item{ 236 Address: want.Address, 237 }) 238 err = db.WriteBatch(batch) 239 if err != nil { 240 t.Fatal(err) 241 } 242 243 wantErr := leveldb.ErrNotFound 244 got, err = index.Get(Item{ 245 Address: want.Address, 246 }) 247 if err != wantErr { 248 t.Fatalf("got error %v, want %v", err, wantErr) 249 } 250 }) 251 } 252 253 // TestIndex_Iterate validates index Iterate 254 // functions for correctness. 255 func TestIndex_Iterate(t *testing.T) { 256 db, cleanupFunc := newTestDB(t) 257 defer cleanupFunc() 258 259 index, err := db.NewIndex("retrieval", retrievalIndexFuncs) 260 if err != nil { 261 t.Fatal(err) 262 } 263 264 items := []Item{ 265 { 266 Address: []byte("iterate-hash-01"), 267 Data: []byte("data80"), 268 }, 269 { 270 Address: []byte("iterate-hash-03"), 271 Data: []byte("data22"), 272 }, 273 { 274 Address: []byte("iterate-hash-05"), 275 Data: []byte("data41"), 276 }, 277 { 278 Address: []byte("iterate-hash-02"), 279 Data: []byte("data84"), 280 }, 281 { 282 Address: []byte("iterate-hash-06"), 283 Data: []byte("data1"), 284 }, 285 } 286 batch := new(leveldb.Batch) 287 for _, i := range items { 288 index.PutInBatch(batch, i) 289 } 290 err = db.WriteBatch(batch) 291 if err != nil { 292 t.Fatal(err) 293 } 294 item04 := Item{ 295 Address: []byte("iterate-hash-04"), 296 Data: []byte("data0"), 297 } 298 err = index.Put(item04) 299 if err != nil { 300 t.Fatal(err) 301 } 302 items = append(items, item04) 303 304 sort.SliceStable(items, func(i, j int) bool { 305 return bytes.Compare(items[i].Address, items[j].Address) < 0 306 }) 307 308 t.Run("all", func(t *testing.T) { 309 var i int 310 err := index.Iterate(func(item Item) (stop bool, err error) { 311 if i > len(items)-1 { 312 return true, fmt.Errorf("got unexpected index item: %#v", item) 313 } 314 want := items[i] 315 checkItem(t, item, want) 316 i++ 317 return false, nil 318 }, nil) 319 if err != nil { 320 t.Fatal(err) 321 } 322 }) 323 324 t.Run("start from", func(t *testing.T) { 325 startIndex := 2 326 i := startIndex 327 err := index.Iterate(func(item Item) (stop bool, err error) { 328 if i > len(items)-1 { 329 return true, fmt.Errorf("got unexpected index item: %#v", item) 330 } 331 want := items[i] 332 checkItem(t, item, want) 333 i++ 334 return false, nil 335 }, &IterateOptions{ 336 StartFrom: &items[startIndex], 337 }) 338 if err != nil { 339 t.Fatal(err) 340 } 341 }) 342 343 t.Run("skip start from", func(t *testing.T) { 344 startIndex := 2 345 i := startIndex + 1 346 err := index.Iterate(func(item Item) (stop bool, err error) { 347 if i > len(items)-1 { 348 return true, fmt.Errorf("got unexpected index item: %#v", item) 349 } 350 want := items[i] 351 checkItem(t, item, want) 352 i++ 353 return false, nil 354 }, &IterateOptions{ 355 StartFrom: &items[startIndex], 356 SkipStartFromItem: true, 357 }) 358 if err != nil { 359 t.Fatal(err) 360 } 361 }) 362 363 t.Run("stop", func(t *testing.T) { 364 var i int 365 stopIndex := 3 366 var count int 367 err := index.Iterate(func(item Item) (stop bool, err error) { 368 if i > len(items)-1 { 369 return true, fmt.Errorf("got unexpected index item: %#v", item) 370 } 371 want := items[i] 372 checkItem(t, item, want) 373 count++ 374 if i == stopIndex { 375 return true, nil 376 } 377 i++ 378 return false, nil 379 }, nil) 380 if err != nil { 381 t.Fatal(err) 382 } 383 wantItemsCount := stopIndex + 1 384 if count != wantItemsCount { 385 t.Errorf("got %v items, expected %v", count, wantItemsCount) 386 } 387 }) 388 389 t.Run("no overflow", func(t *testing.T) { 390 secondIndex, err := db.NewIndex("second-index", retrievalIndexFuncs) 391 if err != nil { 392 t.Fatal(err) 393 } 394 395 secondItem := Item{ 396 Address: []byte("iterate-hash-10"), 397 Data: []byte("data-second"), 398 } 399 err = secondIndex.Put(secondItem) 400 if err != nil { 401 t.Fatal(err) 402 } 403 404 var i int 405 err = index.Iterate(func(item Item) (stop bool, err error) { 406 if i > len(items)-1 { 407 return true, fmt.Errorf("got unexpected index item: %#v", item) 408 } 409 want := items[i] 410 checkItem(t, item, want) 411 i++ 412 return false, nil 413 }, nil) 414 if err != nil { 415 t.Fatal(err) 416 } 417 418 i = 0 419 err = secondIndex.Iterate(func(item Item) (stop bool, err error) { 420 if i > 1 { 421 return true, fmt.Errorf("got unexpected index item: %#v", item) 422 } 423 checkItem(t, item, secondItem) 424 i++ 425 return false, nil 426 }, nil) 427 if err != nil { 428 t.Fatal(err) 429 } 430 }) 431 } 432 433 // TestIndex_Iterate_withPrefix validates index Iterate 434 // function for correctness. 435 func TestIndex_Iterate_withPrefix(t *testing.T) { 436 db, cleanupFunc := newTestDB(t) 437 defer cleanupFunc() 438 439 index, err := db.NewIndex("retrieval", retrievalIndexFuncs) 440 if err != nil { 441 t.Fatal(err) 442 } 443 444 allItems := []Item{ 445 {Address: []byte("want-hash-00"), Data: []byte("data80")}, 446 {Address: []byte("skip-hash-01"), Data: []byte("data81")}, 447 {Address: []byte("skip-hash-02"), Data: []byte("data82")}, 448 {Address: []byte("skip-hash-03"), Data: []byte("data83")}, 449 {Address: []byte("want-hash-04"), Data: []byte("data84")}, 450 {Address: []byte("want-hash-05"), Data: []byte("data85")}, 451 {Address: []byte("want-hash-06"), Data: []byte("data86")}, 452 {Address: []byte("want-hash-07"), Data: []byte("data87")}, 453 {Address: []byte("want-hash-08"), Data: []byte("data88")}, 454 {Address: []byte("want-hash-09"), Data: []byte("data89")}, 455 {Address: []byte("skip-hash-10"), Data: []byte("data90")}, 456 } 457 batch := new(leveldb.Batch) 458 for _, i := range allItems { 459 index.PutInBatch(batch, i) 460 } 461 err = db.WriteBatch(batch) 462 if err != nil { 463 t.Fatal(err) 464 } 465 466 prefix := []byte("want") 467 468 items := make([]Item, 0) 469 for _, item := range allItems { 470 if bytes.HasPrefix(item.Address, prefix) { 471 items = append(items, item) 472 } 473 } 474 sort.SliceStable(items, func(i, j int) bool { 475 return bytes.Compare(items[i].Address, items[j].Address) < 0 476 }) 477 478 t.Run("with prefix", func(t *testing.T) { 479 var i int 480 err := index.Iterate(func(item Item) (stop bool, err error) { 481 if i > len(items)-1 { 482 return true, fmt.Errorf("got unexpected index item: %#v", item) 483 } 484 want := items[i] 485 checkItem(t, item, want) 486 i++ 487 return false, nil 488 }, &IterateOptions{ 489 Prefix: prefix, 490 }) 491 if err != nil { 492 t.Fatal(err) 493 } 494 if i != len(items) { 495 t.Errorf("got %v items, want %v", i, len(items)) 496 } 497 }) 498 499 t.Run("with prefix and start from", func(t *testing.T) { 500 startIndex := 2 501 var count int 502 i := startIndex 503 err := index.Iterate(func(item Item) (stop bool, err error) { 504 if i > len(items)-1 { 505 return true, fmt.Errorf("got unexpected index item: %#v", item) 506 } 507 want := items[i] 508 checkItem(t, item, want) 509 i++ 510 count++ 511 return false, nil 512 }, &IterateOptions{ 513 StartFrom: &items[startIndex], 514 Prefix: prefix, 515 }) 516 if err != nil { 517 t.Fatal(err) 518 } 519 wantCount := len(items) - startIndex 520 if count != wantCount { 521 t.Errorf("got %v items, want %v", count, wantCount) 522 } 523 }) 524 525 t.Run("with prefix and skip start from", func(t *testing.T) { 526 startIndex := 2 527 var count int 528 i := startIndex + 1 529 err := index.Iterate(func(item Item) (stop bool, err error) { 530 if i > len(items)-1 { 531 return true, fmt.Errorf("got unexpected index item: %#v", item) 532 } 533 want := items[i] 534 checkItem(t, item, want) 535 i++ 536 count++ 537 return false, nil 538 }, &IterateOptions{ 539 StartFrom: &items[startIndex], 540 SkipStartFromItem: true, 541 Prefix: prefix, 542 }) 543 if err != nil { 544 t.Fatal(err) 545 } 546 wantCount := len(items) - startIndex - 1 547 if count != wantCount { 548 t.Errorf("got %v items, want %v", count, wantCount) 549 } 550 }) 551 552 t.Run("stop", func(t *testing.T) { 553 var i int 554 stopIndex := 3 555 var count int 556 err := index.Iterate(func(item Item) (stop bool, err error) { 557 if i > len(items)-1 { 558 return true, fmt.Errorf("got unexpected index item: %#v", item) 559 } 560 want := items[i] 561 checkItem(t, item, want) 562 count++ 563 if i == stopIndex { 564 return true, nil 565 } 566 i++ 567 return false, nil 568 }, &IterateOptions{ 569 Prefix: prefix, 570 }) 571 if err != nil { 572 t.Fatal(err) 573 } 574 wantItemsCount := stopIndex + 1 575 if count != wantItemsCount { 576 t.Errorf("got %v items, expected %v", count, wantItemsCount) 577 } 578 }) 579 580 t.Run("no overflow", func(t *testing.T) { 581 secondIndex, err := db.NewIndex("second-index", retrievalIndexFuncs) 582 if err != nil { 583 t.Fatal(err) 584 } 585 586 secondItem := Item{ 587 Address: []byte("iterate-hash-10"), 588 Data: []byte("data-second"), 589 } 590 err = secondIndex.Put(secondItem) 591 if err != nil { 592 t.Fatal(err) 593 } 594 595 var i int 596 err = index.Iterate(func(item Item) (stop bool, err error) { 597 if i > len(items)-1 { 598 return true, fmt.Errorf("got unexpected index item: %#v", item) 599 } 600 want := items[i] 601 checkItem(t, item, want) 602 i++ 603 return false, nil 604 }, &IterateOptions{ 605 Prefix: prefix, 606 }) 607 if err != nil { 608 t.Fatal(err) 609 } 610 if i != len(items) { 611 t.Errorf("got %v items, want %v", i, len(items)) 612 } 613 }) 614 } 615 616 // TestIndex_count tests if Index.Count and Index.CountFrom 617 // returns the correct number of items. 618 func TestIndex_count(t *testing.T) { 619 db, cleanupFunc := newTestDB(t) 620 defer cleanupFunc() 621 622 index, err := db.NewIndex("retrieval", retrievalIndexFuncs) 623 if err != nil { 624 t.Fatal(err) 625 } 626 627 items := []Item{ 628 { 629 Address: []byte("iterate-hash-01"), 630 Data: []byte("data80"), 631 }, 632 { 633 Address: []byte("iterate-hash-02"), 634 Data: []byte("data84"), 635 }, 636 { 637 Address: []byte("iterate-hash-03"), 638 Data: []byte("data22"), 639 }, 640 { 641 Address: []byte("iterate-hash-04"), 642 Data: []byte("data41"), 643 }, 644 { 645 Address: []byte("iterate-hash-05"), 646 Data: []byte("data1"), 647 }, 648 } 649 batch := new(leveldb.Batch) 650 for _, i := range items { 651 index.PutInBatch(batch, i) 652 } 653 err = db.WriteBatch(batch) 654 if err != nil { 655 t.Fatal(err) 656 } 657 658 t.Run("Count", func(t *testing.T) { 659 got, err := index.Count() 660 if err != nil { 661 t.Fatal(err) 662 } 663 664 want := len(items) 665 if got != want { 666 t.Errorf("got %v items count, want %v", got, want) 667 } 668 }) 669 670 t.Run("CountFrom", func(t *testing.T) { 671 got, err := index.CountFrom(Item{ 672 Address: items[1].Address, 673 }) 674 if err != nil { 675 t.Fatal(err) 676 } 677 678 want := len(items) - 1 679 if got != want { 680 t.Errorf("got %v items count, want %v", got, want) 681 } 682 }) 683 684 // update the index with another item 685 t.Run("add item", func(t *testing.T) { 686 item04 := Item{ 687 Address: []byte("iterate-hash-06"), 688 Data: []byte("data0"), 689 } 690 err = index.Put(item04) 691 if err != nil { 692 t.Fatal(err) 693 } 694 695 count := len(items) + 1 696 697 t.Run("Count", func(t *testing.T) { 698 got, err := index.Count() 699 if err != nil { 700 t.Fatal(err) 701 } 702 703 want := count 704 if got != want { 705 t.Errorf("got %v items count, want %v", got, want) 706 } 707 }) 708 709 t.Run("CountFrom", func(t *testing.T) { 710 got, err := index.CountFrom(Item{ 711 Address: items[1].Address, 712 }) 713 if err != nil { 714 t.Fatal(err) 715 } 716 717 want := count - 1 718 if got != want { 719 t.Errorf("got %v items count, want %v", got, want) 720 } 721 }) 722 }) 723 724 // delete some items 725 t.Run("delete items", func(t *testing.T) { 726 deleteCount := 3 727 728 for _, item := range items[:deleteCount] { 729 err := index.Delete(item) 730 if err != nil { 731 t.Fatal(err) 732 } 733 } 734 735 count := len(items) + 1 - deleteCount 736 737 t.Run("Count", func(t *testing.T) { 738 got, err := index.Count() 739 if err != nil { 740 t.Fatal(err) 741 } 742 743 want := count 744 if got != want { 745 t.Errorf("got %v items count, want %v", got, want) 746 } 747 }) 748 749 t.Run("CountFrom", func(t *testing.T) { 750 got, err := index.CountFrom(Item{ 751 Address: items[deleteCount+1].Address, 752 }) 753 if err != nil { 754 t.Fatal(err) 755 } 756 757 want := count - 1 758 if got != want { 759 t.Errorf("got %v items count, want %v", got, want) 760 } 761 }) 762 }) 763 } 764 765 // checkItem is a test helper function that compares if two Index items are the same. 766 func checkItem(t *testing.T, got, want Item) { 767 t.Helper() 768 769 if !bytes.Equal(got.Address, want.Address) { 770 t.Errorf("got hash %q, expected %q", string(got.Address), string(want.Address)) 771 } 772 if !bytes.Equal(got.Data, want.Data) { 773 t.Errorf("got data %q, expected %q", string(got.Data), string(want.Data)) 774 } 775 if got.StoreTimestamp != want.StoreTimestamp { 776 t.Errorf("got store timestamp %v, expected %v", got.StoreTimestamp, want.StoreTimestamp) 777 } 778 if got.AccessTimestamp != want.AccessTimestamp { 779 t.Errorf("got access timestamp %v, expected %v", got.AccessTimestamp, want.AccessTimestamp) 780 } 781 }