gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/decay_test.go (about) 1 package skymodules 2 3 import ( 4 "testing" 5 "time" 6 7 "gitlab.com/NebulousLabs/fastrand" 8 ) 9 10 // testDataSet is a test struct that defines a data set with a decay 11 type testDataSet struct { 12 GenericDecay 13 data [64]float64 14 } 15 16 // addDataPoint adds a data point and applies decay to the underlying data 17 func (ds *testDataSet) addDataPoint(point int) { 18 ds.Decay(func(decay float64) { 19 for i := 0; i < len(ds.data); i++ { 20 ds.data[i] *= decay 21 } 22 }) 23 ds.data[point%64]++ 24 } 25 26 // totalPoints returns the total data points 27 func (ds *testDataSet) totalPoints() float64 { 28 var total float64 29 for _, val := range ds.data { 30 total += val 31 } 32 return total 33 } 34 35 // newTestDataSet returns a test data set with given half time 36 func newTestDataSet(halfTime time.Duration) *testDataSet { 37 return &testDataSet{ 38 GenericDecay: NewDecay(halfTime), 39 } 40 } 41 42 // TestDecay is a unit test to verify the functionality of the GenericDecay. 43 func TestDecay(t *testing.T) { 44 t.Parallel() 45 46 t.Run("Decay", testDecay) 47 t.Run("DecayedLifetime", testDecayedLifetime) 48 } 49 50 // testAddDecay is a unit test that verify decay is correctly applied to an 51 // underlying data set. 52 func testDecay(t *testing.T) { 53 t.Parallel() 54 55 // create a test dataset with a decay with a half life of 100 minutes, which 56 // means a decay operation should trigger every minute. 57 ds := newTestDataSet(time.Minute * 100) 58 59 // add 500 data points 60 for i := 0; i < 500; i++ { 61 ds.addDataPoint(i) 62 } 63 64 // We accept a range of values to compensate for the limited precision of 65 // floating points. 66 if ds.totalPoints() < 499 || ds.totalPoints() > 501 { 67 t.Error("bad", ds.totalPoints()) 68 } 69 70 // Simulate exactly the half life of time passing. 71 ds.lastDecay = time.Now().Add(-100 * time.Minute) 72 ds.addDataPoint(fastrand.Intn(64)) 73 74 // We accept a range of values to compensate for the limited precision of 75 // floating points. 76 if ds.totalPoints() < 250 || ds.totalPoints() > 252 { 77 t.Error("bad", ds.totalPoints()) 78 } 79 80 // Simulate exactly one quarter of the half life passing twice. 81 ds.lastDecay = time.Now().Add(-50 * time.Minute) 82 ds.addDataPoint(fastrand.Intn(64)) 83 ds.lastDecay = time.Now().Add(-50 * time.Minute) 84 ds.addDataPoint(fastrand.Intn(64)) 85 86 // We accept a range of values to compensate for the limited precision of 87 // floating points. 88 if ds.totalPoints() < 126 || ds.totalPoints() > 128 { 89 t.Error("bad", ds.totalPoints()) 90 } 91 } 92 93 // testDecayedLifetime checks that the total counted decayed lifetime of the 94 // distribution is being tracked correctly. 95 func testDecayedLifetime(t *testing.T) { 96 t.Parallel() 97 98 // Create a test dataset with decay with a half life of 300 minutes, which 99 // means a decay operation should trigger every three minutes. 100 ds := newTestDataSet(time.Minute * 300) 101 102 // Do 10k steps, each step advancing one minute. Every third step should 103 // trigger a decay. Add 1 data point each step. 104 for i := 0; i < 10e3; i++ { 105 ds.lastDecay = ds.lastDecay.Add(-1 * time.Minute) 106 ds.addDataPoint(fastrand.Intn(64)) 107 } 108 pointsPerHour := ds.totalPoints() / (float64(ds.decayedLifetime) / float64(time.Hour)) 109 // We accept a range of values to compensate for the limited precision of 110 // floating points. 111 if pointsPerHour < 55 || pointsPerHour > 65 { 112 t.Error("bad", pointsPerHour) 113 } 114 }