aboutsummaryrefslogblamecommitdiffstats
path: root/swarm/shed/index_test.go
blob: 97d7c91f439f95934b43cc328ee8e8633c95c4b3 (plain) (tree)






























                                                                                  
                                                              

                                          
                                                         


                               
                                                                  




                                                                            
                                                                           
















                                                                                 
                             








                                                                    
                                           




                                              
                                       

                                                       
                                     








                                                                            
                                                   




                                                      
                                               



                                                  
                             










                                                                    
                                           




                                              
                                       

                                                       
                                     










                                                                            
                                                   




                                                      
                                               









                                                                           
                                             




                                                                    
                             










                                                                    
                                           




                                         
                                       


                                            
                             








                                                                    
                                           




                                              
                                       
 
                                        






                                              
                                          







                                                                       
                             








                                                                       
                                           




                                              
                                       

                                           
                                                







                                              
                                          







                                                                       


                                            







                                                                   
                        




























                                                           
                       














                                                                            
                                                                             



                                                                                               
                                                

                                         
                       




                                    
                                                

                               
                                                                             



                                                                                               
                                                

                                         





















                                                                                               









                                          
                                                                             



                                                                                               
                                                





                                                
                       














                                                                                    
                                   


                                                           
                                                 




                                    
                                                                            



                                                                                               
                                                

                                         
                       




                                    
                                                                                  


                                                                                               






























































                                                                                               

                                         

                                       



                                    











































































































































































































































































                                                                                               


          

                                                                                     














                                                                                                           
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package shed

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "sort"
    "testing"
    "time"

    "github.com/syndtr/goleveldb/leveldb"
)

// Index functions for the index that is used in tests in this file.
var retrievalIndexFuncs = IndexFuncs{
    EncodeKey: func(fields Item) (key []byte, err error) {
        return fields.Address, nil
    },
    DecodeKey: func(key []byte) (e Item, err error) {
        e.Address = key
        return e, nil
    },
    EncodeValue: func(fields Item) (value []byte, err error) {
        b := make([]byte, 8)
        binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp))
        value = append(b, fields.Data...)
        return value, nil
    },
    DecodeValue: func(keyItem Item, value []byte) (e Item, err error) {
        e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8]))
        e.Data = value[8:]
        return e, nil
    },
}

