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  }