github.com/weaviate/weaviate@v1.24.6/entities/cyclemanager/ticker_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package cyclemanager
    13  
    14  import (
    15  	"context"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/stretchr/testify/assert"
    20  )
    21  
    22  func Test_FixedIntervalTicker(t *testing.T) {
    23  	t.Run("channel is empty before started", func(t *testing.T) {
    24  		interval := 10 * time.Millisecond
    25  		ticker := NewFixedTicker(10 * time.Millisecond)
    26  
    27  		assert.Len(t, ticker.C(), 0)
    28  
    29  		ticker.Start()
    30  		time.Sleep(2 * interval)
    31  
    32  		assert.Len(t, ticker.C(), 1)
    33  	})
    34  
    35  	t.Run("interval is fixed", func(t *testing.T) {
    36  		interval := 50 * time.Millisecond
    37  		tolerance := 25 * time.Millisecond
    38  
    39  		ticker := NewFixedTicker(interval)
    40  		ticker.Start()
    41  
    42  		t0 := time.Now()
    43  		val1 := <-ticker.C()
    44  		t1 := time.Now()
    45  		val2 := <-ticker.C()
    46  		t2 := time.Now()
    47  		val3 := <-ticker.C()
    48  		t3 := time.Now()
    49  		val4 := <-ticker.C()
    50  		t4 := time.Now()
    51  
    52  		ticker.Stop()
    53  
    54  		assertTimeDiffEquals(t, val1, val2, interval, tolerance)
    55  		assertTimeDiffEquals(t, val2, val3, interval, tolerance)
    56  		assertTimeDiffEquals(t, val3, val4, interval, tolerance)
    57  		assertTimeDiffEquals(t, t0, t1, interval, tolerance)
    58  		assertTimeDiffEquals(t, t1, t2, interval, tolerance)
    59  		assertTimeDiffEquals(t, t2, t3, interval, tolerance)
    60  		assertTimeDiffEquals(t, t3, t4, interval, tolerance)
    61  	})
    62  
    63  	t.Run("interval does not change on CycleExecuted call", func(t *testing.T) {
    64  		interval := 50 * time.Millisecond
    65  		tolerance := 25 * time.Millisecond
    66  
    67  		ticker := NewFixedTicker(interval)
    68  		ticker.Start()
    69  
    70  		t0 := time.Now()
    71  		val1 := <-ticker.C()
    72  		t1 := time.Now()
    73  		val2 := <-ticker.C()
    74  		t2 := time.Now()
    75  
    76  		ticker.CycleExecuted(false)
    77  
    78  		val3 := <-ticker.C()
    79  		t3 := time.Now()
    80  		val4 := <-ticker.C()
    81  		t4 := time.Now()
    82  
    83  		ticker.CycleExecuted(true)
    84  
    85  		val5 := <-ticker.C()
    86  		t5 := time.Now()
    87  		val6 := <-ticker.C()
    88  		t6 := time.Now()
    89  
    90  		ticker.Stop()
    91  
    92  		assertTimeDiffEquals(t, val1, val2, interval, tolerance)
    93  		assertTimeDiffEquals(t, val2, val3, interval, tolerance)
    94  		assertTimeDiffEquals(t, val3, val4, interval, tolerance)
    95  		assertTimeDiffEquals(t, val4, val5, interval, tolerance)
    96  		assertTimeDiffEquals(t, val5, val6, interval, tolerance)
    97  		assertTimeDiffEquals(t, t0, t1, interval, tolerance)
    98  		assertTimeDiffEquals(t, t1, t2, interval, tolerance)
    99  		assertTimeDiffEquals(t, t2, t3, interval, tolerance)
   100  		assertTimeDiffEquals(t, t3, t4, interval, tolerance)
   101  		assertTimeDiffEquals(t, t4, t5, interval, tolerance)
   102  		assertTimeDiffEquals(t, t5, t6, interval, tolerance)
   103  	})
   104  
   105  	t.Run("no ticks after stop", func(t *testing.T) {
   106  		interval := 50 * time.Millisecond
   107  		tolerance := 25 * time.Millisecond
   108  
   109  		ticker := NewFixedTicker(interval)
   110  		ticker.Start()
   111  
   112  		t0 := time.Now()
   113  		val1 := <-ticker.C()
   114  		t1 := time.Now()
   115  		val2 := <-ticker.C()
   116  		t2 := time.Now()
   117  
   118  		ticker.Stop()
   119  
   120  		tickOccurred := false
   121  		ctx, cancel := context.WithTimeout(context.Background(), 2*interval)
   122  		defer cancel()
   123  
   124  		select {
   125  		case <-ticker.C():
   126  			tickOccurred = true
   127  		case <-ctx.Done():
   128  			tickOccurred = false
   129  		}
   130  
   131  		assert.False(t, tickOccurred)
   132  
   133  		assertTimeDiffEquals(t, val1, val2, interval, tolerance)
   134  		assertTimeDiffEquals(t, t0, t1, interval, tolerance)
   135  		assertTimeDiffEquals(t, t1, t2, interval, tolerance)
   136  	})
   137  
   138  	t.Run("ticker starts again", func(t *testing.T) {
   139  		interval := 50 * time.Millisecond
   140  		tolerance := 25 * time.Millisecond
   141  
   142  		ticker := NewFixedTicker(interval)
   143  		ticker.Start()
   144  
   145  		t01 := time.Now()
   146  		val1 := <-ticker.C()
   147  		t1 := time.Now()
   148  		val2 := <-ticker.C()
   149  		t2 := time.Now()
   150  
   151  		ticker.Stop()
   152  		ticker.Start()
   153  
   154  		t02 := time.Now()
   155  		val3 := <-ticker.C()
   156  		t3 := time.Now()
   157  		val4 := <-ticker.C()
   158  		t4 := time.Now()
   159  
   160  		ticker.Stop()
   161  
   162  		assertTimeDiffEquals(t, val1, val2, interval, tolerance)
   163  		assertTimeDiffEquals(t, val3, val4, interval, tolerance)
   164  		assertTimeDiffEquals(t, t01, t1, interval, tolerance)
   165  		assertTimeDiffEquals(t, t1, t2, interval, tolerance)
   166  		assertTimeDiffEquals(t, t02, t3, interval, tolerance)
   167  		assertTimeDiffEquals(t, t3, t4, interval, tolerance)
   168  	})
   169  
   170  	t.Run("ticker does not run with <= 0 interval", func(t *testing.T) {
   171  		interval := time.Duration(0)
   172  
   173  		ticker := NewFixedTicker(interval)
   174  		ticker.Start()
   175  
   176  		tickOccurred := false
   177  		ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
   178  		defer cancel()
   179  
   180  		select {
   181  		case <-ticker.C():
   182  			tickOccurred = true
   183  		case <-ctx.Done():
   184  			tickOccurred = false
   185  		}
   186  
   187  		assert.False(t, tickOccurred)
   188  
   189  		ticker.Stop()
   190  	})
   191  }
   192  
   193  func Test_SeriesTicker(t *testing.T) {
   194  	t.Run("channel is empty before started", func(t *testing.T) {
   195  		intervals := []time.Duration{10 * time.Millisecond, 20 * time.Millisecond}
   196  		ticker := NewSeriesTicker(intervals)
   197  
   198  		assert.Len(t, ticker.C(), 0)
   199  
   200  		ticker.Start()
   201  		time.Sleep(2 * intervals[0])
   202  
   203  		assert.Len(t, ticker.C(), 1)
   204  	})
   205  
   206  	t.Run("interval is fixed between CycleExecuted calls, advances on false, resets on true", func(t *testing.T) {
   207  		intervals := []time.Duration{50 * time.Millisecond, 100 * time.Millisecond, 150 * time.Millisecond}
   208  		tolerance := 25 * time.Millisecond
   209  
   210  		ticker := NewSeriesTicker(intervals)
   211  		ticker.Start()
   212  
   213  		t0 := time.Now()
   214  		val1 := <-ticker.C()
   215  		t1 := time.Now()
   216  		val2 := <-ticker.C()
   217  		t2 := time.Now()
   218  
   219  		ticker.CycleExecuted(false)
   220  
   221  		val3 := <-ticker.C()
   222  		t3 := time.Now()
   223  		val4 := <-ticker.C()
   224  		t4 := time.Now()
   225  
   226  		ticker.CycleExecuted(false)
   227  
   228  		val5 := <-ticker.C()
   229  		t5 := time.Now()
   230  		val6 := <-ticker.C()
   231  		t6 := time.Now()
   232  
   233  		ticker.CycleExecuted(false)
   234  
   235  		val7 := <-ticker.C()
   236  		t7 := time.Now()
   237  		val8 := <-ticker.C()
   238  		t8 := time.Now()
   239  
   240  		ticker.CycleExecuted(true)
   241  
   242  		val9 := <-ticker.C()
   243  		t9 := time.Now()
   244  		val10 := <-ticker.C()
   245  		t10 := time.Now()
   246  
   247  		ticker.Stop()
   248  
   249  		assertTimeDiffEquals(t, val1, val2, intervals[0], tolerance)
   250  		assertTimeDiffEquals(t, val2, val3, intervals[1], tolerance)
   251  		assertTimeDiffEquals(t, val3, val4, intervals[1], tolerance)
   252  		assertTimeDiffEquals(t, val4, val5, intervals[2], tolerance)
   253  		assertTimeDiffEquals(t, val5, val6, intervals[2], tolerance)
   254  		assertTimeDiffEquals(t, val6, val7, intervals[2], tolerance)
   255  		assertTimeDiffEquals(t, val7, val8, intervals[2], tolerance)
   256  		assertTimeDiffEquals(t, val8, val9, intervals[0], tolerance)
   257  		assertTimeDiffEquals(t, val9, val10, intervals[0], tolerance)
   258  		assertTimeDiffEquals(t, t0, t1, intervals[0], tolerance)
   259  		assertTimeDiffEquals(t, t1, t2, intervals[0], tolerance)
   260  		assertTimeDiffEquals(t, t2, t3, intervals[1], tolerance)
   261  		assertTimeDiffEquals(t, t3, t4, intervals[1], tolerance)
   262  		assertTimeDiffEquals(t, t4, t5, intervals[2], tolerance)
   263  		assertTimeDiffEquals(t, t5, t6, intervals[2], tolerance)
   264  		assertTimeDiffEquals(t, t6, t7, intervals[2], tolerance)
   265  		assertTimeDiffEquals(t, t7, t8, intervals[2], tolerance)
   266  		assertTimeDiffEquals(t, t8, t9, intervals[0], tolerance)
   267  		assertTimeDiffEquals(t, t9, t10, intervals[0], tolerance)
   268  	})
   269  
   270  	t.Run("no ticks after stop", func(t *testing.T) {
   271  		intervals := []time.Duration{50 * time.Millisecond}
   272  		tolerance := 25 * time.Millisecond
   273  
   274  		ticker := NewSeriesTicker(intervals)
   275  		ticker.Start()
   276  
   277  		t0 := time.Now()
   278  		val1 := <-ticker.C()
   279  		t1 := time.Now()
   280  		val2 := <-ticker.C()
   281  		t2 := time.Now()
   282  
   283  		ticker.Stop()
   284  
   285  		tickOccurred := false
   286  		ctx, cancel := context.WithTimeout(context.Background(), 2*intervals[0])
   287  		defer cancel()
   288  
   289  		select {
   290  		case <-ticker.C():
   291  			tickOccurred = true
   292  		case <-ctx.Done():
   293  			tickOccurred = false
   294  		}
   295  
   296  		assert.False(t, tickOccurred)
   297  
   298  		assertTimeDiffEquals(t, val1, val2, intervals[0], tolerance)
   299  		assertTimeDiffEquals(t, t0, t1, intervals[0], tolerance)
   300  		assertTimeDiffEquals(t, t1, t2, intervals[0], tolerance)
   301  	})
   302  
   303  	t.Run("ticker starts again", func(t *testing.T) {
   304  		intervals := []time.Duration{50 * time.Millisecond}
   305  		tolerance := 25 * time.Millisecond
   306  
   307  		ticker := NewSeriesTicker(intervals)
   308  		ticker.Start()
   309  
   310  		t01 := time.Now()
   311  		val1 := <-ticker.C()
   312  		t1 := time.Now()
   313  		val2 := <-ticker.C()
   314  		t2 := time.Now()
   315  
   316  		ticker.Stop()
   317  		ticker.Start()
   318  
   319  		t02 := time.Now()
   320  		val3 := <-ticker.C()
   321  		t3 := time.Now()
   322  		val4 := <-ticker.C()
   323  		t4 := time.Now()
   324  
   325  		ticker.Stop()
   326  
   327  		assertTimeDiffEquals(t, val1, val2, intervals[0], tolerance)
   328  		assertTimeDiffEquals(t, val3, val4, intervals[0], tolerance)
   329  		assertTimeDiffEquals(t, t01, t1, intervals[0], tolerance)
   330  		assertTimeDiffEquals(t, t1, t2, intervals[0], tolerance)
   331  		assertTimeDiffEquals(t, t02, t3, intervals[0], tolerance)
   332  		assertTimeDiffEquals(t, t3, t4, intervals[0], tolerance)
   333  	})
   334  
   335  	t.Run("ticker does not run with invalid params", func(t *testing.T) {
   336  		run := func(t *testing.T, ticker CycleTicker) {
   337  			ticker.Start()
   338  
   339  			tickOccurred := false
   340  			ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
   341  			defer cancel()
   342  
   343  			select {
   344  			case <-ticker.C():
   345  				tickOccurred = true
   346  			case <-ctx.Done():
   347  				tickOccurred = false
   348  			}
   349  
   350  			assert.False(t, tickOccurred)
   351  
   352  			ticker.Stop()
   353  		}
   354  
   355  		t.Run("any interval <= 0", func(t *testing.T) {
   356  			ticker := NewSeriesTicker([]time.Duration{50 * time.Millisecond, 0})
   357  
   358  			run(t, ticker)
   359  		})
   360  
   361  		t.Run("no intervals", func(t *testing.T) {
   362  			ticker := NewSeriesTicker([]time.Duration{})
   363  
   364  			run(t, ticker)
   365  		})
   366  	})
   367  }
   368  
   369  func Test_LinearTicker(t *testing.T) {
   370  	t.Run("channel is empty before started", func(t *testing.T) {
   371  		minInterval := 10 * time.Millisecond
   372  		maxInterval := 50 * time.Millisecond
   373  		steps := uint(2)
   374  		ticker := NewLinearTicker(minInterval, maxInterval, steps)
   375  
   376  		assert.Len(t, ticker.C(), 0)
   377  
   378  		ticker.Start()
   379  		time.Sleep(2 * minInterval)
   380  
   381  		assert.Len(t, ticker.C(), 1)
   382  	})
   383  
   384  	t.Run("interval is fixed between CycleExecuted calls, advances on false, resets on true", func(t *testing.T) {
   385  		ms50 := 50 * time.Millisecond
   386  		ms75 := 75 * time.Millisecond
   387  		ms100 := 100 * time.Millisecond
   388  		tolerance := 25 * time.Millisecond
   389  
   390  		minInterval := ms50
   391  		maxInterval := ms100
   392  		steps := uint(2)
   393  
   394  		ticker := NewLinearTicker(minInterval, maxInterval, steps)
   395  		ticker.Start()
   396  
   397  		t0 := time.Now()
   398  		val1 := <-ticker.C()
   399  		t1 := time.Now()
   400  		val2 := <-ticker.C()
   401  		t2 := time.Now()
   402  
   403  		ticker.CycleExecuted(false)
   404  
   405  		val3 := <-ticker.C()
   406  		t3 := time.Now()
   407  		val4 := <-ticker.C()
   408  		t4 := time.Now()
   409  
   410  		ticker.CycleExecuted(false)
   411  
   412  		val5 := <-ticker.C()
   413  		t5 := time.Now()
   414  		val6 := <-ticker.C()
   415  		t6 := time.Now()
   416  
   417  		ticker.CycleExecuted(false)
   418  
   419  		val7 := <-ticker.C()
   420  		t7 := time.Now()
   421  		val8 := <-ticker.C()
   422  		t8 := time.Now()
   423  
   424  		ticker.CycleExecuted(true)
   425  
   426  		val9 := <-ticker.C()
   427  		t9 := time.Now()
   428  		val10 := <-ticker.C()
   429  		t10 := time.Now()
   430  
   431  		ticker.Stop()
   432  
   433  		assertTimeDiffEquals(t, val1, val2, ms50, tolerance)
   434  		assertTimeDiffEquals(t, val2, val3, ms75, tolerance)
   435  		assertTimeDiffEquals(t, val3, val4, ms75, tolerance)
   436  		assertTimeDiffEquals(t, val4, val5, ms100, tolerance)
   437  		assertTimeDiffEquals(t, val5, val6, ms100, tolerance)
   438  		assertTimeDiffEquals(t, val6, val7, ms100, tolerance)
   439  		assertTimeDiffEquals(t, val7, val8, ms100, tolerance)
   440  		assertTimeDiffEquals(t, val8, val9, ms50, tolerance)
   441  		assertTimeDiffEquals(t, val9, val10, ms50, tolerance)
   442  		assertTimeDiffEquals(t, t0, t1, ms50, tolerance)
   443  		assertTimeDiffEquals(t, t1, t2, ms50, tolerance)
   444  		assertTimeDiffEquals(t, t2, t3, ms75, tolerance)
   445  		assertTimeDiffEquals(t, t3, t4, ms75, tolerance)
   446  		assertTimeDiffEquals(t, t4, t5, ms100, tolerance)
   447  		assertTimeDiffEquals(t, t5, t6, ms100, tolerance)
   448  		assertTimeDiffEquals(t, t6, t7, ms100, tolerance)
   449  		assertTimeDiffEquals(t, t7, t8, ms100, tolerance)
   450  		assertTimeDiffEquals(t, t8, t9, ms50, tolerance)
   451  		assertTimeDiffEquals(t, t9, t10, ms50, tolerance)
   452  	})
   453  
   454  	t.Run("no ticks after stop", func(t *testing.T) {
   455  		minInterval := 50 * time.Millisecond
   456  		maxInterval := 100 * time.Millisecond
   457  		steps := uint(2)
   458  		tolerance := 10 * time.Millisecond
   459  
   460  		ticker := NewLinearTicker(minInterval, maxInterval, steps)
   461  		ticker.Start()
   462  
   463  		t0 := time.Now()
   464  		val1 := <-ticker.C()
   465  		t1 := time.Now()
   466  		val2 := <-ticker.C()
   467  		t2 := time.Now()
   468  
   469  		ticker.Stop()
   470  
   471  		tickOccurred := false
   472  		ctx, cancel := context.WithTimeout(context.Background(), 2*minInterval)
   473  		defer cancel()
   474  
   475  		select {
   476  		case <-ticker.C():
   477  			tickOccurred = true
   478  		case <-ctx.Done():
   479  			tickOccurred = false
   480  		}
   481  
   482  		assert.False(t, tickOccurred)
   483  
   484  		assertTimeDiffEquals(t, val1, val2, minInterval, tolerance)
   485  		assertTimeDiffEquals(t, t0, t1, minInterval, tolerance)
   486  		assertTimeDiffEquals(t, t1, t2, minInterval, tolerance)
   487  	})
   488  
   489  	t.Run("ticker starts again", func(t *testing.T) {
   490  		minInterval := 50 * time.Millisecond
   491  		maxInterval := 100 * time.Millisecond
   492  		steps := uint(2)
   493  		tolerance := 25 * time.Millisecond
   494  
   495  		ticker := NewLinearTicker(minInterval, maxInterval, steps)
   496  		ticker.Start()
   497  
   498  		t01 := time.Now()
   499  		val1 := <-ticker.C()
   500  		t1 := time.Now()
   501  		val2 := <-ticker.C()
   502  		t2 := time.Now()
   503  
   504  		ticker.Stop()
   505  		ticker.Start()
   506  
   507  		t02 := time.Now()
   508  		val3 := <-ticker.C()
   509  		t3 := time.Now()
   510  		val4 := <-ticker.C()
   511  		t4 := time.Now()
   512  
   513  		ticker.Stop()
   514  
   515  		assertTimeDiffEquals(t, val1, val2, minInterval, tolerance)
   516  		assertTimeDiffEquals(t, val3, val4, minInterval, tolerance)
   517  		assertTimeDiffEquals(t, t01, t1, minInterval, tolerance)
   518  		assertTimeDiffEquals(t, t1, t2, minInterval, tolerance)
   519  		assertTimeDiffEquals(t, t02, t3, minInterval, tolerance)
   520  		assertTimeDiffEquals(t, t3, t4, minInterval, tolerance)
   521  	})
   522  
   523  	t.Run("ticker does not run with invalid params", func(t *testing.T) {
   524  		run := func(t *testing.T, ticker CycleTicker) {
   525  			ticker.Start()
   526  
   527  			tickOccurred := false
   528  			ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
   529  			defer cancel()
   530  
   531  			select {
   532  			case <-ticker.C():
   533  				tickOccurred = true
   534  			case <-ctx.Done():
   535  				tickOccurred = false
   536  			}
   537  
   538  			assert.False(t, tickOccurred)
   539  
   540  			ticker.Stop()
   541  		}
   542  
   543  		t.Run("minInterval <= 0", func(t *testing.T) {
   544  			ticker := NewLinearTicker(0, 100*time.Millisecond, 1)
   545  
   546  			run(t, ticker)
   547  		})
   548  
   549  		t.Run("maxInterval <= 0", func(t *testing.T) {
   550  			ticker := NewLinearTicker(50*time.Millisecond, 0, 1)
   551  
   552  			run(t, ticker)
   553  		})
   554  
   555  		t.Run("steps = 0", func(t *testing.T) {
   556  			ticker := NewLinearTicker(50*time.Millisecond, 100*time.Millisecond, 0)
   557  
   558  			run(t, ticker)
   559  		})
   560  
   561  		t.Run("minInterval > maxInterval", func(t *testing.T) {
   562  			ticker := NewLinearTicker(100*time.Millisecond, 50*time.Millisecond, 0)
   563  
   564  			run(t, ticker)
   565  		})
   566  	})
   567  }
   568  
   569  func Test_ExpTicker(t *testing.T) {
   570  	t.Run("channel is empty before started", func(t *testing.T) {
   571  		minInterval := 10 * time.Millisecond
   572  		maxInterval := 20 * time.Millisecond
   573  		base := uint(2)
   574  		steps := uint(2)
   575  		ticker := NewExpTicker(minInterval, maxInterval, base, steps)
   576  
   577  		assert.Len(t, ticker.C(), 0)
   578  
   579  		ticker.Start()
   580  		time.Sleep(2 * minInterval)
   581  
   582  		assert.Len(t, ticker.C(), 1)
   583  	})
   584  
   585  	t.Run("interval is fixed between CycleExecuted calls, advances on false, resets on true", func(t *testing.T) {
   586  		ms25 := 25 * time.Millisecond
   587  		ms50 := 50 * time.Millisecond
   588  		ms100 := 100 * time.Millisecond
   589  		tolerance := 25 * time.Millisecond
   590  
   591  		minInterval := ms25
   592  		maxInterval := ms100
   593  		base := uint(2)
   594  		steps := uint(2)
   595  
   596  		ticker := NewExpTicker(minInterval, maxInterval, base, steps)
   597  		ticker.Start()
   598  
   599  		t0 := time.Now()
   600  		val1 := <-ticker.C()
   601  		t1 := time.Now()
   602  		val2 := <-ticker.C()
   603  		t2 := time.Now()
   604  
   605  		ticker.CycleExecuted(false)
   606  
   607  		val3 := <-ticker.C()
   608  		t3 := time.Now()
   609  		val4 := <-ticker.C()
   610  		t4 := time.Now()
   611  
   612  		ticker.CycleExecuted(false)
   613  
   614  		val5 := <-ticker.C()
   615  		t5 := time.Now()
   616  		val6 := <-ticker.C()
   617  		t6 := time.Now()
   618  
   619  		ticker.CycleExecuted(false)
   620  
   621  		val7 := <-ticker.C()
   622  		t7 := time.Now()
   623  		val8 := <-ticker.C()
   624  		t8 := time.Now()
   625  
   626  		ticker.CycleExecuted(true)
   627  
   628  		val9 := <-ticker.C()
   629  		t9 := time.Now()
   630  		val10 := <-ticker.C()
   631  		t10 := time.Now()
   632  
   633  		ticker.Stop()
   634  
   635  		assertTimeDiffEquals(t, val1, val2, ms25, tolerance)
   636  		assertTimeDiffEquals(t, val2, val3, ms50, tolerance)
   637  		assertTimeDiffEquals(t, val3, val4, ms50, tolerance)
   638  		assertTimeDiffEquals(t, val4, val5, ms100, tolerance)
   639  		assertTimeDiffEquals(t, val5, val6, ms100, tolerance)
   640  		assertTimeDiffEquals(t, val6, val7, ms100, tolerance)
   641  		assertTimeDiffEquals(t, val7, val8, ms100, tolerance)
   642  		assertTimeDiffEquals(t, val8, val9, ms25, tolerance)
   643  		assertTimeDiffEquals(t, val9, val10, ms25, tolerance)
   644  		assertTimeDiffEquals(t, t0, t1, ms25, tolerance)
   645  		assertTimeDiffEquals(t, t1, t2, ms25, tolerance)
   646  		assertTimeDiffEquals(t, t2, t3, ms50, tolerance)
   647  		assertTimeDiffEquals(t, t3, t4, ms50, tolerance)
   648  		assertTimeDiffEquals(t, t4, t5, ms100, tolerance)
   649  		assertTimeDiffEquals(t, t5, t6, ms100, tolerance)
   650  		assertTimeDiffEquals(t, t6, t7, ms100, tolerance)
   651  		assertTimeDiffEquals(t, t7, t8, ms100, tolerance)
   652  		assertTimeDiffEquals(t, t8, t9, ms25, tolerance)
   653  		assertTimeDiffEquals(t, t9, t10, ms25, tolerance)
   654  	})
   655  
   656  	t.Run("no ticks after stop", func(t *testing.T) {
   657  		minInterval := 25 * time.Millisecond
   658  		maxInterval := 100 * time.Millisecond
   659  		base := uint(2)
   660  		steps := uint(2)
   661  		tolerance := 25 * time.Millisecond
   662  
   663  		ticker := NewExpTicker(minInterval, maxInterval, base, steps)
   664  		ticker.Start()
   665  
   666  		t0 := time.Now()
   667  		val1 := <-ticker.C()
   668  		t1 := time.Now()
   669  		val2 := <-ticker.C()
   670  		t2 := time.Now()
   671  
   672  		ticker.Stop()
   673  
   674  		tickOccurred := false
   675  		ctx, cancel := context.WithTimeout(context.Background(), 2*minInterval)
   676  		defer cancel()
   677  
   678  		select {
   679  		case <-ticker.C():
   680  			tickOccurred = true
   681  		case <-ctx.Done():
   682  			tickOccurred = false
   683  		}
   684  
   685  		assert.False(t, tickOccurred)
   686  
   687  		assertTimeDiffEquals(t, val1, val2, minInterval, tolerance)
   688  		assertTimeDiffEquals(t, t0, t1, minInterval, tolerance)
   689  		assertTimeDiffEquals(t, t1, t2, minInterval, tolerance)
   690  	})
   691  
   692  	t.Run("ticker starts again", func(t *testing.T) {
   693  		minInterval := 25 * time.Millisecond
   694  		maxInterval := 100 * time.Millisecond
   695  		base := uint(2)
   696  		steps := uint(2)
   697  		tolerance := 25 * time.Millisecond
   698  
   699  		ticker := NewExpTicker(minInterval, maxInterval, base, steps)
   700  		ticker.Start()
   701  
   702  		t01 := time.Now()
   703  		val1 := <-ticker.C()
   704  		t1 := time.Now()
   705  		val2 := <-ticker.C()
   706  		t2 := time.Now()
   707  
   708  		ticker.Stop()
   709  		ticker.Start()
   710  
   711  		t02 := time.Now()
   712  		val3 := <-ticker.C()
   713  		t3 := time.Now()
   714  		val4 := <-ticker.C()
   715  		t4 := time.Now()
   716  
   717  		ticker.Stop()
   718  
   719  		assertTimeDiffEquals(t, val1, val2, minInterval, tolerance)
   720  		assertTimeDiffEquals(t, val3, val4, minInterval, tolerance)
   721  		assertTimeDiffEquals(t, t01, t1, minInterval, tolerance)
   722  		assertTimeDiffEquals(t, t1, t2, minInterval, tolerance)
   723  		assertTimeDiffEquals(t, t02, t3, minInterval, tolerance)
   724  		assertTimeDiffEquals(t, t3, t4, minInterval, tolerance)
   725  	})
   726  
   727  	t.Run("ticker does not run with invalid params", func(t *testing.T) {
   728  		run := func(t *testing.T, ticker CycleTicker) {
   729  			ticker.Start()
   730  
   731  			tickOccurred := false
   732  			ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
   733  			defer cancel()
   734  
   735  			select {
   736  			case <-ticker.C():
   737  				tickOccurred = true
   738  			case <-ctx.Done():
   739  				tickOccurred = false
   740  			}
   741  
   742  			assert.False(t, tickOccurred)
   743  
   744  			ticker.Stop()
   745  		}
   746  
   747  		t.Run("minInterval <= 0", func(t *testing.T) {
   748  			ticker := NewExpTicker(0, 100*time.Millisecond, 2, 2)
   749  
   750  			run(t, ticker)
   751  		})
   752  
   753  		t.Run("maxInterval <= 0", func(t *testing.T) {
   754  			ticker := NewExpTicker(100*time.Millisecond, 0, 2, 2)
   755  
   756  			run(t, ticker)
   757  		})
   758  
   759  		t.Run("base == 0", func(t *testing.T) {
   760  			ticker := NewExpTicker(25*time.Millisecond, 100*time.Millisecond, 0, 2)
   761  
   762  			run(t, ticker)
   763  		})
   764  
   765  		t.Run("steps = 0", func(t *testing.T) {
   766  			ticker := NewExpTicker(25*time.Millisecond, 100*time.Millisecond, 2, 0)
   767  
   768  			run(t, ticker)
   769  		})
   770  
   771  		t.Run("minInterval > maxInterval", func(t *testing.T) {
   772  			ticker := NewExpTicker(100*time.Millisecond, 25*time.Millisecond, 2, 2)
   773  
   774  			run(t, ticker)
   775  		})
   776  	})
   777  }
   778  
   779  func Test_LinearToIntervals(t *testing.T) {
   780  	type testCase struct {
   781  		name        string
   782  		minInterval time.Duration
   783  		maxInterval time.Duration
   784  		steps       uint
   785  		expected    []time.Duration
   786  	}
   787  
   788  	testCases := []testCase{
   789  		{
   790  			name:        "100 => 5000; steps 2",
   791  			minInterval: 100 * time.Millisecond,
   792  			maxInterval: 5 * time.Second,
   793  			steps:       2,
   794  			expected: []time.Duration{
   795  				100_000_000,
   796  				2_550_000_000,
   797  				5_000_000_000,
   798  			},
   799  		},
   800  		{
   801  			name:        "100 => 5000; steps 3",
   802  			minInterval: 100 * time.Millisecond,
   803  			maxInterval: 5 * time.Second,
   804  			steps:       3,
   805  			expected: []time.Duration{
   806  				100_000_000,
   807  				1_733_333_333,
   808  				3_366_666_666,
   809  				5_000_000_000,
   810  			},
   811  		},
   812  		{
   813  			name:        "100 => 5000; steps 4",
   814  			minInterval: 100 * time.Millisecond,
   815  			maxInterval: 5 * time.Second,
   816  			steps:       4,
   817  			expected: []time.Duration{
   818  				100_000_000,
   819  				1_325_000_000,
   820  				2_550_000_000,
   821  				3_775_000_000,
   822  				5_000_000_000,
   823  			},
   824  		},
   825  	}
   826  
   827  	for _, tc := range testCases {
   828  		t.Run(tc.name, func(t *testing.T) {
   829  			res := linearToIntervals(tc.minInterval, tc.maxInterval, tc.steps)
   830  
   831  			assert.ElementsMatch(t, res, tc.expected)
   832  		})
   833  	}
   834  }
   835  
   836  func Test_ExpToIntervals(t *testing.T) {
   837  	type testCase struct {
   838  		name        string
   839  		minInterval time.Duration
   840  		maxInterval time.Duration
   841  		base        uint
   842  		steps       uint
   843  		expected    []time.Duration
   844  	}
   845  
   846  	testCases := []testCase{
   847  		{
   848  			name:        "100 => 5000; base 2; steps 2",
   849  			minInterval: 100 * time.Millisecond,
   850  			maxInterval: 5 * time.Second,
   851  			base:        2,
   852  			steps:       2,
   853  			expected: []time.Duration{
   854  				100_000_000,
   855  				1_733_333_333,
   856  				5_000_000_000,
   857  			},
   858  		},
   859  		{
   860  			name:        "100 => 5000; base 2; steps 3",
   861  			minInterval: 100 * time.Millisecond,
   862  			maxInterval: 5 * time.Second,
   863  			base:        2,
   864  			steps:       3,
   865  			expected: []time.Duration{
   866  				100_000_000,
   867  				800_000_000,
   868  				2_200_000_000,
   869  				5_000_000_000,
   870  			},
   871  		},
   872  		{
   873  			name:        "100 => 5000; base 2; steps 4",
   874  			minInterval: 100 * time.Millisecond,
   875  			maxInterval: 5 * time.Second,
   876  			base:        2,
   877  			steps:       4,
   878  			expected: []time.Duration{
   879  				100_000_000,
   880  				426_666_666,
   881  				1_080_000_000,
   882  				2_386_666_666,
   883  				5_000_000_000,
   884  			},
   885  		},
   886  		{
   887  			name:        "100 => 5000; base 3; steps 2",
   888  			minInterval: 100 * time.Millisecond,
   889  			maxInterval: 5 * time.Second,
   890  			base:        3,
   891  			steps:       2,
   892  			expected: []time.Duration{
   893  				100_000_000,
   894  				1_325_000_000,
   895  				5_000_000_000,
   896  			},
   897  		},
   898  		{
   899  			name:        "100 => 5000; base 3; steps 3",
   900  			minInterval: 100 * time.Millisecond,
   901  			maxInterval: 5 * time.Second,
   902  			base:        3,
   903  			steps:       3,
   904  			expected: []time.Duration{
   905  				100_000_000,
   906  				476_923_076,
   907  				1_607_692_307,
   908  				5_000_000_000,
   909  			},
   910  		},
   911  		{
   912  			name:        "100 => 5000; base 3; steps 4",
   913  			minInterval: 100 * time.Millisecond,
   914  			maxInterval: 5 * time.Second,
   915  			base:        3,
   916  			steps:       4,
   917  			expected: []time.Duration{
   918  				100_000_000,
   919  				222_500_000,
   920  				590_000_000,
   921  				1_692_500_000,
   922  				5_000_000_000,
   923  			},
   924  		},
   925  	}
   926  
   927  	for _, tc := range testCases {
   928  		t.Run(tc.name, func(t *testing.T) {
   929  			res := expToIntervals(tc.minInterval, tc.maxInterval, tc.base, tc.steps)
   930  
   931  			assert.ElementsMatch(t, res, tc.expected)
   932  		})
   933  	}
   934  }
   935  
   936  func assertTimeDiffEquals(t *testing.T, time1, time2 time.Time, expected time.Duration, tolerance time.Duration) {
   937  	diff := time2.Sub(time1)
   938  	assert.GreaterOrEqual(t, diff, expected-tolerance)
   939  	assert.LessOrEqual(t, diff, expected+tolerance)
   940  }