// TestIndex validates put, get and delete functions of the Index implementation.
func TestIndex(t *testing.T) {
    db, cleanupFunc := newTestDB(t)
    defer cleanupFunc()

    index, err := db.NewIndex("retrieval", retrievalIndexFuncs)
    if err != nil {
        t.Fatal(err)
    }

    t.Run("put", func(t *testing.T) {
        want := Item{
            Address:        []byte("put-hash"),
            Data:           []byte("DATA"),
            StoreTimestamp: time.Now().UTC().UnixNano(),
        }

        err := index.Put(want)
        if err != nil {
            t.Fatal(err)
        }
        got, err := index.Get(Item{
            Address: want.Address,
        })
        if err != nil {
            t.Fatal(err)
        }
        checkItem(t, got, want)

        t.Run("overwrite", func(t *testing.T) {
            want := Item{
                Address:        []byte("put-hash"),
                Data:           []byte("New DATA"),
                StoreTimestamp: time.Now().UTC().UnixNano(),
            }

            err = index.Put(want)
            if err != nil {
                t.Fatal(err)
            }
            got, err := index.Get(Item{
                Address: want.Address,
            })
            if err != nil {
                t.Fatal(err)
            }
            checkItem(t, got, want)
        })
    })

    t.Run("put in batch", func(t *testing.T) {
        want := Item{
            Address:        []byte("put-in-batch-hash"),
            Data:           []byte("DATA"),
            StoreTimestamp: time.Now().UTC().UnixNano(),
        }

        batch := new(leveldb.Batch)
        index.PutInBatch(batch, want)
        err := db.WriteBatch(batch)
        if err != nil {
            t.Fatal(err)
        }
        got, err := index.Get(Item{
            Address: want.Address,
        })
        if err != nil {
            t.Fatal(err)
        }
        checkItem(t, got, want)

        t.Run("overwrite", func(t *testing.T) {
            want := Item{
                Address:        []byte("put-in-batch-hash"),
                Data:           []byte("New DATA"),
                StoreTimestamp: time.Now().UTC().UnixNano(),
            }

            batch := new(leveldb.Batch)
            index.PutInBatch(batch, want)
            db.WriteBatch(batch)
            if err != nil {
                t.Fatal(err)
            }
            got, err := index.Get(Item{
                Address: want.Address,
            })
            if err != nil {
                t.Fatal(err)
            }
            checkItem(t, got, want)
        })
    })

    t.Run("put in batch twice", func(t *testing.T) {
        // ensure that the last item of items with the same db keys
        // is actually saved
        batch := new(leveldb.Batch)
        address := []byte("put-in-batch-twice-hash")

        // put the first item
        index.PutInBatch(batch, Item{
            Address:        address,
            Data:           []byte("DATA"),
            StoreTimestamp: time.Now().UTC().UnixNano(),
        })

        want := Item{
            Address:        address,
            Data:           []byte("New DATA"),
            StoreTimestamp: time.Now().UTC().UnixNano(),
        }
        // then put the item that will produce the same key
        // but different value in the database
        index.PutInBatch(batch, want)
        db.WriteBatch(batch)
        if err != nil {
            t.Fatal(err)
        }
        got, err := index.Get(Item{
            Address: address,
        })
        if err != nil {
            t.Fatal(err)
        }
        checkItem(t, got, want)
    })

    t.Run("delete", func(t *testing.T) {
        want := Item{
            Address:        []byte("delete-hash"),
            Data:           []byte("DATA"),
            StoreTimestamp: time.Now().UTC().UnixNano(),
        }

        err := index.Put(want)
        if err != nil {
            t.Fatal(err)
        }
        got, err := index.Get(Item{
            Address: want.Address,
        })
        if err != nil {
            t.Fatal(err)
        }
        checkItem(t, got, want)

        err = index.Delete(Item{
            Address: want.Address,
        })
        if err != nil {
            t.Fatal(err)
        }

        wantErr := leveldb.ErrNotFound
        got, err = index.Get(Item{
            Address: want.Address,
        })
        if err != wantErr {
            t.Fatalf("got error %v, want %v", err, wantErr)
        }
    })

    t.Run("delete in batch", func(t *testing.T) {
        want := Item{
            Address:        []byte("delete-in-batch-hash"),
            Data:           []byte("DATA"),
            StoreTimestamp: time.Now().UTC().UnixNano(),
        }

        err := index.Put(want)
        if err != nil {
            t.Fatal(err)
        }
        got, err := index.Get(Item{
            Address: want.Address,
        })
        if err != nil {
            t.Fatal(err)
        }
        checkItem(t, got, want)

        batch := new(leveldb.Batch)
        index.DeleteInBatch(batch, Item{
            Address: want.Address,
        })
        err = db.WriteBatch(batch)
        if err != nil {
            t.Fatal(err)
        }

        wantErr := leveldb.ErrNotFound
        got, err = index.Get(Item{
            Address: want.Address,
        })
        if err != wantErr {
            t.Fatalf("got error %v, want %v", err, wantErr)
        }
    })
}

