go.mway.dev/chrono@v0.6.1-0.20240126030049-189c5aef20d2/clock/fake_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  	"context"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/stretchr/testify/require"
    30  	"go.mway.dev/chrono"
    31  	"go.mway.dev/chrono/clock"
    32  	"go.uber.org/atomic"
    33  )
    34  
    35  func TestFakeClock_Add(t *testing.T) {
    36  	clk := clock.NewFakeClock()
    37  	requireClockIs(t, 0, clk)
    38  
    39  	// Test moving forward.
    40  	for i := int64(1); i <= 1000; i++ {
    41  		prev := clk.Nanotime()
    42  		clk.Add(time.Duration(i))
    43  		requireClockIs(t, prev+i, clk)
    44  	}
    45  
    46  	// Test moving backward.
    47  	for i := int64(1); i <= 1000; i++ {
    48  		prev := clk.Nanotime()
    49  		clk.Add(-time.Duration(i))
    50  		requireClockIs(t, prev-i, clk)
    51  	}
    52  
    53  	// We should be back where we started.
    54  	requireClockIs(t, 0, clk)
    55  }
    56  
    57  func TestFakeClock_SetTime(t *testing.T) {
    58  	clk := clock.NewFakeClock()
    59  
    60  	for i := int64(1); i <= 1000; i++ {
    61  		clk.SetTime(time.Unix(0, i))
    62  		requireClockIs(t, i, clk)
    63  	}
    64  
    65  	for i := int64(1000); i > 0; i-- {
    66  		clk.SetTime(time.Unix(0, i))
    67  		requireClockIs(t, i, clk)
    68  	}
    69  }
    70  
    71  func TestFakeClock_SetNanotime(t *testing.T) {
    72  	clk := clock.NewFakeClock()
    73  
    74  	for i := int64(1); i <= 1000; i++ {
    75  		clk.SetNanotime(i)
    76  		requireClockIs(t, i, clk)
    77  	}
    78  
    79  	for i := int64(1000); i > 0; i-- {
    80  		clk.SetNanotime(i)
    81  		requireClockIs(t, i, clk)
    82  	}
    83  }
    84  
    85  func TestFakeClock_After(t *testing.T) {
    86  	var (
    87  		clk    = clock.NewFakeClock()
    88  		timerC = clk.After(time.Second)
    89  	)
    90  
    91  	require.NotNil(t, timerC)
    92  	requireNoTick(t, timerC)
    93  
    94  	clk.Add(time.Second)
    95  
    96  	ts := requireTick(t, timerC)
    97  	requireTimeIs(t, int64(time.Second), ts)
    98  
    99  	// Start a new timer. This time, we want to advance the clock once to its
   100  	// expiration, and then once again immediately after, to ensure that the
   101  	// timer's channel only contains the first tick.
   102  	timerC = clk.After(time.Second)
   103  	clk.Add(time.Second)
   104  	clk.Add(time.Second)
   105  
   106  	ts = requireTick(t, timerC)
   107  	requireTimeIs(t, 2*int64(time.Second), ts)
   108  }
   109  
   110  func TestFakeClock_AfterFunc(t *testing.T) {
   111  	var (
   112  		clk   = clock.NewFakeClock()
   113  		calls = atomic.NewInt64(0)
   114  		fn    = func() { calls.Inc() }
   115  		timer = clk.AfterFunc(time.Second, fn)
   116  	)
   117  
   118  	for i := int64(0); i < 10; i++ {
   119  		timer.Reset(time.Second)
   120  		clk.Add(time.Second)
   121  		waitFor(t, time.Second, func() bool {
   122  			return calls.Load() == i+1
   123  		})
   124  	}
   125  }
   126  
   127  func TestFakeClockSince(t *testing.T) {
   128  	var (
   129  		clk   = clock.NewFakeClock()
   130  		since int64
   131  	)
   132  
   133  	for i := int64(1); i < 1000; i++ {
   134  		clk.SetNanotime(i)
   135  		requireClockIs(t, i, clk)
   136  		requireClockSince(t, i, since, clk)
   137  	}
   138  }
   139  
   140  func TestFakeClock_NewTimer(t *testing.T) {
   141  	var (
   142  		clk   = clock.NewFakeClock()
   143  		timer = clk.NewTimer(time.Second)
   144  	)
   145  
   146  	for i := int64(0); i < 10; i++ {
   147  		requireNoTick(t, timer.C)
   148  		clk.Add(time.Second)
   149  		requireTick(t, timer.C)
   150  		require.False(t, timer.Reset(time.Second))
   151  	}
   152  
   153  	// Cause the timer's tick channel to fill, and then tick again.
   154  	clk.Add(time.Second)
   155  
   156  	// Because the last tick wasn't consumed, the reported timestamp will be
   157  	// in the past.
   158  	want := clk.Nanotime()
   159  	timer.Reset(time.Second)
   160  	clk.Add(time.Second)
   161  	ts := requireTick(t, timer.C)
   162  	requireTimeIs(t, want, ts)
   163  
   164  	require.False(t, timer.Stop())
   165  	requireNoTick(t, timer.C)
   166  }
   167  
   168  func TestFakeClock_Timer_Zeroes(t *testing.T) {
   169  	var (
   170  		clk   = clock.NewFakeClock()
   171  		timer *clock.Timer
   172  	)
   173  
   174  	require.NotPanics(t, func() {
   175  		timer = clk.NewTimer(-1)
   176  	})
   177  
   178  	require.NotPanics(t, func() {
   179  		timer = clk.NewTimer(0)
   180  	})
   181  
   182  	requireNoTick(t, timer.C)
   183  
   184  	clk.Add(time.Second)
   185  	requireTick(t, timer.C)
   186  
   187  	// Ensure that resetting the timer will not panic if given a duration <= 0.
   188  	require.NotPanics(t, func() {
   189  		timer.Reset(-1)
   190  	})
   191  	require.NotPanics(t, func() {
   192  		timer.Reset(0)
   193  	})
   194  
   195  	// The timer will still report that it has been stopped, because the clock
   196  	// has not been changed.
   197  	require.True(t, timer.Stop())
   198  }
   199  
   200  func TestFakeClock_NewTicker(t *testing.T) {
   201  	var (
   202  		clk    = clock.NewFakeClock()
   203  		ticker = clk.NewTicker(time.Second)
   204  	)
   205  
   206  	for i := int64(0); i < 10; i++ {
   207  		requireNoTick(t, ticker.C)
   208  		clk.Add(time.Second)
   209  		requireTick(t, ticker.C)
   210  
   211  		if i%2 == 0 {
   212  			ticker.Reset(time.Second)
   213  		}
   214  	}
   215  
   216  	ticker.Stop()
   217  	requireNoTick(t, ticker.C)
   218  
   219  	require.Panics(t, func() {
   220  		clk.NewTicker(-1)
   221  	})
   222  
   223  	require.Panics(t, func() {
   224  		clk.NewTicker(0)
   225  	})
   226  }
   227  
   228  //nolint:gocyclo
   229  func TestFakeClock_Ticker_Goroutine(t *testing.T) {
   230  	var (
   231  		clk   = clock.NewFakeClock()
   232  		ticks = make(chan struct{}, 10)
   233  		ready = make(chan struct{})
   234  		wg    sync.WaitGroup
   235  	)
   236  
   237  	ctx, cancel := context.WithCancel(context.Background())
   238  	defer cancel()
   239  
   240  	for i := 0; i < cap(ticks); i++ {
   241  		wg.Add(1)
   242  		go func() {
   243  			ticker := clk.NewTicker(time.Second)
   244  			defer ticker.Stop()
   245  
   246  			ready <- struct{}{}
   247  
   248  			for {
   249  				select {
   250  				case <-ticker.C:
   251  				case <-ctx.Done():
   252  					return
   253  				}
   254  
   255  				select {
   256  				case ticks <- struct{}{}:
   257  				case <-ctx.Done():
   258  					return
   259  				}
   260  			}
   261  		}()
   262  	}
   263  
   264  	maxWait := time.NewTicker(5 * time.Second)
   265  	defer maxWait.Stop()
   266  
   267  	for i := 0; i < cap(ticks); i++ {
   268  		select {
   269  		case <-ready:
   270  		case <-maxWait.C:
   271  			require.FailNow(t, "timed out waiting for ticker ticks")
   272  		}
   273  	}
   274  
   275  	for i := 0; i < cap(ticks); i++ {
   276  		clk.Add(time.Second)
   277  
   278  		select {
   279  		case <-ticks:
   280  		case <-maxWait.C:
   281  			require.FailNow(t, "timed out waiting for ticker ticks")
   282  		}
   283  	}
   284  }
   285  
   286  func TestFakeClock_Ticker_Zeroes(t *testing.T) {
   287  	var (
   288  		clk    = clock.NewFakeClock()
   289  		ticker = clk.NewTicker(time.Second)
   290  	)
   291  
   292  	requireNoTick(t, ticker.C)
   293  
   294  	clk.Add(time.Second)
   295  	requireTick(t, ticker.C)
   296  
   297  	// Ensure that the ticker will panic if given a duration <= 0.
   298  	require.Panics(t, func() {
   299  		ticker.Reset(-1)
   300  	})
   301  	require.Panics(t, func() {
   302  		ticker.Reset(0)
   303  	})
   304  }
   305  
   306  func TestFakeClock_Tick(t *testing.T) {
   307  	var (
   308  		clk     = clock.NewFakeClock()
   309  		tickerC = clk.Tick(time.Second)
   310  	)
   311  
   312  	for i := int64(0); i < 10; i++ {
   313  		requireNoTick(t, tickerC)
   314  		clk.Add(time.Second)
   315  		requireTick(t, tickerC)
   316  	}
   317  
   318  	require.Panics(t, func() {
   319  		clk.Tick(-1)
   320  	})
   321  
   322  	require.Panics(t, func() {
   323  		clk.Tick(0)
   324  	})
   325  }
   326  
   327  func TestFakeClock_Sleep(t *testing.T) {
   328  	var (
   329  		clk       = clock.NewFakeClock()
   330  		sleepdone = make(chan struct{})
   331  	)
   332  	go func() {
   333  		defer close(sleepdone)
   334  		clk.Sleep(time.Second)
   335  	}()
   336  
   337  	for i := 0; i < 10; i++ {
   338  		clk.Add(time.Second)
   339  		time.Sleep(time.Millisecond)
   340  	}
   341  
   342  	select {
   343  	case <-sleepdone:
   344  	case <-time.After(time.Second):
   345  		require.Fail(t, "sleep did not wake")
   346  	}
   347  }
   348  
   349  func TestFakeClock_InterleavedTimers(t *testing.T) {
   350  	var (
   351  		clk    = clock.NewFakeClock()
   352  		timer2 = clk.NewTimer(3 * time.Second)
   353  		timer3 = clk.NewTimer(1 * time.Second)
   354  		timer1 = clk.NewTimer(2 * time.Second)
   355  	)
   356  
   357  	timer2.Reset(2 * time.Second)
   358  	timer3.Reset(3 * time.Second)
   359  	timer1.Reset(1 * time.Second)
   360  
   361  	// n.b. Handle channels in reverse order. We defined them above in order of
   362  	//      descending duration, but since ticking is synchonous, evaluate them
   363  	//      in sorted (ascending) order to ensure that the internals are doing
   364  	//      what we expect them to.
   365  	for i := int64(0); i < 10; i++ {
   366  		requireNoTick(t, timer1.C)
   367  		requireNoTick(t, timer2.C)
   368  		requireNoTick(t, timer3.C)
   369  
   370  		clk.Add(time.Second)
   371  		requireTick(t, timer1.C)
   372  		requireNoTick(t, timer2.C)
   373  		requireNoTick(t, timer3.C)
   374  
   375  		clk.Add(time.Second)
   376  		requireNoTick(t, timer1.C)
   377  		requireTick(t, timer2.C)
   378  		requireNoTick(t, timer3.C)
   379  
   380  		clk.Add(time.Second)
   381  		requireNoTick(t, timer1.C)
   382  		requireNoTick(t, timer2.C)
   383  		requireTick(t, timer3.C)
   384  
   385  		require.False(t, timer1.Reset(1*time.Second))
   386  		require.False(t, timer2.Reset(2*time.Second))
   387  		require.False(t, timer3.Reset(3*time.Second))
   388  	}
   389  
   390  	// Ensure that all timers covered by the time change fire.
   391  	clk.Add(3 * time.Second)
   392  	requireTick(t, timer1.C)
   393  	requireTick(t, timer2.C)
   394  	requireTick(t, timer3.C)
   395  
   396  	require.False(t, timer1.Stop())
   397  	require.False(t, timer2.Stop())
   398  	require.False(t, timer3.Stop())
   399  
   400  	requireNoTick(t, timer1.C)
   401  	requireNoTick(t, timer2.C)
   402  	requireNoTick(t, timer3.C)
   403  }
   404  
   405  func TestFakeClock_ManyTimers(t *testing.T) {
   406  	var (
   407  		clk    = clock.NewFakeClock()
   408  		timers []*clock.Timer
   409  	)
   410  
   411  	// for i := 0; i < 1<<10; i++ {
   412  	for i := 0; i < 5; i++ {
   413  		timers = append(timers, clk.NewTimer(time.Second))
   414  	}
   415  
   416  	for i := int64(0); i < 10; i++ {
   417  		for j := 0; j < len(timers); j++ {
   418  			requireNoTick(t, timers[j].C)
   419  		}
   420  
   421  		clk.Add(time.Second)
   422  
   423  		for j := 0; j < len(timers); j++ {
   424  			requireTick(t, timers[j].C)
   425  			require.False(t, timers[j].Reset(time.Second))
   426  		}
   427  	}
   428  
   429  	for j := 0; j < len(timers); j++ {
   430  		require.True(t, timers[j].Stop())
   431  		requireNoTick(t, timers[j].C)
   432  	}
   433  }
   434  
   435  func TestFakeClock_Timer_DoubleStop(t *testing.T) {
   436  	cases := [][]time.Duration{
   437  		{time.Second, 5 * time.Second},
   438  		{5 * time.Second, time.Second},
   439  		{time.Second, time.Second},
   440  		{time.Second, 2 * time.Second, 3 * time.Second},
   441  		{3 * time.Second, 2 * time.Second, time.Second},
   442  	}
   443  
   444  	var (
   445  		clk    = clock.NewFakeClock()
   446  		timers []*clock.Timer
   447  	)
   448  
   449  	for _, durations := range cases {
   450  		for _, dur := range durations {
   451  			timers = append(timers, clk.NewTimer(dur))
   452  		}
   453  
   454  		for _, timer := range timers {
   455  			require.True(t, timer.Stop())
   456  			require.False(t, timer.Stop())
   457  		}
   458  
   459  		timers = nil
   460  	}
   461  }
   462  
   463  func TestFakeClock_Stopwatch(t *testing.T) {
   464  	var (
   465  		clk       = clock.NewFakeClock()
   466  		stopwatch = clk.NewStopwatch()
   467  	)
   468  
   469  	require.Equal(t, 0*time.Second, stopwatch.Elapsed())
   470  
   471  	clk.Add(time.Second)
   472  	require.Equal(t, time.Second, stopwatch.Elapsed())
   473  
   474  	clk.Add(time.Second)
   475  	require.Equal(t, 2*time.Second, stopwatch.Elapsed())
   476  	require.Equal(t, 2*time.Second, stopwatch.Reset())
   477  	require.Equal(t, 0*time.Second, stopwatch.Elapsed())
   478  
   479  	clk.Add(time.Second)
   480  	require.Equal(t, time.Second, stopwatch.Elapsed())
   481  }
   482  
   483  func requireClockSince(t *testing.T, expect int64, since int64, clk *clock.FakeClock) {
   484  	require.EqualValues(t, expect, clk.Since(time.Unix(0, since)))
   485  	require.EqualValues(t, expect, clk.SinceNanotime(since))
   486  }
   487  
   488  func requireClockIs(t *testing.T, expect int64, clk *clock.FakeClock) {
   489  	requireTimeIs(t, expect, clk.Now())
   490  	requireNanotimeIs(t, expect, clk.Nanotime())
   491  }
   492  
   493  func requireNanotimeIs(t *testing.T, expect int64, ns int64) {
   494  	require.Equal(t, expect, ns)
   495  }
   496  
   497  func requireTimeIs(t *testing.T, expect int64, ts time.Time) {
   498  	require.EqualValues(t, expect, ts.UnixNano())
   499  }
   500  
   501  func requireTick(t *testing.T, ch <-chan time.Time) (ts time.Time) {
   502  	select {
   503  	case ts = <-ch:
   504  	case <-time.After(time.Second):
   505  		require.Fail(t, "timed out waiting for tick")
   506  	}
   507  	return
   508  }
   509  
   510  func requireNoTick(t *testing.T, ch <-chan time.Time) {
   511  	select {
   512  	case <-ch:
   513  		require.Fail(t, "unexpected tick")
   514  	default:
   515  	}
   516  }
   517  
   518  func waitFor(t *testing.T, d time.Duration, f func() bool) {
   519  	start := chrono.Nanotime()
   520  	for !f() {
   521  		if time.Duration(chrono.Nanotime()-start) >= d {
   522  			require.Fail(t, "timeout", "waited for %v", d)
   523  		}
   524  		time.Sleep(d >> 8)
   525  	}
   526  }