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