// TestIndex_Iterate validates index Iterate
// functions for correctness.
func TestIndex_Iterate(t *testing.T) {
    db, cleanupFunc := newTestDB(t)
    defer cleanupFunc()

    index, err := db.NewIndex("retrieval", retrievalIndexFuncs)
    if err != nil {
        t.Fatal(err)
    }

    items := []Item{
        {
            Address: []byte("iterate-hash-01"),
            Data:    []byte("data80"),
        },
        {
            Address: []byte("iterate-hash-03"),
            Data:    []byte("data22"),
        },
        {
            Address: []byte("iterate-hash-05"),
            Data:    []byte("data41"),
        },
        {
            Address: []byte("iterate-hash-02"),
            Data:    []byte("data84"),
        },
        {
            Address: []byte("iterate-hash-06"),
            Data:    []byte("data1"),
        },
    }
    batch := new(leveldb.Batch)
    for _, i := range items {
        index.PutInBatch(batch, i)
    }
    err = db.WriteBatch(batch)
    if err != nil {
        t.Fatal(err)
    }
    item04 := Item{
        Address: []byte("iterate-hash-04"),
        Data:    []byte("data0"),
    }
    err = index.Put(item04)
    if err != nil {
        t.Fatal(err)
    }
    items = append(items, item04)

    sort.SliceStable(items, func(i, j int) bool {
        return bytes.Compare(items[i].Address, items[j].Address) < 0
    })

    t.Run("all", func(t *testing.T) {
        var i int
        err := index.Iterate(func(item Item) (stop bool, err error) {
            if i > len(items)-1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            want := items[i]
            checkItem(t, item, want)
            i++
            return false, nil
        }, nil)
        if err != nil {
            t.Fatal(err)
        }
    })

    t.Run("start from", func(t *testing.T) {
        startIndex := 2
        i := startIndex
        err := index.Iterate(func(item Item) (stop bool, err error) {
            if i > len(items)-1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            want := items[i]
            checkItem(t, item, want)
            i++
            return false, nil
        }, &IterateOptions{
            StartFrom: &items[startIndex],
        })
        if err != nil {
            t.Fatal(err)
        }
    })

    t.Run("skip start from", func(t *testing.T) {
        startIndex := 2
        i := startIndex + 1
        err := index.Iterate(func(item Item) (stop bool, err error) {
            if i > len(items)-1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            want := items[i]
            checkItem(t, item, want)
            i++
            return false, nil
        }, &IterateOptions{
            StartFrom:         &items[startIndex],
            SkipStartFromItem: true,
        })
        if err != nil {
            t.Fatal(err)
        }
    })

    t.Run("stop", func(t *testing.T) {
        var i int
        stopIndex := 3
        var count int
        err := index.Iterate(func(item Item) (stop bool, err error) {
            if i > len(items)-1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            want := items[i]
            checkItem(t, item, want)
            count++
            if i == stopIndex {
                return true, nil
            }
            i++
            return false, nil
        }, nil)
        if err != nil {
            t.Fatal(err)
        }
        wantItemsCount := stopIndex + 1
        if count != wantItemsCount {
            t.Errorf("got %v items, expected %v", count, wantItemsCount)
        }
    })

    t.Run("no overflow", func(t *testing.T) {
        secondIndex, err := db.NewIndex("second-index", retrievalIndexFuncs)
        if err != nil {
            t.Fatal(err)
        }

        secondItem := Item{
            Address: []byte("iterate-hash-10"),
            Data:    []byte("data-second"),
        }
        err = secondIndex.Put(secondItem)
        if err != nil {
            t.Fatal(err)
        }

        var i int
        err = index.Iterate(func(item Item) (stop bool, err error) {
            if i > len(items)-1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            want := items[i]
            checkItem(t, item, want)
            i++
            return false, nil
        }, nil)
        if err != nil {
            t.Fatal(err)
        }

        i = 0
        err = secondIndex.Iterate(func(item Item) (stop bool, err error) {
            if i > 1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            checkItem(t, item, secondItem)
            i++
            return false, nil
        }, nil)
        if err != nil {
            t.Fatal(err)
        }
    })
}

