diff --git a/swarm/storage/feed/lookup/lookup_test.go b/swarm/storage/feed/lookup/lookup_test.go index ac0d1ddb84..3c63c8929e 100644 --- a/swarm/storage/feed/lookup/lookup_test.go +++ b/swarm/storage/feed/lookup/lookup_test.go @@ -41,10 +41,9 @@ var algorithms = []AlgorithmInfo{ const enablePrintMetrics = true // set to true to display algorithm benchmarking stats func printMetric(metric string, reads int32, elapsed time.Duration) { - if !enablePrintMetrics { - return + if enablePrintMetrics { + fmt.Printf("metric=%s, readcount=%d, elapsed=%s\n", metric, reads, elapsed) } - fmt.Printf("metric=%s, readcount=%d, elapsed=%s\n", metric, reads, elapsed) } type Data struct { @@ -97,11 +96,15 @@ func makeReadFunc(store Store, counter *int32) lookup.ReadFunc { } } +// TestLookup verifies if the last update and intermediates are +// found and if that same last update is found faster if a hint is given func TestLookup(t *testing.T) { + // ### 1.- Initialize stopwatch time sim stopwatch := NewStopwatch(50 * time.Millisecond) lookup.TimeAfter = stopwatch.TimeAfter() defer stopwatch.Stop() + // ### 2.- Setup mock storage and generate updates store := make(Store) var readCount int32 = 0 readFunc := makeReadFunc(store, &readCount) @@ -121,79 +124,84 @@ func TestLookup(t *testing.T) { lastData = &data } + // ### 3.- Test all algorithms for _, algo := range algorithms { t.Run(algo.Name, func(t *testing.T) { readCount = 0 - stopwatch.Reset() - stopwatch.Run() - // try to get the last value - value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } - timeElapsedWithoutHint := stopwatch.Elapsed() + // ### 3.1.- Test how long it takes to find the last update without a hint: + timeElapsedWithoutHint := stopwatch.Measure(func() { + + // try to get the last value + value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc) + if err != nil { + t.Fatal(err) + } + if value != lastData { + t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) + } + + }) printMetric("SIMPLE READ", readCount, timeElapsedWithoutHint) - if value != lastData { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) - } - // reset the read count for the next test readCount = 0 - stopwatch.Reset() - stopwatch.Run() - // Provide a hint to get a faster lookup. In particular, we give the exact location of the last update - value, err = algo.Lookup(context.Background(), now, epoch, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } + + // ### 3.2.- Test how long it takes to find the last update *with* a hint. + // it should take less time! + timeElapsed := stopwatch.Measure(func() { + // Provide a hint to get a faster lookup. In particular, we give the exact location of the last update + value, err := algo.Lookup(context.Background(), now, epoch, readFunc) + if err != nil { + t.Fatal(err) + } + if value != lastData { + t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) + } + }) printMetric("WITH HINT", readCount, stopwatch.Elapsed()) - if value != lastData { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) + if timeElapsed > timeElapsedWithoutHint { + t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, timeElapsed) } - if stopwatch.Elapsed() > timeElapsedWithoutHint { - t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, stopwatch.Elapsed()) - } - - // try to get an intermediate value - // if we look for a value in now - Year*3 + 6*Month, we should get that value - // Since the "payload" is the timestamp itself, we can check this. - - expectedTime := now - Year*3 + 6*Month // reset the read count for the next test readCount = 0 - stopwatch.Reset() - stopwatch.Run() - value, err = algo.Lookup(context.Background(), expectedTime, lookup.NoClue, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } - printMetric("INTERMEDIATE READ", readCount, stopwatch.Elapsed()) - data, ok := value.(*Data) + // ### 3.3.- try to get an intermediate value + // if we look for a value in, e.g., now - Year*3 + 6*Month, we should get that value + // Since the "payload" is the timestamp itself, we can check this. + expectedTime := now - Year*3 + 6*Month + timeElapsed = stopwatch.Measure(func() { + value, err := algo.Lookup(context.Background(), expectedTime, lookup.NoClue, readFunc) + if err != nil { + t.Fatal(err) + } - if !ok { - t.Fatal("Expected value to contain data") - } + data, ok := value.(*Data) - if data.Time != expectedTime { - t.Fatalf("Expected value timestamp to be %d, got %d", data.Time, expectedTime) - } + if !ok { + t.Fatal("Expected value to contain data") + } + + if data.Time != expectedTime { + t.Fatalf("Expected value timestamp to be %d, got %d", data.Time, expectedTime) + } + }) + printMetric("INTERMEDIATE READ", readCount, timeElapsed) }) } } +// TestOneUpdateAt0 checks if the lookup algorithm can return an update that +// is precisely set at t=0 func TestOneUpdateAt0(t *testing.T) { + // ### 1.- Initialize stopwatch time sim stopwatch := NewStopwatch(50 * time.Millisecond) lookup.TimeAfter = stopwatch.TimeAfter() defer stopwatch.Stop() + // ### 2.- Setup mock storage and generate updates store := make(Store) var readCount int32 = 0 @@ -205,32 +213,34 @@ func TestOneUpdateAt0(t *testing.T) { Payload: 79, Time: 0, } - update(store, epoch, 0, &data) + update(store, epoch, 0, &data) //place 1 update in t=0 + // ### 3.- Test all algorithms for _, algo := range algorithms { - stopwatch.Reset() t.Run(algo.Name, func(t *testing.T) { readCount = 0 - stopwatch.Run() - value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } - if value != &data { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", data, value) - } - printMetric("SIMPLE", readCount, stopwatch.Elapsed()) + timeElapsed := stopwatch.Measure(func() { + value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc) + if err != nil { + t.Fatal(err) + } + if value != &data { + t.Fatalf("Expected lookup to return the last written value: %v. Got %v", data, value) + } + }) + printMetric("SIMPLE", readCount, timeElapsed) }) } } -// Tests the update is found even when a bad hint is given +// TestBadHint tests if the update is found even when a bad hint is given func TestBadHint(t *testing.T) { + // ### 1.- Initialize stopwatch time sim stopwatch := NewStopwatch(50 * time.Millisecond) lookup.TimeAfter = stopwatch.TimeAfter() defer stopwatch.Stop() + // ### 2.- Setup mock storage and generate updates store := make(Store) var readCount int32 = 0 @@ -252,30 +262,32 @@ func TestBadHint(t *testing.T) { Time: 1200000000, } + // ### 3.- Test all algorithms for _, algo := range algorithms { - stopwatch.Reset() t.Run(algo.Name, func(t *testing.T) { readCount = 0 - stopwatch.Run() - value, err := algo.Lookup(context.Background(), now, badHint, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } - if value != &data { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", data, value) - } - printMetric("SIMPLE", readCount, stopwatch.Elapsed()) + timeElapsed := stopwatch.Measure(func() { + value, err := algo.Lookup(context.Background(), now, badHint, readFunc) + if err != nil { + t.Fatal(err) + } + if value != &data { + t.Fatalf("Expected lookup to return the last written value: %v. Got %v", data, value) + } + }) + printMetric("SIMPLE", readCount, timeElapsed) }) } } -// Tests whether the update is found when the bad hint is exactly below the last update +// TestBadHintNextToUpdate checks whether the update is found when the bad hint is exactly below the last update func TestBadHintNextToUpdate(t *testing.T) { + // ### 1.- Initialize stopwatch time sim stopwatch := NewStopwatch(50 * time.Millisecond) lookup.TimeAfter = stopwatch.TimeAfter() defer stopwatch.Stop() + // ### 2.- Setup mock storage and generate updates store := make(Store) var readCount int32 = 0 @@ -314,58 +326,61 @@ func TestBadHintNextToUpdate(t *testing.T) { Level: 20, Time: 1200000005, } + + // ### 3.- Test all algorithms for _, algo := range algorithms { - stopwatch.Reset() t.Run(algo.Name, func(t *testing.T) { readCount = 0 - stopwatch.Run() - value, err := algo.Lookup(context.Background(), now, badHint, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } - if value != last { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", last, value) - } - printMetric("SIMPLE", readCount, stopwatch.Elapsed()) + timeElapsed := stopwatch.Measure(func() { + value, err := algo.Lookup(context.Background(), now, badHint, readFunc) + if err != nil { + t.Fatal(err) + } + if value != last { + t.Fatalf("Expected lookup to return the last written value: %v. Got %v", last, value) + } + }) + printMetric("SIMPLE", readCount, timeElapsed) }) } } +// TestContextCancellation checks whether a lookup can be cancelled func TestContextCancellation(t *testing.T) { - readFunc := func(ctx context.Context, epoch lookup.Epoch, now uint64) (interface{}, error) { - <-ctx.Done() - return nil, ctx.Err() - } - + // ### 1.- Test all algorithms for _, algo := range algorithms { t.Run(algo.Name, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + // ### 2.1.- Test a simple cancel of an always blocking read function + readFunc := func(ctx context.Context, epoch lookup.Epoch, now uint64) (interface{}, error) { + <-ctx.Done() + return nil, ctx.Err() + } + ctx, cancel := context.WithCancel(context.Background()) errc := make(chan error) go func() { - _, err := lookup.Lookup(ctx, 1200000000, lookup.NoClue, readFunc) + _, err := algo.Lookup(ctx, 1200000000, lookup.NoClue, readFunc) errc <- err }() - cancel() + cancel() //actually cancel the lookup if err := <-errc; err != context.Canceled { t.Fatalf("Expected lookup to return a context Cancelled error, got %v", err) } - // text context cancellation during hint lookup: + // ### 2.2.- Test context cancellation during hint lookup: ctx, cancel = context.WithCancel(context.Background()) errc = make(chan error) someHint := lookup.Epoch{ Level: 25, Time: 300, } - + // put up a read function that gets cancelled only on hint lookup readFunc = func(ctx context.Context, epoch lookup.Epoch, now uint64) (interface{}, error) { if epoch == someHint { go cancel() @@ -388,34 +403,39 @@ func TestContextCancellation(t *testing.T) { } +// TestLookupFail makes sure the lookup function fails on a timely manner +// when there are no updates at all func TestLookupFail(t *testing.T) { + // ### 1.- Initialize stopwatch time sim stopwatch := NewStopwatch(50 * time.Millisecond) lookup.TimeAfter = stopwatch.TimeAfter() defer stopwatch.Stop() + // ### 2.- Setup mock storage, without adding updates + // don't write anything and try to look up. + // we're testing we don't get stuck in a loop and that the lookup + // function converges in a timely fashion + store := make(Store) var readCount int32 = 0 readFunc := makeReadFunc(store, &readCount) now := uint64(1533903729) + // ### 3.- Test all algorithms for _, algo := range algorithms { - stopwatch.Reset() t.Run(algo.Name, func(t *testing.T) { readCount = 0 - stopwatch.Run() - // don't write anything and try to look up. - // we're testing we don't get stuck in a loop - - value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } - if value != nil { - t.Fatal("Expected value to be nil, since the update should've failed") - } + stopwatch.Measure(func() { + value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc) + if err != nil { + t.Fatal(err) + } + if value != nil { + t.Fatal("Expected value to be nil, since the update should've failed") + } + }) printMetric("SIMPLE", readCount, stopwatch.Elapsed()) }) @@ -423,17 +443,19 @@ func TestLookupFail(t *testing.T) { } func TestHighFreqUpdates(t *testing.T) { + // ### 1.- Initialize stopwatch time sim stopwatch := NewStopwatch(50 * time.Millisecond) lookup.TimeAfter = stopwatch.TimeAfter() defer stopwatch.Stop() + // ### 2.- Setup mock storage and add one update per second + // for the last 1000 seconds: store := make(Store) var readCount int32 = 0 readFunc := makeReadFunc(store, &readCount) now := uint64(1533903729) - // write an update every second for the last 1000 seconds var epoch lookup.Epoch var lastData *Data @@ -447,78 +469,84 @@ func TestHighFreqUpdates(t *testing.T) { lastData = &data } + // ### 3.- Test all algorithms for _, algo := range algorithms { - stopwatch.Reset() t.Run(algo.Name, func(t *testing.T) { readCount = 0 - stopwatch.Run() - value, err := algo.Lookup(context.Background(), lastData.Time, lookup.NoClue, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } + // ### 3.1.- Test how long it takes to find the last update without a hint: + timeElapsedWithoutHint := stopwatch.Measure(func() { + value, err := algo.Lookup(context.Background(), lastData.Time, lookup.NoClue, readFunc) + stopwatch.Stop() + if err != nil { + t.Fatal(err) + } - if value != lastData { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) - } - timeElapsedWithoutHint := stopwatch.Elapsed() + if value != lastData { + t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) + } + }) printMetric("SIMPLE", readCount, timeElapsedWithoutHint) // reset the read count for the next test readCount = 0 - stopwatch.Reset() - stopwatch.Run() - // Provide a hint to get a faster lookup. In particular, we give the exact location of the last update - value, err = algo.Lookup(context.Background(), now, epoch, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } - if value != lastData { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) - } - - if stopwatch.Elapsed() > timeElapsedWithoutHint { - t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, stopwatch.Elapsed()) - } - printMetric("WITH HINT", readCount, stopwatch.Elapsed()) - - readCount = 0 - stopwatch.Reset() - stopwatch.Run() - for i := uint64(0); i <= 10; i++ { - T := uint64(now - 1000 + i) // update every second for the last 1000 seconds - value, err := algo.Lookup(context.Background(), T, lookup.NoClue, readFunc) + // ### 3.2.- Now test how long it takes to find the last update *with* a hint, + // it should take less time! + timeElapsed := stopwatch.Measure(func() { + // Provide a hint to get a faster lookup. In particular, we give the exact location of the last update + value, err := algo.Lookup(context.Background(), now, epoch, readFunc) + stopwatch.Stop() if err != nil { t.Fatal(err) } - data, _ := value.(*Data) - if data == nil { - t.Fatalf("Expected lookup to return %d, got nil", T) - } - if data.Payload != T { - t.Fatalf("Expected lookup to return %d, got %d", T, data.Time) + + if value != lastData { + t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) } + + }) + if timeElapsed > timeElapsedWithoutHint { + t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, timeElapsed) } - stopwatch.Stop() - printMetric("MULTIPLE", readCount, stopwatch.Elapsed()) + printMetric("WITH HINT", readCount, timeElapsed) + + readCount = 0 + + // ### 3.3.- Test multiple lookups at different intervals + timeElapsed = stopwatch.Measure(func() { + for i := uint64(0); i <= 10; i++ { + T := uint64(now - 1000 + i) + value, err := algo.Lookup(context.Background(), T, lookup.NoClue, readFunc) + if err != nil { + t.Fatal(err) + } + data, _ := value.(*Data) + if data == nil { + t.Fatalf("Expected lookup to return %d, got nil", T) + } + if data.Payload != T { + t.Fatalf("Expected lookup to return %d, got %d", T, data.Time) + } + } + }) + printMetric("MULTIPLE", readCount, timeElapsed) }) } } func TestSparseUpdates(t *testing.T) { + // ### 1.- Initialize stopwatch time sim stopwatch := NewStopwatch(50 * time.Millisecond) lookup.TimeAfter = stopwatch.TimeAfter() defer stopwatch.Stop() + // ### 2.- Setup mock storage and write an update sparsely, + // every 5 years 3 times starting in Jan 1st 1970 and then silence store := make(Store) var readCount int32 = 0 readFunc := makeReadFunc(store, &readCount) - // write an update every 5 years 3 times starting in Jan 1st 1970 and then silence - now := uint64(1533799046) var epoch lookup.Epoch @@ -533,43 +561,43 @@ func TestSparseUpdates(t *testing.T) { lastData = &data } + // ### 3.- Test all algorithms for _, algo := range algorithms { - stopwatch.Reset() t.Run(algo.Name, func(t *testing.T) { readCount = 0 - stopwatch.Run() - // try to get the last value - value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } + // ### 3.1.- Test how long it takes to find the last update without a hint: + timeElapsedWithoutHint := stopwatch.Measure(func() { + value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc) + stopwatch.Stop() + if err != nil { + t.Fatal(err) + } - if value != lastData { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) - } - - timeElapsedWithoutHint := stopwatch.Elapsed() + if value != lastData { + t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) + } + }) printMetric("SIMPLE", readCount, timeElapsedWithoutHint) // reset the read count for the next test readCount = 0 - stopwatch.Reset() - stopwatch.Run() - // Provide a hint to get a faster lookup. In particular, we give the exact location of the last update - value, err = algo.Lookup(context.Background(), now, epoch, readFunc) - stopwatch.Stop() - if err != nil { - t.Fatal(err) - } - if value != lastData { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) - } + // ### 3.2.- Now test how long it takes to find the last update *with* a hint, + // it should take less time! + timeElapsed := stopwatch.Measure(func() { + value, err := algo.Lookup(context.Background(), now, epoch, readFunc) + stopwatch.Stop() + if err != nil { + t.Fatal(err) + } - if stopwatch.Elapsed() > timeElapsedWithoutHint { - t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, stopwatch.Elapsed()) + if value != lastData { + t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) + } + }) + if timeElapsed > timeElapsedWithoutHint { + t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, timeElapsed) } printMetric("WITH HINT", readCount, stopwatch.Elapsed()) @@ -589,6 +617,7 @@ type testG struct { // test cases var testGetNextLevelCases = []testG{{e: lookup.Epoch{Time: 989875233, Level: 12}, n: 989875233, x: 11}, {e: lookup.Epoch{Time: 995807650, Level: 18}, n: 995598156, x: 19}, {e: lookup.Epoch{Time: 969167082, Level: 0}, n: 968990357, x: 18}, {e: lookup.Epoch{Time: 993087628, Level: 14}, n: 992987044, x: 20}, {e: lookup.Epoch{Time: 963364631, Level: 20}, n: 963364630, x: 19}, {e: lookup.Epoch{Time: 963497510, Level: 16}, n: 963370732, x: 18}, {e: lookup.Epoch{Time: 955421349, Level: 22}, n: 955421348, x: 21}, {e: lookup.Epoch{Time: 968220379, Level: 15}, n: 968220378, x: 14}, {e: lookup.Epoch{Time: 939129014, Level: 6}, n: 939128771, x: 11}, {e: lookup.Epoch{Time: 907847903, Level: 6}, n: 907791833, x: 18}, {e: lookup.Epoch{Time: 910835564, Level: 15}, n: 910835564, x: 14}, {e: lookup.Epoch{Time: 913578333, Level: 22}, n: 881808431, x: 25}, {e: lookup.Epoch{Time: 895818460, Level: 3}, n: 895818132, x: 9}, {e: lookup.Epoch{Time: 903843025, Level: 24}, n: 895609561, x: 23}, {e: lookup.Epoch{Time: 877889433, Level: 13}, n: 877877093, x: 15}, {e: lookup.Epoch{Time: 901450396, Level: 10}, n: 901450058, x: 9}, {e: lookup.Epoch{Time: 925179910, Level: 3}, n: 925168393, x: 16}, {e: lookup.Epoch{Time: 913485477, Level: 21}, n: 913485476, x: 20}, {e: lookup.Epoch{Time: 924462991, Level: 18}, n: 924462990, x: 17}, {e: lookup.Epoch{Time: 941175128, Level: 13}, n: 941175127, x: 12}, {e: lookup.Epoch{Time: 920126583, Level: 3}, n: 920100782, x: 19}, {e: lookup.Epoch{Time: 932403200, Level: 9}, n: 932279891, x: 17}, {e: lookup.Epoch{Time: 948284931, Level: 2}, n: 948284921, x: 9}, {e: lookup.Epoch{Time: 953540997, Level: 7}, n: 950547986, x: 22}, {e: lookup.Epoch{Time: 926639837, Level: 18}, n: 918608882, x: 24}, {e: lookup.Epoch{Time: 954637598, Level: 1}, n: 954578761, x: 17}, {e: lookup.Epoch{Time: 943482981, Level: 10}, n: 942924151, x: 19}, {e: lookup.Epoch{Time: 963580771, Level: 7}, n: 963580771, x: 6}, {e: lookup.Epoch{Time: 993744930, Level: 7}, n: 993690858, x: 16}, {e: lookup.Epoch{Time: 1018890213, Level: 12}, n: 1018890212, x: 11}, {e: lookup.Epoch{Time: 1030309411, Level: 2}, n: 1030309227, x: 9}, {e: lookup.Epoch{Time: 1063204997, Level: 20}, n: 1063204996, x: 19}, {e: lookup.Epoch{Time: 1094340832, Level: 6}, n: 1094340633, x: 7}, {e: lookup.Epoch{Time: 1077880597, Level: 10}, n: 1075914292, x: 20}, {e: lookup.Epoch{Time: 1051114957, Level: 18}, n: 1051114957, x: 17}, {e: lookup.Epoch{Time: 1045649701, Level: 22}, n: 1045649700, x: 21}, {e: lookup.Epoch{Time: 1066198885, Level: 14}, n: 1066198884, x: 13}, {e: lookup.Epoch{Time: 1053231952, Level: 1}, n: 1053210845, x: 16}, {e: lookup.Epoch{Time: 1068763404, Level: 14}, n: 1068675428, x: 18}, {e: lookup.Epoch{Time: 1039042173, Level: 15}, n: 1038973110, x: 17}, {e: lookup.Epoch{Time: 1050747636, Level: 6}, n: 1050747364, x: 9}, {e: lookup.Epoch{Time: 1030034434, Level: 23}, n: 1030034433, x: 22}, {e: lookup.Epoch{Time: 1003783425, Level: 18}, n: 1003783424, x: 17}, {e: lookup.Epoch{Time: 988163976, Level: 15}, n: 988084064, x: 17}, {e: lookup.Epoch{Time: 1007222377, Level: 15}, n: 1007222377, x: 14}, {e: lookup.Epoch{Time: 1001211375, Level: 13}, n: 1001208178, x: 14}, {e: lookup.Epoch{Time: 997623199, Level: 8}, n: 997623198, x: 7}, {e: lookup.Epoch{Time: 1026283830, Level: 10}, n: 1006681704, x: 24}, {e: lookup.Epoch{Time: 1019421907, Level: 20}, n: 1019421906, x: 19}, {e: lookup.Epoch{Time: 1043154306, Level: 16}, n: 1043108343, x: 16}, {e: lookup.Epoch{Time: 1075643767, Level: 17}, n: 1075325898, x: 18}, {e: lookup.Epoch{Time: 1043726309, Level: 20}, n: 1043726308, x: 19}, {e: lookup.Epoch{Time: 1056415324, Level: 17}, n: 1056415324, x: 16}, {e: lookup.Epoch{Time: 1088650219, Level: 13}, n: 1088650218, x: 12}, {e: lookup.Epoch{Time: 1088551662, Level: 7}, n: 1088543355, x: 13}, {e: lookup.Epoch{Time: 1069667265, Level: 6}, n: 1069667075, x: 7}, {e: lookup.Epoch{Time: 1079145970, Level: 18}, n: 1079145969, x: 17}, {e: lookup.Epoch{Time: 1083338876, Level: 7}, n: 1083338875, x: 6}, {e: lookup.Epoch{Time: 1051581086, Level: 4}, n: 1051568869, x: 14}, {e: lookup.Epoch{Time: 1028430882, Level: 4}, n: 1028430864, x: 5}, {e: lookup.Epoch{Time: 1057356462, Level: 1}, n: 1057356417, x: 5}, {e: lookup.Epoch{Time: 1033104266, Level: 0}, n: 1033097479, x: 13}, {e: lookup.Epoch{Time: 1031391367, Level: 11}, n: 1031387304, x: 14}, {e: lookup.Epoch{Time: 1049781164, Level: 15}, n: 1049781163, x: 14}, {e: lookup.Epoch{Time: 1027271628, Level: 12}, n: 1027271627, x: 11}, {e: lookup.Epoch{Time: 1057270560, Level: 23}, n: 1057270560, x: 22}, {e: lookup.Epoch{Time: 1047501317, Level: 15}, n: 1047501317, x: 14}, {e: lookup.Epoch{Time: 1058349035, Level: 11}, n: 1045175573, x: 24}, {e: lookup.Epoch{Time: 1057396147, Level: 20}, n: 1057396147, x: 19}, {e: lookup.Epoch{Time: 1048906375, Level: 18}, n: 1039616919, x: 25}, {e: lookup.Epoch{Time: 1074294831, Level: 20}, n: 1074294831, x: 19}, {e: lookup.Epoch{Time: 1088946052, Level: 1}, n: 1088917364, x: 14}, {e: lookup.Epoch{Time: 1112337595, Level: 17}, n: 1111008110, x: 22}, {e: lookup.Epoch{Time: 1099990284, Level: 5}, n: 1099968370, x: 15}, {e: lookup.Epoch{Time: 1087036441, Level: 16}, n: 1053967855, x: 25}, {e: lookup.Epoch{Time: 1069225185, Level: 8}, n: 1069224660, x: 10}, {e: lookup.Epoch{Time: 1057505479, Level: 9}, n: 1057505170, x: 14}, {e: lookup.Epoch{Time: 1072381377, Level: 12}, n: 1065950959, x: 22}, {e: lookup.Epoch{Time: 1093887139, Level: 8}, n: 1093863305, x: 14}, {e: lookup.Epoch{Time: 1082366510, Level: 24}, n: 1082366510, x: 23}, {e: lookup.Epoch{Time: 1103231132, Level: 14}, n: 1102292201, x: 22}, {e: lookup.Epoch{Time: 1094502355, Level: 3}, n: 1094324652, x: 18}, {e: lookup.Epoch{Time: 1068488344, Level: 12}, n: 1067577330, x: 19}, {e: lookup.Epoch{Time: 1050278233, Level: 12}, n: 1050278232, x: 11}, {e: lookup.Epoch{Time: 1047660768, Level: 5}, n: 1047652137, x: 17}, {e: lookup.Epoch{Time: 1060116167, Level: 11}, n: 1060114091, x: 12}, {e: lookup.Epoch{Time: 1068149392, Level: 21}, n: 1052074801, x: 24}, {e: lookup.Epoch{Time: 1081934120, Level: 6}, n: 1081933847, x: 8}, {e: lookup.Epoch{Time: 1107943693, Level: 16}, n: 1107096139, x: 25}, {e: lookup.Epoch{Time: 1131571649, Level: 9}, n: 1131570428, x: 11}, {e: lookup.Epoch{Time: 1123139367, Level: 0}, n: 1122912198, x: 20}, {e: lookup.Epoch{Time: 1121144423, Level: 6}, n: 1120568289, x: 20}, {e: lookup.Epoch{Time: 1089932411, Level: 17}, n: 1089932410, x: 16}, {e: lookup.Epoch{Time: 1104899012, Level: 22}, n: 1098978789, x: 22}, {e: lookup.Epoch{Time: 1094588059, Level: 21}, n: 1094588059, x: 20}, {e: lookup.Epoch{Time: 1114987438, Level: 24}, n: 1114987437, x: 23}, {e: lookup.Epoch{Time: 1084186305, Level: 7}, n: 1084186241, x: 6}, {e: lookup.Epoch{Time: 1058827111, Level: 8}, n: 1058826504, x: 9}, {e: lookup.Epoch{Time: 1090679810, Level: 12}, n: 1090616539, x: 17}, {e: lookup.Epoch{Time: 1084299475, Level: 23}, n: 1084299475, x: 22}} +// TestGetNextLevel tests the lookup.GetNextLevel function func TestGetNextLevel(t *testing.T) { // First, test well-known cases @@ -632,7 +661,7 @@ func TestGetNextLevel(t *testing.T) { } -// cookGetNextLevelTests is used to generate a deterministic +// CookGetNextLevelTests is used to generate a deterministic // set of cases for TestGetNextLevel and thus "freeze" its current behavior func CookGetNextLevelTests(t *testing.T) { st := "" diff --git a/swarm/storage/feed/lookup/timesim_test.go b/swarm/storage/feed/lookup/timesim_test.go index a269893eef..b15fc820db 100644 --- a/swarm/storage/feed/lookup/timesim_test.go +++ b/swarm/storage/feed/lookup/timesim_test.go @@ -1,44 +1,54 @@ package lookup_test +// This file contains simple time simulation tools for testing +// and measuring time-aware algorithms + import ( "sync" - "sync/atomic" "time" ) +// Timer tracks information about a simulated timer type Timer struct { deadline time.Time signal chan time.Time - id int32 + id int } +// Stopwatch measures simulated execution time and manages simulated timers type Stopwatch struct { t time.Time - r time.Duration - timers map[int32]*Timer - timerCounter int32 + resolution time.Duration + timers map[int]*Timer + timerCounter int stopSignal chan struct{} lock sync.RWMutex } +// NewStopwatch returns a simulated clock that ticks on `resolution` intervals func NewStopwatch(resolution time.Duration) *Stopwatch { s := &Stopwatch{ - r: resolution, + resolution: resolution, } s.Reset() return s } +// Reset clears all timers and sents the stopwatch to zero func (s *Stopwatch) Reset() { s.t = time.Time{} - s.timers = make(map[int32]*Timer) + s.timers = make(map[int]*Timer) s.Stop() } +// Tick advances simulated time by the stopwatch's resolution and triggers +// all due timers func (s *Stopwatch) Tick() { - s.t = s.t.Add(s.r) + s.t = s.t.Add(s.resolution) + s.lock.Lock() defer s.lock.Unlock() + for id, timer := range s.timers { if s.t.After(timer.deadline) || s.t.Equal(timer.deadline) { timer.signal <- s.t @@ -48,33 +58,45 @@ func (s *Stopwatch) Tick() { } } +// GetTimer returns a new timer that will trigger after `duration` elapses in the +// simulation func (s *Stopwatch) GetTimer(duration time.Duration) <-chan time.Time { + s.lock.Lock() + defer s.lock.Unlock() + + s.timerCounter++ timer := &Timer{ deadline: s.t.Add(duration), signal: make(chan time.Time, 1), - id: atomic.AddInt32(&s.timerCounter, 1), + id: s.timerCounter, } - s.lock.Lock() - defer s.lock.Unlock() s.timers[timer.id] = timer return timer.signal } +// TimeAfter returns a simulated timer factory that can replace `time.After` func (s *Stopwatch) TimeAfter() func(d time.Duration) <-chan time.Time { return func(d time.Duration) <-chan time.Time { return s.GetTimer(d) } } +// Elapsed returns the time that has passed in the simulation func (s *Stopwatch) Elapsed() time.Duration { return s.t.Sub(time.Time{}) } +// Run starts the time simulation func (s *Stopwatch) Run() { go func() { stopSignal := make(chan struct{}) + s.lock.Lock() + if s.stopSignal != nil { + close(s.stopSignal) + } s.stopSignal = stopSignal + s.lock.Unlock() for { select { case <-time.After(1 * time.Millisecond): @@ -86,9 +108,21 @@ func (s *Stopwatch) Run() { }() } +// Stop stops the time simulation func (s *Stopwatch) Stop() { + s.lock.Lock() + defer s.lock.Unlock() + if s.stopSignal != nil { close(s.stopSignal) s.stopSignal = nil } } + +func (s *Stopwatch) Measure(measuredFunc func()) time.Duration { + s.Reset() + s.Run() + defer s.Stop() + measuredFunc() + return s.Elapsed() +}