go.mway.dev/chrono@v0.6.1-0.20240126030049-189c5aef20d2/clock/throttled_clock_test.go (about)

     1  // Copyright (c) 2023 Matt Way
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to
     5  // deal in the Software without restriction, including without limitation the
     6  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
     7  // sell copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    18  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    19  // IN THE THE SOFTWARE.
    20  
    21  package clock_test
    22  
    23  import (
    24  	"runtime"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/stretchr/testify/require"
    30  	"go.mway.dev/chrono/clock"
    31  	"go.mway.dev/math"
    32  	"go.uber.org/atomic"
    33  )
    34  
    35  func TestThrottledClock_Constructors(t *testing.T) {
    36  	cases := map[string]struct {
    37  		clockFn func(time.Duration) *clock.ThrottledClock
    38  	}{
    39  		"NewThrottledMonotonicClock": {
    40  			clockFn: func(d time.Duration) *clock.ThrottledClock {
    41  				return clock.NewThrottledMonotonicClock(d)
    42  			},
    43  		},
    44  		"NewThrottledWallClock": {
    45  			clockFn: func(d time.Duration) *clock.ThrottledClock {
    46  				return clock.NewThrottledWallClock(d)
    47  			},
    48  		},
    49  	}
    50  
    51  	for name, tt := range cases {
    52  		t.Run(name, func(t *testing.T) {
    53  			var (
    54  				clock     = tt.clockFn(time.Millisecond)
    55  				prevNanos = clock.Nanotime()
    56  				prevTime  = clock.Now()
    57  			)
    58  			defer clock.Stop()
    59  
    60  			waitForChange(t, clock, prevNanos)
    61  
    62  			require.True(t, clock.Nanotime() > prevNanos, "nanotime did not increase")
    63  			require.True(t, clock.Now().After(prevTime), "time did not increase")
    64  		})
    65  	}
    66  }
    67  
    68  //nolint:gocyclo
    69  func TestThrottledClock_Timers(t *testing.T) {
    70  	clk := clock.NewThrottledClock(func() int64 { return 0 }, time.Minute)
    71  	defer clk.Stop()
    72  
    73  	var (
    74  		first = clk.Nanotime()
    75  		wg    sync.WaitGroup
    76  	)
    77  
    78  	// ThrottledClock.NewTimer
    79  	wg.Add(1)
    80  	go func() {
    81  		defer wg.Done()
    82  
    83  		timer := clk.NewTimer(time.Millisecond)
    84  		defer timer.Stop()
    85  
    86  		select {
    87  		case _, ok := <-timer.C:
    88  			require.True(t, ok, "zero time returned")
    89  		case <-time.After(time.Second):
    90  			require.FailNow(t, "timer did not fire")
    91  		}
    92  	}()
    93  
    94  	// ThrottledClock.NewTicker
    95  	wg.Add(1)
    96  	go func() {
    97  		defer wg.Done()
    98  
    99  		ticker := clk.NewTicker(time.Millisecond)
   100  		defer ticker.Stop()
   101  
   102  		for i := 0; i < 10; i++ {
   103  			select {
   104  			case <-ticker.C:
   105  			case <-time.After(time.Second):
   106  				require.FailNow(t, "timer did not fire")
   107  			}
   108  		}
   109  	}()
   110  
   111  	// ThrottledClock.Tick
   112  	wg.Add(1)
   113  	go func() {
   114  		defer wg.Done()
   115  
   116  		tickerC := clk.Tick(time.Millisecond)
   117  
   118  		for i := 0; i < 10; i++ {
   119  			select {
   120  			case <-tickerC:
   121  			case <-time.After(time.Second):
   122  				require.FailNow(t, "timer did not fire")
   123  			}
   124  		}
   125  	}()
   126  
   127  	// ThrottledClock.After
   128  	wg.Add(1)
   129  	go func() {
   130  		defer wg.Done()
   131  
   132  		timerC := clk.After(time.Millisecond)
   133  
   134  		select {
   135  		case <-timerC:
   136  		case <-time.After(time.Second):
   137  			require.FailNow(t, "timer did not fire")
   138  		}
   139  	}()
   140  
   141  	// ThrottledClock.AfterFunc
   142  	wg.Add(1)
   143  	go func() {
   144  		defer wg.Done()
   145  
   146  		timerC := make(chan struct{})
   147  		clk.AfterFunc(time.Millisecond, func() {
   148  			close(timerC)
   149  		})
   150  
   151  		select {
   152  		case <-timerC:
   153  		case <-time.After(time.Second):
   154  			require.FailNow(t, "timer did not fire")
   155  		}
   156  	}()
   157  
   158  	// ThrottledClock.Sleep
   159  	wg.Add(1)
   160  	go func() {
   161  		defer wg.Done()
   162  
   163  		timerC := make(chan struct{})
   164  		go func() {
   165  			clk.Sleep(10 * time.Millisecond)
   166  			close(timerC)
   167  		}()
   168  
   169  		select {
   170  		case <-timerC:
   171  		case <-time.After(time.Second):
   172  			require.FailNow(t, "timer did not fire")
   173  		}
   174  	}()
   175  
   176  	wg.Wait()
   177  	require.Equal(t, first, clk.Nanotime())
   178  }
   179  
   180  func TestThrottledClock_Since(t *testing.T) {
   181  	clk := clock.NewThrottledClock(func() int64 { return 123 }, time.Minute)
   182  	defer clk.Stop()
   183  
   184  	require.Equal(t, 23*time.Nanosecond, clk.Since(time.Unix(0, 100)))
   185  	require.Equal(t, 23*time.Nanosecond, clk.SinceNanotime(100))
   186  }
   187  
   188  func TestThrottledClock_Internals(t *testing.T) {
   189  	var (
   190  		now   = atomic.NewInt64(123)
   191  		nowfn = func() int64 {
   192  			return now.Load()
   193  		}
   194  	)
   195  
   196  	clk := clock.NewThrottledClock(nowfn, time.Microsecond)
   197  	defer clk.Stop()
   198  
   199  	require.Equal(t, now.Load(), clk.Nanotime())
   200  	require.True(t, clk.Now().Equal(time.Unix(0, now.Load())))
   201  
   202  	prev := now.Load()
   203  	now.Store(456)
   204  	waitForChange(t, clk, prev)
   205  
   206  	require.Equal(t, now.Load(), clk.Nanotime())
   207  	require.True(t, clk.Now().Equal(time.Unix(0, now.Load())))
   208  
   209  	prev = now.Load()
   210  	now.Store(1)
   211  	waitForChange(t, clk, prev)
   212  
   213  	require.Equal(t, now.Load(), clk.Nanotime())
   214  	require.True(t, clk.Now().Equal(time.Unix(0, now.Load())))
   215  
   216  	clk.Stop()
   217  
   218  	prev = now.Load()
   219  	now.Store(1)
   220  
   221  	// The clock should no longer update once it is stopped.
   222  	time.Sleep(100 * time.Millisecond)
   223  	require.Equal(t, prev, clk.Nanotime())
   224  }
   225  
   226  func TestThrottledClock_Stopwatch(t *testing.T) {
   227  	var (
   228  		now   = atomic.NewInt64(0)
   229  		nowfn = func() int64 {
   230  			return now.Load()
   231  		}
   232  	)
   233  
   234  	clk := clock.NewThrottledClock(nowfn, time.Microsecond)
   235  	defer clk.Stop()
   236  
   237  	stopwatch := clk.NewStopwatch()
   238  	require.Equal(t, 0*time.Second, stopwatch.Elapsed())
   239  
   240  	now.Add(int64(time.Second))
   241  	waitForChange(t, clk, 0)
   242  	require.Equal(t, time.Second, stopwatch.Elapsed())
   243  
   244  	now.Add(int64(time.Second))
   245  	waitForChange(t, clk, int64(time.Second))
   246  	require.Equal(t, 2*time.Second, stopwatch.Elapsed())
   247  	require.Equal(t, 2*time.Second, stopwatch.Reset())
   248  	require.Equal(t, 0*time.Second, stopwatch.Elapsed())
   249  
   250  	now.Add(int64(time.Second))
   251  	waitForChange(t, clk, int64(2*time.Second))
   252  	require.Equal(t, time.Second, stopwatch.Elapsed())
   253  }
   254  
   255  func waitForChange(t *testing.T, clk *clock.ThrottledClock, prev int64) {
   256  	var (
   257  		done = make(chan struct{})
   258  		stop atomic.Bool
   259  	)
   260  
   261  	go func() {
   262  		defer close(done)
   263  
   264  		for !stop.Load() && clk.Nanotime() == prev {
   265  			time.Sleep(clk.Interval() / 2)
   266  			runtime.Gosched()
   267  		}
   268  	}()
   269  
   270  	wait := math.Max(time.Second, 2*clk.Interval())
   271  	select {
   272  	case <-time.After(wait):
   273  		stop.Store(true)
   274  	case <-done:
   275  	}
   276  
   277  	<-done
   278  	require.NotEqual(t, prev, clk.Nanotime(), "clock did not update")
   279  }