github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/swarm/storage/localstore/localstore_test.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package localstore
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"math/rand"
    24  	"os"
    25  	"runtime"
    26  	"sort"
    27  	"strconv"
    28  	"sync"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/ethereum/go-ethereum/swarm/chunk"
    33  	"github.com/ethereum/go-ethereum/swarm/shed"
    34  	"github.com/syndtr/goleveldb/leveldb"
    35  )
    36  
    37  func init() {
    38  	// Some of the tests in localstore package rely on the same ordering of
    39  	// items uploaded or accessed compared to the ordering of items in indexes
    40  	// that contain StoreTimestamp or AccessTimestamp in keys. In tests
    41  	// where the same order is required from the database as the order
    42  	// in which chunks are put or accessed, if the StoreTimestamp or
    43  	// AccessTimestamp are the same for two or more sequential items
    44  	// their order in database will be based on the chunk address value,
    45  	// in which case the ordering of items/chunks stored in a test slice
    46  	// will not be the same. To ensure the same ordering in database on such
    47  	// indexes on windows systems, an additional short sleep is added to
    48  	// the now function.
    49  	if runtime.GOOS == "windows" {
    50  		setNow(func() int64 {
    51  			time.Sleep(time.Microsecond)
    52  			return time.Now().UTC().UnixNano()
    53  		})
    54  	}
    55  }
    56  
    57  // TestDB validates if the chunk can be uploaded and
    58  // correctly retrieved.
    59  func TestDB(t *testing.T) {
    60  	db, cleanupFunc := newTestDB(t, nil)
    61  	defer cleanupFunc()
    62  
    63  	chunk := generateTestRandomChunk()
    64  
    65  	err := db.NewPutter(ModePutUpload).Put(chunk)
    66  	if err != nil {
    67  		t.Fatal(err)
    68  	}
    69  
    70  	got, err := db.NewGetter(ModeGetRequest).Get(chunk.Address())
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  
    75  	if !bytes.Equal(got.Address(), chunk.Address()) {
    76  		t.Errorf("got address %x, want %x", got.Address(), chunk.Address())
    77  	}
    78  	if !bytes.Equal(got.Data(), chunk.Data()) {
    79  		t.Errorf("got data %x, want %x", got.Data(), chunk.Data())
    80  	}
    81  }
    82  
    83  // TestDB_updateGCSem tests maxParallelUpdateGC limit.
    84  // This test temporary sets the limit to a low number,
    85  // makes updateGC function execution time longer by
    86  // setting a custom testHookUpdateGC function with a sleep
    87  // and a count current and maximal number of goroutines.
    88  func TestDB_updateGCSem(t *testing.T) {
    89  	updateGCSleep := time.Second
    90  	var count int
    91  	var max int
    92  	var mu sync.Mutex
    93  	defer setTestHookUpdateGC(func() {
    94  		mu.Lock()
    95  		// add to the count of current goroutines
    96  		count++
    97  		if count > max {
    98  			// set maximal detected numbers of goroutines
    99  			max = count
   100  		}
   101  		mu.Unlock()
   102  
   103  		// wait for some time to ensure multiple parallel goroutines
   104  		time.Sleep(updateGCSleep)
   105  
   106  		mu.Lock()
   107  		count--
   108  		mu.Unlock()
   109  	})()
   110  
   111  	defer func(m int) { maxParallelUpdateGC = m }(maxParallelUpdateGC)
   112  	maxParallelUpdateGC = 3
   113  
   114  	db, cleanupFunc := newTestDB(t, nil)
   115  	defer cleanupFunc()
   116  
   117  	chunk := generateTestRandomChunk()
   118  
   119  	err := db.NewPutter(ModePutUpload).Put(chunk)
   120  	if err != nil {
   121  		t.Fatal(err)
   122  	}
   123  
   124  	getter := db.NewGetter(ModeGetRequest)
   125  
   126  	// get more chunks then maxParallelUpdateGC
   127  	// in time shorter then updateGCSleep
   128  	for i := 0; i < 5; i++ {
   129  		_, err = getter.Get(chunk.Address())
   130  		if err != nil {
   131  			t.Fatal(err)
   132  		}
   133  	}
   134  
   135  	if max != maxParallelUpdateGC {
   136  		t.Errorf("got max %v, want %v", max, maxParallelUpdateGC)
   137  	}
   138  }
   139  
   140  // BenchmarkNew measures the time that New function
   141  // needs to initialize and count the number of key/value
   142  // pairs in GC index.
   143  // This benchmark generates a number of chunks, uploads them,
   144  // sets them to synced state for them to enter the GC index,
   145  // and measures the execution time of New function by creating
   146  // new databases with the same data directory.
   147  //
   148  // This benchmark takes significant amount of time.
   149  //
   150  // Measurements on MacBook Pro (Retina, 15-inch, Mid 2014) show
   151  // that New function executes around 1s for database with 1M chunks.
   152  //
   153  // # go test -benchmem -run=none github.com/ethereum/go-ethereum/swarm/storage/localstore -bench BenchmarkNew -v -timeout 20m
   154  // goos: darwin
   155  // goarch: amd64
   156  // pkg: github.com/ethereum/go-ethereum/swarm/storage/localstore
   157  // BenchmarkNew/1000-8         	     200	  11672414 ns/op	 9570960 B/op	   10008 allocs/op
   158  // BenchmarkNew/10000-8        	     100	  14890609 ns/op	10490118 B/op	    7759 allocs/op
   159  // BenchmarkNew/100000-8       	      20	  58334080 ns/op	17763157 B/op	   22978 allocs/op
   160  // BenchmarkNew/1000000-8      	       2	 748595153 ns/op	45297404 B/op	  253242 allocs/op
   161  // PASS
   162  func BenchmarkNew(b *testing.B) {
   163  	if testing.Short() {
   164  		b.Skip("skipping benchmark in short mode")
   165  	}
   166  	for _, count := range []int{
   167  		1000,
   168  		10000,
   169  		100000,
   170  		1000000,
   171  	} {
   172  		b.Run(strconv.Itoa(count), func(b *testing.B) {
   173  			dir, err := ioutil.TempDir("", "localstore-new-benchmark")
   174  			if err != nil {
   175  				b.Fatal(err)
   176  			}
   177  			defer os.RemoveAll(dir)
   178  			baseKey := make([]byte, 32)
   179  			if _, err := rand.Read(baseKey); err != nil {
   180  				b.Fatal(err)
   181  			}
   182  			db, err := New(dir, baseKey, nil)
   183  			if err != nil {
   184  				b.Fatal(err)
   185  			}
   186  			defer db.Close()
   187  			uploader := db.NewPutter(ModePutUpload)
   188  			syncer := db.NewSetter(ModeSetSync)
   189  			for i := 0; i < count; i++ {
   190  				chunk := generateTestRandomChunk()
   191  				err := uploader.Put(chunk)
   192  				if err != nil {
   193  					b.Fatal(err)
   194  				}
   195  				err = syncer.Set(chunk.Address())
   196  				if err != nil {
   197  					b.Fatal(err)
   198  				}
   199  			}
   200  			err = db.Close()
   201  			if err != nil {
   202  				b.Fatal(err)
   203  			}
   204  			b.ResetTimer()
   205  
   206  			for n := 0; n < b.N; n++ {
   207  				b.StartTimer()
   208  				db, err := New(dir, baseKey, nil)
   209  				b.StopTimer()
   210  
   211  				if err != nil {
   212  					b.Fatal(err)
   213  				}
   214  				err = db.Close()
   215  				if err != nil {
   216  					b.Fatal(err)
   217  				}
   218  			}
   219  		})
   220  	}
   221  }
   222  
   223  // newTestDB is a helper function that constructs a
   224  // temporary database and returns a cleanup function that must
   225  // be called to remove the data.
   226  func newTestDB(t testing.TB, o *Options) (db *DB, cleanupFunc func()) {
   227  	t.Helper()
   228  
   229  	dir, err := ioutil.TempDir("", "localstore-test")
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	cleanupFunc = func() { os.RemoveAll(dir) }
   234  	baseKey := make([]byte, 32)
   235  	if _, err := rand.Read(baseKey); err != nil {
   236  		t.Fatal(err)
   237  	}
   238  	db, err = New(dir, baseKey, o)
   239  	if err != nil {
   240  		cleanupFunc()
   241  		t.Fatal(err)
   242  	}
   243  	cleanupFunc = func() {
   244  		err := db.Close()
   245  		if err != nil {
   246  			t.Error(err)
   247  		}
   248  		os.RemoveAll(dir)
   249  	}
   250  	return db, cleanupFunc
   251  }
   252  
   253  func init() {
   254  	// needed for generateTestRandomChunk
   255  	rand.Seed(time.Now().UnixNano())
   256  }
   257  
   258  // generateTestRandomChunk generates a Chunk that is not
   259  // valid, but it contains a random key and a random value.
   260  // This function is faster then storage.generateTestRandomChunk
   261  // which generates a valid chunk.
   262  // Some tests in this package do not need valid chunks, just
   263  // random data, and their execution time can be decreased
   264  // using this function.
   265  func generateTestRandomChunk() chunk.Chunk {
   266  	data := make([]byte, chunk.DefaultSize)
   267  	rand.Read(data)
   268  	key := make([]byte, 32)
   269  	rand.Read(key)
   270  	return chunk.NewChunk(key, data)
   271  }
   272  
   273  // TestGenerateTestRandomChunk validates that
   274  // generateTestRandomChunk returns random data by comparing
   275  // two generated chunks.
   276  func TestGenerateTestRandomChunk(t *testing.T) {
   277  	c1 := generateTestRandomChunk()
   278  	c2 := generateTestRandomChunk()
   279  	addrLen := len(c1.Address())
   280  	if addrLen != 32 {
   281  		t.Errorf("first chunk address length %v, want %v", addrLen, 32)
   282  	}
   283  	dataLen := len(c1.Data())
   284  	if dataLen != chunk.DefaultSize {
   285  		t.Errorf("first chunk data length %v, want %v", dataLen, chunk.DefaultSize)
   286  	}
   287  	addrLen = len(c2.Address())
   288  	if addrLen != 32 {
   289  		t.Errorf("second chunk address length %v, want %v", addrLen, 32)
   290  	}
   291  	dataLen = len(c2.Data())
   292  	if dataLen != chunk.DefaultSize {
   293  		t.Errorf("second chunk data length %v, want %v", dataLen, chunk.DefaultSize)
   294  	}
   295  	if bytes.Equal(c1.Address(), c2.Address()) {
   296  		t.Error("fake chunks addresses do not differ")
   297  	}
   298  	if bytes.Equal(c1.Data(), c2.Data()) {
   299  		t.Error("fake chunks data bytes do not differ")
   300  	}
   301  }
   302  
   303  // newRetrieveIndexesTest returns a test function that validates if the right
   304  // chunk values are in the retrieval indexes.
   305  func newRetrieveIndexesTest(db *DB, chunk chunk.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) {
   306  	return func(t *testing.T) {
   307  		item, err := db.retrievalDataIndex.Get(addressToItem(chunk.Address()))
   308  		if err != nil {
   309  			t.Fatal(err)
   310  		}
   311  		validateItem(t, item, chunk.Address(), chunk.Data(), storeTimestamp, 0)
   312  
   313  		// access index should not be set
   314  		wantErr := leveldb.ErrNotFound
   315  		item, err = db.retrievalAccessIndex.Get(addressToItem(chunk.Address()))
   316  		if err != wantErr {
   317  			t.Errorf("got error %v, want %v", err, wantErr)
   318  		}
   319  	}
   320  }
   321  
   322  // newRetrieveIndexesTestWithAccess returns a test function that validates if the right
   323  // chunk values are in the retrieval indexes when access time must be stored.
   324  func newRetrieveIndexesTestWithAccess(db *DB, chunk chunk.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) {
   325  	return func(t *testing.T) {
   326  		item, err := db.retrievalDataIndex.Get(addressToItem(chunk.Address()))
   327  		if err != nil {
   328  			t.Fatal(err)
   329  		}
   330  		validateItem(t, item, chunk.Address(), chunk.Data(), storeTimestamp, 0)
   331  
   332  		if accessTimestamp > 0 {
   333  			item, err = db.retrievalAccessIndex.Get(addressToItem(chunk.Address()))
   334  			if err != nil {
   335  				t.Fatal(err)
   336  			}
   337  			validateItem(t, item, chunk.Address(), nil, 0, accessTimestamp)
   338  		}
   339  	}
   340  }
   341  
   342  // newPullIndexTest returns a test function that validates if the right
   343  // chunk values are in the pull index.
   344  func newPullIndexTest(db *DB, chunk chunk.Chunk, storeTimestamp int64, wantError error) func(t *testing.T) {
   345  	return func(t *testing.T) {
   346  		item, err := db.pullIndex.Get(shed.Item{
   347  			Address:        chunk.Address(),
   348  			StoreTimestamp: storeTimestamp,
   349  		})
   350  		if err != wantError {
   351  			t.Errorf("got error %v, want %v", err, wantError)
   352  		}
   353  		if err == nil {
   354  			validateItem(t, item, chunk.Address(), nil, storeTimestamp, 0)
   355  		}
   356  	}
   357  }
   358  
   359  // newPushIndexTest returns a test function that validates if the right
   360  // chunk values are in the push index.
   361  func newPushIndexTest(db *DB, chunk chunk.Chunk, storeTimestamp int64, wantError error) func(t *testing.T) {
   362  	return func(t *testing.T) {
   363  		item, err := db.pushIndex.Get(shed.Item{
   364  			Address:        chunk.Address(),
   365  			StoreTimestamp: storeTimestamp,
   366  		})
   367  		if err != wantError {
   368  			t.Errorf("got error %v, want %v", err, wantError)
   369  		}
   370  		if err == nil {
   371  			validateItem(t, item, chunk.Address(), nil, storeTimestamp, 0)
   372  		}
   373  	}
   374  }
   375  
   376  // newGCIndexTest returns a test function that validates if the right
   377  // chunk values are in the push index.
   378  func newGCIndexTest(db *DB, chunk chunk.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) {
   379  	return func(t *testing.T) {
   380  		item, err := db.gcIndex.Get(shed.Item{
   381  			Address:         chunk.Address(),
   382  			StoreTimestamp:  storeTimestamp,
   383  			AccessTimestamp: accessTimestamp,
   384  		})
   385  		if err != nil {
   386  			t.Fatal(err)
   387  		}
   388  		validateItem(t, item, chunk.Address(), nil, storeTimestamp, accessTimestamp)
   389  	}
   390  }
   391  
   392  // newItemsCountTest returns a test function that validates if
   393  // an index contains expected number of key/value pairs.
   394  func newItemsCountTest(i shed.Index, want int) func(t *testing.T) {
   395  	return func(t *testing.T) {
   396  		var c int
   397  		err := i.Iterate(func(item shed.Item) (stop bool, err error) {
   398  			c++
   399  			return
   400  		}, nil)
   401  		if err != nil {
   402  			t.Fatal(err)
   403  		}
   404  		if c != want {
   405  			t.Errorf("got %v items in index, want %v", c, want)
   406  		}
   407  	}
   408  }
   409  
   410  // newIndexGCSizeTest retruns a test function that validates if DB.gcSize
   411  // value is the same as the number of items in DB.gcIndex.
   412  func newIndexGCSizeTest(db *DB) func(t *testing.T) {
   413  	return func(t *testing.T) {
   414  		var want int64
   415  		err := db.gcIndex.Iterate(func(item shed.Item) (stop bool, err error) {
   416  			want++
   417  			return
   418  		}, nil)
   419  		if err != nil {
   420  			t.Fatal(err)
   421  		}
   422  		got := db.getGCSize()
   423  		if got != want {
   424  			t.Errorf("got gc size %v, want %v", got, want)
   425  		}
   426  	}
   427  }
   428  
   429  // testIndexChunk embeds storageChunk with additional data that is stored
   430  // in database. It is used for index values validations.
   431  type testIndexChunk struct {
   432  	chunk.Chunk
   433  	storeTimestamp int64
   434  }
   435  
   436  // testItemsOrder tests the order of chunks in the index. If sortFunc is not nil,
   437  // chunks will be sorted with it before validation.
   438  func testItemsOrder(t *testing.T, i shed.Index, chunks []testIndexChunk, sortFunc func(i, j int) (less bool)) {
   439  	newItemsCountTest(i, len(chunks))(t)
   440  
   441  	if sortFunc != nil {
   442  		sort.Slice(chunks, sortFunc)
   443  	}
   444  
   445  	var cursor int
   446  	err := i.Iterate(func(item shed.Item) (stop bool, err error) {
   447  		want := chunks[cursor].Address()
   448  		got := item.Address
   449  		if !bytes.Equal(got, want) {
   450  			return true, fmt.Errorf("got address %x at position %v, want %x", got, cursor, want)
   451  		}
   452  		cursor++
   453  		return false, nil
   454  	}, nil)
   455  	if err != nil {
   456  		t.Fatal(err)
   457  	}
   458  }
   459  
   460  // validateItem is a helper function that checks Item values.
   461  func validateItem(t *testing.T, item shed.Item, address, data []byte, storeTimestamp, accessTimestamp int64) {
   462  	t.Helper()
   463  
   464  	if !bytes.Equal(item.Address, address) {
   465  		t.Errorf("got item address %x, want %x", item.Address, address)
   466  	}
   467  	if !bytes.Equal(item.Data, data) {
   468  		t.Errorf("got item data %x, want %x", item.Data, data)
   469  	}
   470  	if item.StoreTimestamp != storeTimestamp {
   471  		t.Errorf("got item store timestamp %v, want %v", item.StoreTimestamp, storeTimestamp)
   472  	}
   473  	if item.AccessTimestamp != accessTimestamp {
   474  		t.Errorf("got item access timestamp %v, want %v", item.AccessTimestamp, accessTimestamp)
   475  	}
   476  }
   477  
   478  // setNow replaces now function and
   479  // returns a function that will reset it to the
   480  // value before the change.
   481  func setNow(f func() int64) (reset func()) {
   482  	current := now
   483  	reset = func() { now = current }
   484  	now = f
   485  	return reset
   486  }
   487  
   488  // TestSetNow tests if setNow function changes now function
   489  // correctly and if its reset function resets the original function.
   490  func TestSetNow(t *testing.T) {
   491  	// set the current function after the test finishes
   492  	defer func(f func() int64) { now = f }(now)
   493  
   494  	// expected value for the unchanged function
   495  	var original int64 = 1
   496  	// expected value for the changed function
   497  	var changed int64 = 2
   498  
   499  	// define the original (unchanged) functions
   500  	now = func() int64 {
   501  		return original
   502  	}
   503  
   504  	// get the time
   505  	got := now()
   506  
   507  	// test if got variable is set correctly
   508  	if got != original {
   509  		t.Errorf("got now value %v, want %v", got, original)
   510  	}
   511  
   512  	// set the new function
   513  	reset := setNow(func() int64 {
   514  		return changed
   515  	})
   516  
   517  	// get the time
   518  	got = now()
   519  
   520  	// test if got variable is set correctly to changed value
   521  	if got != changed {
   522  		t.Errorf("got hook value %v, want %v", got, changed)
   523  	}
   524  
   525  	// set the function to the original one
   526  	reset()
   527  
   528  	// get the time
   529  	got = now()
   530  
   531  	// test if got variable is set correctly to original value
   532  	if got != original {
   533  		t.Errorf("got hook value %v, want %v", got, original)
   534  	}
   535  }