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 }