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 }