go.mway.dev/chrono@v0.6.1-0.20240126030049-189c5aef20d2/clock/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  	"errors"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/stretchr/testify/require"
    29  	"go.mway.dev/chrono/clock"
    30  	"go.uber.org/atomic"
    31  )
    32  
    33  var (
    34  	_withNanotimeFunc = clock.WithNanotimeFunc(clock.DefaultNanotimeFunc())
    35  	_withTimeFunc     = clock.WithTimeFunc(clock.DefaultTimeFunc())
    36  )
    37  
    38  func TestNewClock(t *testing.T) {
    39  	cases := map[string]struct {
    40  		opts      []clock.Option
    41  		expectErr bool
    42  	}{
    43  		"no opts": {
    44  			opts:      nil,
    45  			expectErr: false,
    46  		},
    47  		"default opts": {
    48  			opts:      []clock.Option{clock.DefaultOptions()},
    49  			expectErr: false,
    50  		},
    51  		"with nanotime func": {
    52  			opts:      []clock.Option{_withNanotimeFunc},
    53  			expectErr: false,
    54  		},
    55  		"with time func": {
    56  			opts:      []clock.Option{_withTimeFunc},
    57  			expectErr: false,
    58  		},
    59  		"with nanotime and time funcs": {
    60  			opts: []clock.Option{
    61  				_withNanotimeFunc,
    62  				_withTimeFunc,
    63  			},
    64  			expectErr: false,
    65  		},
    66  	}
    67  
    68  	for name, tt := range cases {
    69  		t.Run(name, func(t *testing.T) {
    70  			clk, err := clock.NewClock(tt.opts...)
    71  			if tt.expectErr {
    72  				require.Error(t, err)
    73  				require.Nil(t, clk)
    74  			} else {
    75  				require.NoError(t, err)
    76  				require.NotNil(t, clk)
    77  			}
    78  		})
    79  	}
    80  }
    81  
    82  func TestNewClock_Funcs(t *testing.T) {
    83  	var (
    84  		nanotimeFunc = func() int64 { return 123 }
    85  		timeFunc     = func() time.Time { return time.Unix(0, 456) }
    86  		cases        = map[string]struct {
    87  			opts           []clock.Option
    88  			expectNanotime int64
    89  		}{
    90  			"with nanotime func": {
    91  				opts:           []clock.Option{clock.WithNanotimeFunc(nanotimeFunc)},
    92  				expectNanotime: nanotimeFunc(),
    93  			},
    94  			"with nanotime func options": {
    95  				opts: []clock.Option{clock.Options{
    96  					NanotimeFunc: nanotimeFunc,
    97  				}},
    98  				expectNanotime: nanotimeFunc(),
    99  			},
   100  			"with time func": {
   101  				opts:           []clock.Option{clock.WithTimeFunc(timeFunc)},
   102  				expectNanotime: timeFunc().UnixNano(),
   103  			},
   104  			"with time func options": {
   105  				opts: []clock.Option{clock.Options{
   106  					TimeFunc: timeFunc,
   107  				}},
   108  				expectNanotime: timeFunc().UnixNano(),
   109  			},
   110  			"with nanotime and time funcs": {
   111  				opts: []clock.Option{
   112  					clock.WithTimeFunc(timeFunc),
   113  					clock.WithNanotimeFunc(nanotimeFunc),
   114  				},
   115  				expectNanotime: nanotimeFunc(),
   116  			},
   117  		}
   118  	)
   119  
   120  	for name, tt := range cases {
   121  		t.Run(name, func(t *testing.T) {
   122  			clk := newTestClock(t, tt.opts...)
   123  			require.Equal(t, tt.expectNanotime, clk.Nanotime())
   124  		})
   125  	}
   126  }
   127  
   128  func TestMustClock(t *testing.T) {
   129  	require.Panics(t, func() {
   130  		clock.MustClock(nil, errors.New("error"))
   131  	})
   132  
   133  	require.NotPanics(t, func() {
   134  		clk := clock.NewFakeClock()
   135  		require.Equal(t, clk, clock.MustClock(clk, nil))
   136  	})
   137  }
   138  
   139  func TestSpecializedClockConstructors(t *testing.T) {
   140  	cases := map[string]struct {
   141  		clock clock.Clock
   142  	}{
   143  		"NewMonotonicClock": {
   144  			clock: clock.NewMonotonicClock(),
   145  		},
   146  		"NewWallClock": {
   147  			clock: clock.NewWallClock(),
   148  		},
   149  	}
   150  
   151  	for name, tt := range cases {
   152  		t.Run(name, func(t *testing.T) {
   153  			cur := tt.clock.Nanotime()
   154  			for i := 0; i < 100; i++ {
   155  				time.Sleep(time.Millisecond)
   156  				now := tt.clock.Nanotime()
   157  				require.Greater(t, now, cur)
   158  				cur = now
   159  			}
   160  		})
   161  	}
   162  }
   163  
   164  func TestClock_NewTimer(t *testing.T) {
   165  	cases := map[string]struct {
   166  		opts []clock.Option
   167  	}{
   168  		"nanotime func": {
   169  			opts: []clock.Option{_withNanotimeFunc},
   170  		},
   171  		"time func": {
   172  			opts: []clock.Option{_withTimeFunc},
   173  		},
   174  	}
   175  
   176  	for name, tt := range cases {
   177  		t.Run(name, func(t *testing.T) {
   178  			var (
   179  				clk   = newTestClock(t, tt.opts...)
   180  				timer = clk.NewTimer(time.Millisecond)
   181  			)
   182  
   183  			requireTick(t, timer.C)
   184  			require.False(t, timer.Stop())
   185  			timer.Reset(time.Second)
   186  			require.True(t, timer.Stop())
   187  		})
   188  	}
   189  }
   190  
   191  func TestClock_NewTicker(t *testing.T) {
   192  	cases := map[string]struct {
   193  		opts []clock.Option
   194  	}{
   195  		"nanotime func": {
   196  			opts: []clock.Option{_withNanotimeFunc},
   197  		},
   198  		"time func": {
   199  			opts: []clock.Option{_withTimeFunc},
   200  		},
   201  	}
   202  
   203  	for name, tt := range cases {
   204  		t.Run(name, func(t *testing.T) {
   205  			var (
   206  				clk    = newTestClock(t, tt.opts...)
   207  				ticker = clk.NewTicker(time.Millisecond)
   208  			)
   209  			defer ticker.Stop()
   210  
   211  			requireTick(t, ticker.C)
   212  			ticker.Stop()
   213  			ticker.Reset(time.Millisecond)
   214  			requireTick(t, ticker.C)
   215  		})
   216  	}
   217  }
   218  
   219  func TestClock_Since(t *testing.T) {
   220  	cases := map[string]struct {
   221  		opts []clock.Option
   222  	}{
   223  		"nanotime func": {
   224  			opts: []clock.Option{_withNanotimeFunc},
   225  		},
   226  		"time func": {
   227  			opts: []clock.Option{_withTimeFunc},
   228  		},
   229  	}
   230  
   231  	for name, tt := range cases {
   232  		t.Run(name, func(t *testing.T) {
   233  			clk := newTestClock(t, tt.opts...)
   234  			require.InEpsilon(
   235  				t,
   236  				clk.Now().UnixNano(),
   237  				clk.Since(time.Unix(0, 0)),
   238  				float64(time.Second),
   239  			)
   240  			require.InEpsilon(
   241  				t,
   242  				clk.Nanotime(),
   243  				clk.SinceNanotime(0),
   244  				float64(time.Second),
   245  			)
   246  		})
   247  	}
   248  }
   249  
   250  func TestClock_After(t *testing.T) {
   251  	cases := map[string]struct {
   252  		opts []clock.Option
   253  	}{
   254  		"nanotime func": {
   255  			opts: []clock.Option{_withNanotimeFunc},
   256  		},
   257  		"time func": {
   258  			opts: []clock.Option{_withTimeFunc},
   259  		},
   260  	}
   261  
   262  	for name, tt := range cases {
   263  		t.Run(name, func(t *testing.T) {
   264  			clk := newTestClock(t, tt.opts...)
   265  			requireTick(t, clk.After(time.Millisecond))
   266  
   267  			var called atomic.Bool
   268  			clk.AfterFunc(time.Millisecond, func() { called.Store(true) })
   269  			waitFor(t, time.Second, called.Load)
   270  		})
   271  	}
   272  }
   273  
   274  func TestClock_Tick(t *testing.T) {
   275  	cases := map[string]struct {
   276  		opts []clock.Option
   277  	}{
   278  		"nanotime func": {
   279  			opts: []clock.Option{_withNanotimeFunc},
   280  		},
   281  		"time func": {
   282  			opts: []clock.Option{_withTimeFunc},
   283  		},
   284  	}
   285  
   286  	for name, tt := range cases {
   287  		t.Run(name, func(t *testing.T) {
   288  			var (
   289  				clk     = newTestClock(t, tt.opts...)
   290  				tickerC = clk.Tick(time.Millisecond)
   291  			)
   292  
   293  			for i := 0; i < 10; i++ {
   294  				requireTick(t, tickerC)
   295  			}
   296  		})
   297  	}
   298  }
   299  
   300  func TestClock_Sleep(t *testing.T) {
   301  	cases := map[string]struct {
   302  		name string
   303  		opts []clock.Option
   304  	}{
   305  		"nanotime func": {
   306  			opts: []clock.Option{_withNanotimeFunc},
   307  		},
   308  		"time func": {
   309  			opts: []clock.Option{_withTimeFunc},
   310  		},
   311  	}
   312  
   313  	for _, tt := range cases {
   314  		t.Run(tt.name, func(t *testing.T) {
   315  			var (
   316  				clk       = newTestClock(t, tt.opts...)
   317  				sleepdone = make(chan struct{})
   318  			)
   319  
   320  			go func() {
   321  				defer close(sleepdone)
   322  				clk.Sleep(50 * time.Millisecond)
   323  			}()
   324  
   325  			start := clk.Nanotime()
   326  			select {
   327  			case <-time.After(time.Second):
   328  				require.Fail(t, "did not wake")
   329  			case <-sleepdone:
   330  				since := clk.SinceNanotime(start)
   331  				require.InEpsilon(
   332  					t,
   333  					50*time.Millisecond,
   334  					since,
   335  					float64(25*time.Millisecond),
   336  				)
   337  			}
   338  		})
   339  	}
   340  }
   341  
   342  func TestClock_Stopwatch(t *testing.T) {
   343  	var (
   344  		cases = map[string]struct {
   345  			giveClock clock.Clock
   346  		}{
   347  			"nanotime func": {
   348  				giveClock: clock.NewMonotonicClock(),
   349  			},
   350  			"time func": {
   351  				giveClock: clock.NewWallClock(),
   352  			},
   353  		}
   354  		waitElapse = func(s *clock.Stopwatch, d time.Duration) time.Duration {
   355  			for {
   356  				if x := s.Elapsed(); x >= d {
   357  					return d
   358  				}
   359  				time.Sleep(10 * time.Millisecond)
   360  			}
   361  		}
   362  	)
   363  
   364  	for name, tt := range cases {
   365  		t.Run(name, func(t *testing.T) {
   366  			var (
   367  				stopwatch = tt.giveClock.NewStopwatch()
   368  				elapsed   = waitElapse(stopwatch, 10*time.Millisecond)
   369  			)
   370  
   371  			require.GreaterOrEqual(t, elapsed, 10*time.Millisecond)
   372  			require.GreaterOrEqual(t, stopwatch.Reset(), elapsed)
   373  			require.Less(t, stopwatch.Elapsed(), 10*time.Millisecond)
   374  
   375  			elapsed = waitElapse(stopwatch, 10*time.Millisecond)
   376  			require.GreaterOrEqual(t, elapsed, 10*time.Millisecond)
   377  			require.GreaterOrEqual(t, stopwatch.Reset(), elapsed)
   378  		})
   379  	}
   380  }
   381  
   382  func newTestClock(t *testing.T, opts ...clock.Option) clock.Clock {
   383  	clk, err := clock.NewClock(opts...)
   384  	require.NoError(t, err)
   385  	return clk
   386  }