github.com/daeglee/go-ethereum@v0.0.0-20190504220456-cad3e8d18e9b/swarm/storage/localstore/gc_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  	"io/ioutil"
    21  	"math/rand"
    22  	"os"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/swarm/chunk"
    27  )
    28  
    29  // TestDB_collectGarbageWorker tests garbage collection runs
    30  // by uploading and syncing a number of chunks.
    31  func TestDB_collectGarbageWorker(t *testing.T) {
    32  	testDB_collectGarbageWorker(t)
    33  }
    34  
    35  // TestDB_collectGarbageWorker_multipleBatches tests garbage
    36  // collection runs by uploading and syncing a number of
    37  // chunks by having multiple smaller batches.
    38  func TestDB_collectGarbageWorker_multipleBatches(t *testing.T) {
    39  	// lower the maximal number of chunks in a single
    40  	// gc batch to ensure multiple batches.
    41  	defer func(s uint64) { gcBatchSize = s }(gcBatchSize)
    42  	gcBatchSize = 2
    43  
    44  	testDB_collectGarbageWorker(t)
    45  }
    46  
    47  // testDB_collectGarbageWorker is a helper test function to test
    48  // garbage collection runs by uploading and syncing a number of chunks.
    49  func testDB_collectGarbageWorker(t *testing.T) {
    50  	t.Helper()
    51  
    52  	chunkCount := 150
    53  
    54  	db, cleanupFunc := newTestDB(t, &Options{
    55  		Capacity: 100,
    56  	})
    57  	testHookCollectGarbageChan := make(chan uint64)
    58  	defer setTestHookCollectGarbage(func(collectedCount uint64) {
    59  		select {
    60  		case testHookCollectGarbageChan <- collectedCount:
    61  		case <-db.close:
    62  		}
    63  	})()
    64  	defer cleanupFunc()
    65  
    66  	uploader := db.NewPutter(ModePutUpload)
    67  	syncer := db.NewSetter(ModeSetSync)
    68  
    69  	addrs := make([]chunk.Address, 0)
    70  
    71  	// upload random chunks
    72  	for i := 0; i < chunkCount; i++ {
    73  		chunk := generateTestRandomChunk()
    74  
    75  		err := uploader.Put(chunk)
    76  		if err != nil {
    77  			t.Fatal(err)
    78  		}
    79  
    80  		err = syncer.Set(chunk.Address())
    81  		if err != nil {
    82  			t.Fatal(err)
    83  		}
    84  
    85  		addrs = append(addrs, chunk.Address())
    86  	}
    87  
    88  	gcTarget := db.gcTarget()
    89  
    90  	for {
    91  		select {
    92  		case <-testHookCollectGarbageChan:
    93  		case <-time.After(10 * time.Second):
    94  			t.Error("collect garbage timeout")
    95  		}
    96  		gcSize, err := db.gcSize.Get()
    97  		if err != nil {
    98  			t.Fatal(err)
    99  		}
   100  		if gcSize == gcTarget {
   101  			break
   102  		}
   103  	}
   104  
   105  	t.Run("pull index count", newItemsCountTest(db.pullIndex, int(gcTarget)))
   106  
   107  	t.Run("gc index count", newItemsCountTest(db.gcIndex, int(gcTarget)))
   108  
   109  	t.Run("gc size", newIndexGCSizeTest(db))
   110  
   111  	// the first synced chunk should be removed
   112  	t.Run("get the first synced chunk", func(t *testing.T) {
   113  		_, err := db.NewGetter(ModeGetRequest).Get(addrs[0])
   114  		if err != chunk.ErrChunkNotFound {
   115  			t.Errorf("got error %v, want %v", err, chunk.ErrChunkNotFound)
   116  		}
   117  	})
   118  
   119  	// last synced chunk should not be removed
   120  	t.Run("get most recent synced chunk", func(t *testing.T) {
   121  		_, err := db.NewGetter(ModeGetRequest).Get(addrs[len(addrs)-1])
   122  		if err != nil {
   123  			t.Fatal(err)
   124  		}
   125  	})
   126  }
   127  
   128  // TestDB_collectGarbageWorker_withRequests is a helper test function
   129  // to test garbage collection runs by uploading, syncing and
   130  // requesting a number of chunks.
   131  func TestDB_collectGarbageWorker_withRequests(t *testing.T) {
   132  	db, cleanupFunc := newTestDB(t, &Options{
   133  		Capacity: 100,
   134  	})
   135  	defer cleanupFunc()
   136  
   137  	uploader := db.NewPutter(ModePutUpload)
   138  	syncer := db.NewSetter(ModeSetSync)
   139  
   140  	testHookCollectGarbageChan := make(chan uint64)
   141  	defer setTestHookCollectGarbage(func(collectedCount uint64) {
   142  		testHookCollectGarbageChan <- collectedCount
   143  	})()
   144  
   145  	addrs := make([]chunk.Address, 0)
   146  
   147  	// upload random chunks just up to the capacity
   148  	for i := 0; i < int(db.capacity)-1; i++ {
   149  		chunk := generateTestRandomChunk()
   150  
   151  		err := uploader.Put(chunk)
   152  		if err != nil {
   153  			t.Fatal(err)
   154  		}
   155  
   156  		err = syncer.Set(chunk.Address())
   157  		if err != nil {
   158  			t.Fatal(err)
   159  		}
   160  
   161  		addrs = append(addrs, chunk.Address())
   162  	}
   163  
   164  	// set update gc test hook to signal when
   165  	// update gc goroutine is done by closing
   166  	// testHookUpdateGCChan channel
   167  	testHookUpdateGCChan := make(chan struct{})
   168  	resetTestHookUpdateGC := setTestHookUpdateGC(func() {
   169  		close(testHookUpdateGCChan)
   170  	})
   171  
   172  	// request the latest synced chunk
   173  	// to prioritize it in the gc index
   174  	// not to be collected
   175  	_, err := db.NewGetter(ModeGetRequest).Get(addrs[0])
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	// wait for update gc goroutine to finish for garbage
   181  	// collector to be correctly triggered after the last upload
   182  	select {
   183  	case <-testHookUpdateGCChan:
   184  	case <-time.After(10 * time.Second):
   185  		t.Fatal("updateGC was not called after getting chunk with ModeGetRequest")
   186  	}
   187  
   188  	// no need to wait for update gc hook anymore
   189  	resetTestHookUpdateGC()
   190  
   191  	// upload and sync another chunk to trigger
   192  	// garbage collection
   193  	ch := generateTestRandomChunk()
   194  	err = uploader.Put(ch)
   195  	if err != nil {
   196  		t.Fatal(err)
   197  	}
   198  	err = syncer.Set(ch.Address())
   199  	if err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	addrs = append(addrs, ch.Address())
   203  
   204  	// wait for garbage collection
   205  
   206  	gcTarget := db.gcTarget()
   207  
   208  	var totalCollectedCount uint64
   209  	for {
   210  		select {
   211  		case c := <-testHookCollectGarbageChan:
   212  			totalCollectedCount += c
   213  		case <-time.After(10 * time.Second):
   214  			t.Error("collect garbage timeout")
   215  		}
   216  		gcSize, err := db.gcSize.Get()
   217  		if err != nil {
   218  			t.Fatal(err)
   219  		}
   220  		if gcSize == gcTarget {
   221  			break
   222  		}
   223  	}
   224  
   225  	wantTotalCollectedCount := uint64(len(addrs)) - gcTarget
   226  	if totalCollectedCount != wantTotalCollectedCount {
   227  		t.Errorf("total collected chunks %v, want %v", totalCollectedCount, wantTotalCollectedCount)
   228  	}
   229  
   230  	t.Run("pull index count", newItemsCountTest(db.pullIndex, int(gcTarget)))
   231  
   232  	t.Run("gc index count", newItemsCountTest(db.gcIndex, int(gcTarget)))
   233  
   234  	t.Run("gc size", newIndexGCSizeTest(db))
   235  
   236  	// requested chunk should not be removed
   237  	t.Run("get requested chunk", func(t *testing.T) {
   238  		_, err := db.NewGetter(ModeGetRequest).Get(addrs[0])
   239  		if err != nil {
   240  			t.Fatal(err)
   241  		}
   242  	})
   243  
   244  	// the second synced chunk should be removed
   245  	t.Run("get gc-ed chunk", func(t *testing.T) {
   246  		_, err := db.NewGetter(ModeGetRequest).Get(addrs[1])
   247  		if err != chunk.ErrChunkNotFound {
   248  			t.Errorf("got error %v, want %v", err, chunk.ErrChunkNotFound)
   249  		}
   250  	})
   251  
   252  	// last synced chunk should not be removed
   253  	t.Run("get most recent synced chunk", func(t *testing.T) {
   254  		_, err := db.NewGetter(ModeGetRequest).Get(addrs[len(addrs)-1])
   255  		if err != nil {
   256  			t.Fatal(err)
   257  		}
   258  	})
   259  }
   260  
   261  // TestDB_gcSize checks if gcSize has a correct value after
   262  // database is initialized with existing data.
   263  func TestDB_gcSize(t *testing.T) {
   264  	dir, err := ioutil.TempDir("", "localstore-stored-gc-size")
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  	defer os.RemoveAll(dir)
   269  	baseKey := make([]byte, 32)
   270  	if _, err := rand.Read(baseKey); err != nil {
   271  		t.Fatal(err)
   272  	}
   273  	db, err := New(dir, baseKey, nil)
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  
   278  	uploader := db.NewPutter(ModePutUpload)
   279  	syncer := db.NewSetter(ModeSetSync)
   280  
   281  	count := 100
   282  
   283  	for i := 0; i < count; i++ {
   284  		chunk := generateTestRandomChunk()
   285  
   286  		err := uploader.Put(chunk)
   287  		if err != nil {
   288  			t.Fatal(err)
   289  		}
   290  
   291  		err = syncer.Set(chunk.Address())
   292  		if err != nil {
   293  			t.Fatal(err)
   294  		}
   295  	}
   296  
   297  	if err := db.Close(); err != nil {
   298  		t.Fatal(err)
   299  	}
   300  
   301  	db, err = New(dir, baseKey, nil)
   302  	if err != nil {
   303  		t.Fatal(err)
   304  	}
   305  	defer db.Close()
   306  
   307  	t.Run("gc index size", newIndexGCSizeTest(db))
   308  }
   309  
   310  // setTestHookCollectGarbage sets testHookCollectGarbage and
   311  // returns a function that will reset it to the
   312  // value before the change.
   313  func setTestHookCollectGarbage(h func(collectedCount uint64)) (reset func()) {
   314  	current := testHookCollectGarbage
   315  	reset = func() { testHookCollectGarbage = current }
   316  	testHookCollectGarbage = h
   317  	return reset
   318  }
   319  
   320  // TestSetTestHookCollectGarbage tests if setTestHookCollectGarbage changes
   321  // testHookCollectGarbage function correctly and if its reset function
   322  // resets the original function.
   323  func TestSetTestHookCollectGarbage(t *testing.T) {
   324  	// Set the current function after the test finishes.
   325  	defer func(h func(collectedCount uint64)) { testHookCollectGarbage = h }(testHookCollectGarbage)
   326  
   327  	// expected value for the unchanged function
   328  	original := 1
   329  	// expected value for the changed function
   330  	changed := 2
   331  
   332  	// this variable will be set with two different functions
   333  	var got int
   334  
   335  	// define the original (unchanged) functions
   336  	testHookCollectGarbage = func(_ uint64) {
   337  		got = original
   338  	}
   339  
   340  	// set got variable
   341  	testHookCollectGarbage(0)
   342  
   343  	// test if got variable is set correctly
   344  	if got != original {
   345  		t.Errorf("got hook value %v, want %v", got, original)
   346  	}
   347  
   348  	// set the new function
   349  	reset := setTestHookCollectGarbage(func(_ uint64) {
   350  		got = changed
   351  	})
   352  
   353  	// set got variable
   354  	testHookCollectGarbage(0)
   355  
   356  	// test if got variable is set correctly to changed value
   357  	if got != changed {
   358  		t.Errorf("got hook value %v, want %v", got, changed)
   359  	}
   360  
   361  	// set the function to the original one
   362  	reset()
   363  
   364  	// set got variable
   365  	testHookCollectGarbage(0)
   366  
   367  	// test if got variable is set correctly to original value
   368  	if got != original {
   369  		t.Errorf("got hook value %v, want %v", got, original)
   370  	}
   371  }