// TestIndex_Iterate_withPrefix validates index Iterate
// function for correctness.
func TestIndex_Iterate_withPrefix(t *testing.T) {
    db, cleanupFunc := newTestDB(t)
    defer cleanupFunc()

    index, err := db.NewIndex("retrieval", retrievalIndexFuncs)
    if err != nil {
        t.Fatal(err)
    }

    allItems := []Item{
        {Address: []byte("want-hash-00"), Data: []byte("data80")},
        {Address: []byte("skip-hash-01"), Data: []byte("data81")},
        {Address: []byte("skip-hash-02"), Data: []byte("data82")},
        {Address: []byte("skip-hash-03"), Data: []byte("data83")},
        {Address: []byte("want-hash-04"), Data: []byte("data84")},
        {Address: []byte("want-hash-05"), Data: []byte("data85")},
        {Address: []byte("want-hash-06"), Data: []byte("data86")},
        {Address: []byte("want-hash-07"), Data: []byte("data87")},
        {Address: []byte("want-hash-08"), Data: []byte("data88")},
        {Address: []byte("want-hash-09"), Data: []byte("data89")},
        {Address: []byte("skip-hash-10"), Data: []byte("data90")},
    }
    batch := new(leveldb.Batch)
    for _, i := range allItems {
        index.PutInBatch(batch, i)
    }
    err = db.WriteBatch(batch)
    if err != nil {
        t.Fatal(err)
    }

    prefix := []byte("want")

    items := make([]Item, 0)
    for _, item := range allItems {
        if bytes.HasPrefix(item.Address, prefix) {
            items = append(items, item)
        }
    }
    sort.SliceStable(items, func(i, j int) bool {
        return bytes.Compare(items[i].Address, items[j].Address) < 0
    })

    t.Run("with prefix", func(t *testing.T) {
        var i int
        err := index.Iterate(func(item Item) (stop bool, err error) {
            if i > len(items)-1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            want := items[i]
            checkItem(t, item, want)
            i++
            return false, nil
        }, &IterateOptions{
            Prefix: prefix,
        })
        if err != nil {
            t.Fatal(err)
        }
        if i != len(items) {
            t.Errorf("got %v items, want %v", i, len(items))
        }
    })

    t.Run("with prefix and start from", func(t *testing.T) {
        startIndex := 2
        var count int
        i := startIndex
        err := index.Iterate(func(item Item) (stop bool, err error) {
            if i > len(items)-1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            want := items[i]
            checkItem(t, item, want)
            i++
            count++
            return false, nil
        }, &IterateOptions{
            StartFrom: &items[startIndex],
            Prefix:    prefix,
        })
        if err != nil {
            t.Fatal(err)
        }
        wantCount := len(items) - startIndex
        if count != wantCount {
            t.Errorf("got %v items, want %v", count, wantCount)
        }
    })

    t.Run("with prefix and skip start from", func(t *testing.T) {
        startIndex := 2
        var count int
        i := startIndex + 1
        err := index.Iterate(func(item Item) (stop bool, err error) {
            if i > len(items)-1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            want := items[i]
            checkItem(t, item, want)
            i++
            count++
            return false, nil
        }, &IterateOptions{
            StartFrom:         &items[startIndex],
            SkipStartFromItem: true,
            Prefix:            prefix,
        })
        if err != nil {
            t.Fatal(err)
        }
        wantCount := len(items) - startIndex - 1
        if count != wantCount {
            t.Errorf("got %v items, want %v", count, wantCount)
        }
    })

    t.Run("stop", func(t *testing.T) {
        var i int
        stopIndex := 3
        var count int
        err := index.Iterate(func(item Item) (stop bool, err error) {
            if i > len(items)-1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            want := items[i]
            checkItem(t, item, want)
            count++
            if i == stopIndex {
                return true, nil
            }
            i++
            return false, nil
        }, &IterateOptions{
            Prefix: prefix,
        })
        if err != nil {
            t.Fatal(err)
        }
        wantItemsCount := stopIndex + 1
        if count != wantItemsCount {
            t.Errorf("got %v items, expected %v", count, wantItemsCount)
        }
    })

    t.Run("no overflow", func(t *testing.T) {
        secondIndex, err := db.NewIndex("second-index", retrievalIndexFuncs)
        if err != nil {
            t.Fatal(err)
        }

        secondItem := Item{
            Address: []byte("iterate-hash-10"),
            Data:    []byte("data-second"),
        }
        err = secondIndex.Put(secondItem)
        if err != nil {
            t.Fatal(err)
        }

        var i int
        err = index.Iterate(func(item Item) (stop bool, err error) {
            if i > len(items)-1 {
                return true, fmt.Errorf("got unexpected index item: %#v", item)
            }
            want := items[i]
            checkItem(t, item, want)
            i++
            return false, nil
        }, &IterateOptions{
            Prefix: prefix,
        })
        if err != nil {
            t.Fatal(err)
        }
        if i != len(items) {
            t.Errorf("got %v items, want %v", i, len(items))
        }
    })
}

