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 }