// TestIndex_count tests if Index.Count and Index.CountFrom
// returns the correct number of items.
func TestIndex_count(t *testing.T) {
    db, cleanupFunc := newTestDB(t)
    defer cleanupFunc()

    index, err := db.NewIndex("retrieval", retrievalIndexFuncs)
    if err != nil {
        t.Fatal(err)
    }

    items := []Item{
        {
            Address: []byte("iterate-hash-01"),
            Data:    []byte("data80"),
        },
        {
            Address: []byte("iterate-hash-02"),
            Data:    []byte("data84"),
        },
        {
            Address: []byte("iterate-hash-03"),
            Data:    []byte("data22"),
        },
        {
            Address: []byte("iterate-hash-04"),
            Data:    []byte("data41"),
        },
        {
            Address: []byte("iterate-hash-05"),
            Data:    []byte("data1"),
        },
    }
    batch := new(leveldb.Batch)
    for _, i := range items {
        index.PutInBatch(batch, i)
    }
    err = db.WriteBatch(batch)
    if err != nil {
        t.Fatal(err)
    }

    t.Run("Count", func(t *testing.T) {
        got, err := index.Count()
        if err != nil {
            t.Fatal(err)
        }

        want := len(items)
        if got != want {
            t.Errorf("got %v items count, want %v", got, want)
        }
    })

    t.Run("CountFrom", func(t *testing.T) {
        got, err := index.CountFrom(Item{
            Address: items[1].Address,
        })
        if err != nil {
            t.Fatal(err)
        }

        want := len(items) - 1
        if got != want {
            t.Errorf("got %v items count, want %v", got, want)
        }
    })

    // update the index with another item
    t.Run("add item", func(t *testing.T) {
        item04 := Item{
            Address: []byte("iterate-hash-06"),
            Data:    []byte("data0"),
        }
        err = index.Put(item04)
        if err != nil {
            t.Fatal(err)
        }

        count := len(items) + 1

        t.Run("Count", func(t *testing.T) {
            got, err := index.Count()
            if err != nil {
                t.Fatal(err)
            }

            want := count
            if got != want {
                t.Errorf("got %v items count, want %v", got, want)
            }
        })

        t.Run("CountFrom", func(t *testing.T) {
            got, err := index.CountFrom(Item{
                Address: items[1].Address,
            })
            if err != nil {
                t.Fatal(err)
            }

            want := count - 1
            if got != want {
                t.Errorf("got %v items count, want %v", got, want)
            }
        })
    })

    // delete some items
    t.Run("delete items", func(t *testing.T) {
        deleteCount := 3

        for _, item := range items[:deleteCount] {
            err := index.Delete(item)
            if err != nil {
                t.Fatal(err)
            }
        }

        count := len(items) + 1 - deleteCount

        t.Run("Count", func(t *testing.T) {
            got, err := index.Count()
            if err != nil {
                t.Fatal(err)
            }

            want := count
            if got != want {
                t.Errorf("got %v items count, want %v", got, want)
            }
        })

        t.Run("CountFrom", func(t *testing.T) {
            got, err := index.CountFrom(Item{
                Address: items[deleteCount+1].Address,
            })
            if err != nil {
                t.Fatal(err)
            }

            want := count - 1
            if got != want {
                t.Errorf("got %v items count, want %v", got, want)
            }
        })
    })
}

// checkItem is a test helper function that compares if two Index items are the same.
func checkItem(t *testing.T, got, want Item) {
    t.Helper()

    if !bytes.Equal(got.Address, want.Address) {
        t.Errorf("got hash %q, expected %q", string(got.Address), string(want.Address))
    }
    if !bytes.Equal(got.Data, want.Data) {
        t.Errorf("got data %q, expected %q", string(got.Data), string(want.Data))
    }
    if got.StoreTimestamp != want.StoreTimestamp {
        t.Errorf("got store timestamp %v, expected %v", got.StoreTimestamp, want.StoreTimestamp)
    }
    if got.AccessTimestamp != want.AccessTimestamp {
        t.Errorf("got access timestamp %v, expected %v", got.AccessTimestamp, want.AccessTimestamp)
    }
}