From 182b6e3d14a43a1e87a614e7cc1040f559215b7f Mon Sep 17 00:00:00 2001 From: Adam Luzsi Date: Wed, 5 Jun 2024 22:07:59 +0200 Subject: [PATCH] introducing clock.Ticker, a drop-in replacement for time.Ticker clock.Ticker is just like time.Ticker, except it supports time travelling, and adapts the ticking time according to that. --- clock/Clock.go | 161 +++++++++++++++-- clock/Clock_test.go | 318 ++++++++++++++++++++++++++++------ clock/examples_test.go | 10 +- clock/internal/chronos.go | 25 ++- clock/internal/notify.go | 36 ++-- clock/timecop/timecop.go | 4 +- clock/timecop/timecop_test.go | 36 ++-- examples_test.go | 10 +- pp/PP.go | 4 + random/Unique.go | 4 +- 10 files changed, 497 insertions(+), 111 deletions(-) diff --git a/clock/Clock.go b/clock/Clock.go index e122ff5..689d2fe 100644 --- a/clock/Clock.go +++ b/clock/Clock.go @@ -1,38 +1,175 @@ package clock import ( + "sync" "time" "go.llib.dev/testcase/clock/internal" ) -func TimeNow() time.Time { - return internal.GetTime().Local() +// Now returns the current time. +// Time returned by Now is affected by time travelling. +func Now() time.Time { + return internal.TimeNow().Local() } +// TimeNow is an alias for clock.Now +func TimeNow() time.Time { return Now() } + func Sleep(d time.Duration) { <-After(d) } -func After(d time.Duration) <-chan time.Time { - ch := make(chan time.Time) +func After(d time.Duration) <-chan struct{} { + startedAt := internal.TimeNow() + ch := make(chan struct{}) if d == 0 { close(ch) return ch } - startedAt := internal.GetTime() go func() { - wait: - for { + timeTravel := make(chan struct{}) + defer internal.Notify(timeTravel)() + var onWait = func() (_restart bool) { + c, td := after(internal.RemainingDuration(startedAt, d)) + defer td() select { - case <-internal.Listen(): - continue wait - case <-time.After(internal.RemainingDuration(startedAt, d)): - break wait + case <-c: + return false + case <-timeTravel: + return true } } - ch <- TimeNow() + for onWait() { + } close(ch) }() return ch } + +func NewTicker(d time.Duration) *Ticker { + ticker := &Ticker{d: d} + ticker.init() + return ticker +} + +type Ticker struct { + C chan time.Time + + d time.Duration + + onInit sync.Once + lock sync.RWMutex + done chan struct{} + pulse chan struct{} + ticker *time.Ticker + lastTickedAt time.Time +} + +func (t *Ticker) init() { + t.onInit.Do(func() { + t.C = make(chan time.Time) + t.done = make(chan struct{}) + t.pulse = make(chan struct{}) + t.ticker = time.NewTicker(t.getScaledDuration()) + t.updateLastTickedAt() + go func() { + timeTravel := make(chan struct{}) + defer internal.Notify(timeTravel)() + for { + if !t.ticking(timeTravel, t.ticker.C) { + break + } + } + }() + }) +} + +func (t *Ticker) ticking(timeTravel <-chan struct{}, tick <-chan time.Time) bool { + select { + case <-t.done: + return false + + case <-timeTravel: // on time travel, we reset the ticker according to the new time + defer t.resetTicker() + c, td := after(internal.RemainingDuration(t.getLastTickedAt(), t.getRealDuration())) + defer td() + return t.ticking(timeTravel, c) // wait the remaining time from the current tick + + case <-tick: // on timeout, we notify the listener + now := t.updateLastTickedAt() + t.C <- now + return true + } +} + +// Stop turns off a ticker. After Stop, no more ticks will be sent. +// Stop does not close the channel, to prevent a concurrent goroutine +// reading from the channel from seeing an erroneous "tick". +func (t *Ticker) Stop() { + t.init() + close(t.done) + t.ticker.Stop() + t.onInit = sync.Once{} +} + +func (t *Ticker) Reset(d time.Duration) { + t.init() + t.setDuration(d) + t.resetTicker() +} + +func (t *Ticker) resetTicker() { + d := t.getScaledDuration() + if d == 0 { // zero is not an acceptable tick time + d = time.Nanosecond + } + t.ticker.Reset(d) +} + +// getScaledDuration returns the time duration that is altered by time +func (t *Ticker) getScaledDuration() time.Duration { + return internal.ScaledDuration(t.getRealDuration()) +} + +func (t *Ticker) getRealDuration() time.Duration { + t.lock.RLock() + defer t.lock.RUnlock() + return t.d +} + +func (t *Ticker) setDuration(d time.Duration) { + t.lock.Lock() + defer t.lock.Unlock() + t.d = d +} + +func (t *Ticker) getLastTickedAt() time.Time { + t.lock.RLock() + defer t.lock.RUnlock() + return t.lastTickedAt +} + +func (t *Ticker) updateLastTickedAt() time.Time { + t.lock.RLock() + defer t.lock.RUnlock() + t.lastTickedAt = Now() + return t.lastTickedAt +} + +func after(d time.Duration) (<-chan time.Time, func()) { + if d == 0 { + var ch = make(chan time.Time) + close(ch) + return ch, func() {} + } + timer := time.NewTimer(d) + return timer.C, func() { + if !timer.Stop() { + select { + case <-timer.C: // drain channel to unlock the resource + default: + } + } + } +} diff --git a/clock/Clock_test.go b/clock/Clock_test.go index e82e1b7..9446f2b 100644 --- a/clock/Clock_test.go +++ b/clock/Clock_test.go @@ -2,6 +2,8 @@ package clock_test import ( "context" + "runtime" + "sync/atomic" "testing" "time" @@ -13,20 +15,25 @@ import ( "go.llib.dev/testcase/clock/timecop" ) -const bufferDuration = 50 * time.Millisecond +const BufferTime = 50 * time.Millisecond -func TestTimeNow(t *testing.T) { +func ExampleNow() { + now := clock.Now() + _ = now +} + +func TestNow(t *testing.T) { s := testcase.NewSpec(t) s.HasSideEffect() - act := func(t *testcase.T) time.Time { - return clock.TimeNow() + act := func(*testcase.T) time.Time { + return clock.Now() } s.Test("normally it just returns the current time", func(t *testcase.T) { timeNow := time.Now() clockNow := act(t) - t.Must.True(timeNow.Add(-1 * bufferDuration).Before(clockNow)) + t.Must.True(timeNow.Add(-1 * BufferTime).Before(clockNow)) }, testcase.Flaky(time.Second)) s.When("Timecop is moving in time", func(s *testcase.Spec) { @@ -35,7 +42,7 @@ func TestTimeNow(t *testing.T) { }) s.Then("the time it just returns the current time", func(t *testcase.T) { - t.Must.True(time.Hour-bufferDuration <= act(t).Sub(time.Now())) + t.Must.True(time.Hour-BufferTime <= time.Until(act(t))) }) s.Then("time is still moving forward", func(t *testcase.T) { @@ -95,7 +102,7 @@ func TestSleep(t *testing.T) { } s.Test("normally it just sleeps normally", func(t *testcase.T) { - t.Must.True(act(t) <= duration.Get(t)+bufferDuration) + t.Must.True(act(t) <= duration.Get(t)+BufferTime) }) s.When("Timecop change the flow of time", func(s *testcase.Spec) { @@ -106,7 +113,7 @@ func TestSleep(t *testing.T) { }) s.Then("the time it just returns the current time", func(t *testcase.T) { - expectedMaximumDuration := time.Duration(float64(duration.Get(t))/multi.Get(t)) + bufferDuration + expectedMaximumDuration := time.Duration(float64(duration.Get(t))/multi.Get(t)) + BufferTime sleptFor := act(t) t.Log("expectedMaximumDuration:", expectedMaximumDuration.String()) t.Log("sleptFor:", sleptFor.String()) @@ -126,7 +133,7 @@ func TestSleep(t *testing.T) { select { case <-ctx.Done(): t.Fatal("was not expected to finish already") - case <-time.After(bufferDuration): + case <-time.After(BufferTime): // OK } @@ -134,7 +141,7 @@ func TestSleep(t *testing.T) { select { case <-ctx.Done(): // OK - case <-time.After(bufferDuration): + case <-time.After(BufferTime): t.Fatal("was expected to finish already") } }) @@ -145,23 +152,30 @@ func TestAfter(t *testing.T) { s.HasSideEffect() duration := testcase.Let(s, func(t *testcase.T) time.Duration { - return time.Duration(t.Random.IntB(24, 42)) * time.Microsecond + return time.Duration(t.Random.IntB(24, 42)) * time.Millisecond }) - act := func(t *testcase.T) (time.Time, time.Duration) { - before := time.Now() - out := <-clock.After(duration.Get(t)) - after := time.Now() - return out, after.Sub(before) + act := func(t *testcase.T, ctx context.Context) { + select { + case <-clock.After(duration.Get(t)): + case <-ctx.Done(): // assertion already finished + case <-t.Done(): // test already finished + } } - s.Test("normally it just sleeps normally", func(t *testcase.T) { - before := time.Now() - gotTime, gotDuration := act(t) - t.Must.True(gotDuration <= duration.Get(t)+bufferDuration) - t.Must.True(before.Before(gotTime)) + buftime := testcase.Let(s, func(t *testcase.T) time.Duration { + return time.Duration(float64(duration.Get(t)) * 0.2) }) - s.When("Timecop change the flow of time", func(s *testcase.Spec) { + s.Test("normally it just sleeps normally for the duration of the time", func(t *testcase.T) { + assert.NotWithin(t, duration.Get(t)-buftime.Get(t), func(ctx context.Context) { + act(t, ctx) + }) + assert.Within(t, duration.Get(t)+buftime.Get(t), func(ctx context.Context) { + act(t, ctx) + }) + }) + + s.When("Timecop change the flow of time's speed", func(s *testcase.Spec) { speed := testcase.LetValue[float64](s, 2) s.Before(func(t *testcase.T) { @@ -169,34 +183,13 @@ func TestAfter(t *testing.T) { }) alteredDuration := testcase.Let(s, func(t *testcase.T) time.Duration { - return time.Duration(float64(duration.Get(t))/speed.Get(t)) + bufferDuration + return time.Duration(float64(duration.Get(t))/speed.Get(t)) + BufferTime }) s.Then("clock.After goes faster", func(t *testcase.T) { - _, d := act(t) - t.Must.True(d <= alteredDuration.Get(t)) - }) - - s.Test("returned time is relatively calculated to the flow of time", func(t *testcase.T) { - before := time.Now().Add(alteredDuration.Get(t)) - gotTime, _ := act(t) - t.Must.True(before.Add(-1 * bufferDuration).Before(gotTime)) - }) - }) - - s.When("Timecop travel in time", func(s *testcase.Spec) { - date := testcase.Let(s, func(t *testcase.T) time.Time { - return t.Random.Time() - }) - s.Before(func(t *testcase.T) { - timecop.Travel(t, date.Get(t)) - }) - - s.Then("returned time will represent this", func(t *testcase.T) { - finishedAt, _ := act(t) - - t.Must.True(date.Get(t).Before(finishedAt)) - t.Must.True(date.Get(t).Add(duration.Get(t) + bufferDuration).After(finishedAt)) + assert.Within(t, alteredDuration.Get(t)+time.Millisecond, func(ctx context.Context) { + act(t, ctx) + }) }) }) @@ -204,7 +197,18 @@ func TestAfter(t *testing.T) { duration := time.Hour ch := clock.After(duration) - timecop.Travel(t, time.Second) + t.Log("before any travelling, just a regular check if the time is done") + select { + case <-ch: + t.Fatal("it was not expected that clock.After finished already") + default: + // OK + } + + t.Log("travel takes us to a time where the original duration is not yet reached") + + timecop.Travel(t, duration/2) + select { case <-ch: t.Fatal("it was not expected that clock.After is already done since we moved less forward than the total duration") @@ -212,14 +216,16 @@ func TestAfter(t *testing.T) { // OK } - timecop.Travel(t, duration+bufferDuration) + t.Log("travel takes us after the original duration already reached") + timecop.Travel(t, (duration/2)+BufferTime) select { case <-ch: // OK - case <-time.After(time.Second): + case <-time.After(3 * time.Second): t.Fatal("clock.After should have finished already its work after travel that went more forward as the duration") } + }) //Ω, testcase.Flaky(5*time.Second)) s.Test("no matter what, when the wait time is zero, clock.After returns instantly", func(t *testcase.T) { @@ -246,9 +252,219 @@ func Test_race(t *testing.T) { timecop.SetSpeed(t, 1) } read := func() { - clock.TimeNow() + clock.Now() clock.Sleep(time.Millisecond) clock.After(time.Millisecond) } testcase.Race(write, read, read, read, read) } + +func TestNewTicker(t *testing.T) { + const failureRateMultiplier = 0.80 + s := testcase.NewSpec(t) + + duration := testcase.Let[time.Duration](s, nil) + ticker := testcase.Let(s, func(t *testcase.T) *clock.Ticker { + ticker := clock.NewTicker(duration.Get(t)) + t.Defer(ticker.Stop) + return ticker + }) + + s.Test("E2E", func(t *testcase.T) { + duration.Set(t, time.Duration(t.Random.IntBetween(int(time.Minute), int(time.Hour)))) + var ( + now int64 + done = make(chan struct{}) + ) + defer close(done) + go func() { + select { + case at := <-ticker.Get(t).C: + t.Log("ticker ticked") + atomic.AddInt64(&now, at.Unix()) + case <-done: + return + } + }() + + t.Log("normal scenario, no tick yet expected") + time.Sleep(time.Millisecond) + runtime.Gosched() + assert.Empty(t, atomic.LoadInt64(&now), "no tick expected yet") + + t.Log("time travel to the future, but before the tick is suppose to happen") + timecop.Travel(t, duration.Get(t)/2) // travel to a time in the future where the ticker is still not fired + runtime.Gosched() + assert.Empty(t, atomic.LoadInt64(&now), "tick is still not expected") + + t.Log("time travel after the ") + beforeTravel := clock.Now() + timecop.Travel(t, (duration.Get(t)/2)+time.Nanosecond) // travel to a time, where the ticker should fire + runtime.Gosched() + + assert.Eventually(t, time.Second, func(t assert.It) { + got := atomic.LoadInt64(&now) + t.Must.NotEmpty(got) + t.Must.True(beforeTravel.Unix() <= got, "tick is expected at this point") + }) + }) + + s.Test("ticks continously", func(t *testcase.T) { + duration.Set(t, time.Second/100) + + var ( + ticks int64 + done = make(chan struct{}) + ) + defer close(done) + go func() { + for { + select { + case <-ticker.Get(t).C: + atomic.AddInt64(&ticks, 1) + case <-done: + return + } + } + }() + + time.Sleep(time.Second / 4) + assert.True(t, 100/4 <= atomic.LoadInt64(&ticks)) + }) + + s.Test("duration is scaled", func(t *testcase.T) { + timecop.SetSpeed(t, 100) // 100x times faster + duration.Set(t, time.Second) + + var ( + ticks int64 + done = make(chan struct{}) + ) + defer close(done) + go func() { + for { + select { + case <-ticker.Get(t).C: + atomic.AddInt64(&ticks, 1) + case <-done: + return + } + } + }() + + time.Sleep(time.Second / 4) + assert.True(t, 100/4 <= atomic.LoadInt64(&ticks)) + }) + + s.Test("duration is scaled midflight", func(t *testcase.T) { + duration.Set(t, time.Second/100) + + var ( + ticks int64 + done = make(chan struct{}) + ) + defer close(done) + go func() { + for { + select { + case <-ticker.Get(t).C: + atomic.AddInt64(&ticks, 1) + case <-done: + return + } + } + }() + + t.Log("ticks:", atomic.LoadInt64(&ticks)) + time.Sleep(time.Second/4 + time.Microsecond) + runtime.Gosched() + var expectedTickCount int64 = 100 / 4 * failureRateMultiplier + t.Log("exp:", expectedTickCount, "got:", atomic.LoadInt64(&ticks)) + assert.True(t, expectedTickCount <= atomic.LoadInt64(&ticks)) + + timecop.SetSpeed(t, 1000) // 100x times faster + time.Sleep(time.Second/4 + time.Microsecond) + runtime.Gosched() + expectedTickCount += 100 / 4 * 1000 * failureRateMultiplier + t.Log("exp:", expectedTickCount, "got:", atomic.LoadInt64(&ticks)) + assert.True(t, expectedTickCount <= atomic.LoadInt64(&ticks)) + }) + + t.Run("race", func(t *testing.T) { + ticker := clock.NewTicker(time.Minute) + + testcase.Race( + func() { + select { + case <-ticker.C: + case <-clock.After(time.Second): + } + }, + func() { + ticker.Reset(time.Minute) + }, + func() { + <-clock.After(time.Second) + ticker.Stop() + }, + ) + }) +} + +func Test_spike_timeTicker(t *testing.T) { + ticker := time.NewTicker(time.Second / 4) + + done := make(chan struct{}) + defer close(done) + go func() { + for { + select { + case <-ticker.C: + t.Log("ticked") + case <-done: + return + } + } + }() + + t.Log("4 expected on sleep") + time.Sleep(time.Second + time.Microsecond) + + t.Log("now we reset and we expect 8 tick on sleep") + ticker.Reset(time.Second / 8) + time.Sleep(time.Second + time.Microsecond) + +} + +func Test_spike_clockTicker(t *testing.T) { + ticker := clock.NewTicker(time.Second / 4) + + done := make(chan struct{}) + defer close(done) + go func() { + for { + select { + case <-ticker.C: + t.Log("ticked") + case <-done: + return + } + } + }() + + t.Log("4 expected on sleep") + time.Sleep(time.Second + time.Microsecond) + + t.Log("now we reset and we expect 8 tick on sleep") + ticker.Reset(time.Second / 8) + time.Sleep(time.Second + time.Microsecond) + + t.Log("now time sped up, and where 4 would be expected on the following sleep, it should be 8") + timecop.SetSpeed(t, 2) + time.Sleep(time.Second + time.Microsecond) + +} + +func Test_spike(t *testing.T) { + +} diff --git a/clock/examples_test.go b/clock/examples_test.go index d2edf4b..71159bf 100644 --- a/clock/examples_test.go +++ b/clock/examples_test.go @@ -18,12 +18,12 @@ func ExampleTimeNow_freeze() { MyFunc := func() Entity { return Entity{ - CreatedAt: clock.TimeNow(), + CreatedAt: clock.Now(), } } expected := Entity{ - CreatedAt: clock.TimeNow(), + CreatedAt: clock.Now(), } timecop.Travel(tb, expected.CreatedAt, timecop.Freeze()) @@ -34,9 +34,9 @@ func ExampleTimeNow_freeze() { func ExampleTimeNow_withTravelByDuration() { var tb testing.TB - _ = clock.TimeNow() // now + _ = clock.Now() // now timecop.Travel(tb, time.Hour) - _ = clock.TimeNow() // now + 1 hour + _ = clock.Now() // now + 1 hour } func ExampleTimeNow_withTravelByDate() { @@ -45,7 +45,7 @@ func ExampleTimeNow_withTravelByDate() { date := time.Date(2022, 01, 01, 12, 0, 0, 0, time.Local) timecop.Travel(tb, date, timecop.Freeze()) // freeze the time until it is read time.Sleep(time.Second) - _ = clock.TimeNow() // equals with date + _ = clock.Now() // equals with date } func ExampleAfter() { diff --git a/clock/internal/chronos.go b/clock/internal/chronos.go index 887366f..7a29eea 100644 --- a/clock/internal/chronos.go +++ b/clock/internal/chronos.go @@ -65,18 +65,35 @@ func setTime(target time.Time, opt Option) func() { return func() { chrono.Timeline = og } } -func RemainingDuration(from time.Time, d time.Duration) time.Duration { +func ScaledDuration(d time.Duration) time.Duration { + // for some reason, two read lock at the same time has sometimes a deadlock that is not detecable with the -race conditiona detector + // so don't use this inside other functions which are protected by rlock + defer rlock()() + return scaledDuration(d) +} + +func scaledDuration(d time.Duration) time.Duration { + if !chrono.Timeline.Altered { + return d + } + return time.Duration(float64(d) / chrono.Speed) +} + +func RemainingDuration(from time.Time, nonScaledDuration time.Duration) time.Duration { defer rlock()() now := getTime() if now.Before(from) { // time travelling can be a bit weird, let's not wait forever if we went back in time return 0 } - scaled := time.Duration(float64(d) / chrono.Speed) delta := now.Sub(from) - return scaled - delta + remainer := scaledDuration(nonScaledDuration) - delta + if remainer < 0 { // if due to the time shift, the it was already expected + return 0 + } + return remainer } -func GetTime() time.Time { +func TimeNow() time.Time { defer rlock()() return getTime() } diff --git a/clock/internal/notify.go b/clock/internal/notify.go index 4ff9e9f..5e38eeb 100644 --- a/clock/internal/notify.go +++ b/clock/internal/notify.go @@ -1,20 +1,32 @@ package internal -func init() { - notify() -} - -var change chan struct{} +var handlers = map[int]chan<- struct{}{} -func Listen() <-chan struct{} { - defer rlock()() - return change +func Notify(c chan<- struct{}) func() { + if c == nil { + panic("clock: Notify using nil channel") + } + defer lock()() + var index int + for i := 0; true; i++ { + if _, ok := handlers[i]; !ok { + index = i + break + } + } + handlers[index] = c + return func() { + defer lock()() + delete(handlers, index) + } } func notify() { - defer lock()() - if change != nil { - close(change) + defer rlock()() + for index, ch := range handlers { + go func(i int, ch chan<- struct{}) { + defer recover() + ch <- struct{}{} + }(index, ch) } - change = make(chan struct{}) } diff --git a/clock/timecop/timecop.go b/clock/timecop/timecop.go index 8db0010..70e943f 100644 --- a/clock/timecop/timecop.go +++ b/clock/timecop/timecop.go @@ -35,12 +35,12 @@ func SetSpeed(tb testing.TB, multiplier float64) { func guardAgainstParallel(tb testing.TB) { tb.Helper() const key, value = `TEST_CASE_TIMECOP_IN_USE`, "TRUE" - tb.Setenv(key, value) + tb.Setenv(key, value) // will fail on parallel execution } func travelByDuration(tb testing.TB, d time.Duration, opt internal.Option) { tb.Helper() - travelByTime(tb, internal.GetTime().Add(d), opt) + travelByTime(tb, internal.TimeNow().Add(d), opt) } func travelByTime(tb testing.TB, target time.Time, opt internal.Option) { diff --git a/clock/timecop/timecop_test.go b/clock/timecop/timecop_test.go index 2f494c6..d54d859 100644 --- a/clock/timecop/timecop_test.go +++ b/clock/timecop/timecop_test.go @@ -18,8 +18,8 @@ func TestSetSpeed_wBlazingFast(t *testing.T) { timecop.SetSpeed(t, timecop.BlazingFast) assert.Eventually(t, 5, func(it assert.It) { var count int - deadline := clock.TimeNow().Add(time.Millisecond) - for clock.TimeNow().Before(deadline) { + deadline := clock.Now().Add(time.Millisecond) + for clock.Now().Before(deadline) { count++ } assert.True(t, 1 <= count) @@ -45,9 +45,9 @@ func TestSetSpeed(t *testing.T) { }) t.Run("on positive value", func(t *testing.T) { timecop.SetSpeed(t, 10000000) - s := clock.TimeNow() + s := clock.Now() time.Sleep(time.Millisecond) - e := clock.TimeNow() + e := clock.Now() assert.True(t, time.Hour < e.Sub(s)) }) t.Run("on frozen time SetSpeed don't start the time", func(t *testing.T) { @@ -55,7 +55,7 @@ func TestSetSpeed(t *testing.T) { timecop.Travel(t, now, timecop.Freeze()) timecop.SetSpeed(t, rnd.Float64()) time.Sleep(time.Microsecond) - got := clock.TimeNow() + got := clock.Now() assert.True(t, now.Equal(got)) }) } @@ -65,14 +65,14 @@ const buffer = 500 * time.Millisecond func TestTravel_duration(t *testing.T) { t.Run("on no travel", func(t *testing.T) { t1 := time.Now() - t2 := clock.TimeNow() + t2 := clock.Now() assert.True(t, t1.Equal(t2) || t1.Before(t2)) }) t.Run("on travel forward", func(t *testing.T) { d := time.Duration(rnd.IntB(100, 200)) * time.Second timecop.Travel(t, d) tnow := time.Now() - cnow := clock.TimeNow() + cnow := clock.Now() assert.True(t, tnow.Before(cnow)) assert.True(t, cnow.Sub(tnow) <= d+buffer) }) @@ -80,7 +80,7 @@ func TestTravel_duration(t *testing.T) { d := time.Duration(rnd.IntB(100, 200)) * time.Second timecop.Travel(t, d*-1) tnow := time.Now() - cnow := clock.TimeNow() + cnow := clock.Now() assert.True(t, tnow.Add(d*-1-buffer).Before(cnow)) assert.True(t, tnow.Add(d*-1+buffer).After(cnow)) }) @@ -89,7 +89,7 @@ func TestTravel_duration(t *testing.T) { func TestTravel_timeTime(t *testing.T) { t.Run("on no travel", func(t *testing.T) { t1 := time.Now() - t2 := clock.TimeNow() + t2 := clock.Now() assert.True(t, t1.Equal(t2) || t1.Before(t2)) }) t.Run("on travel", func(t *testing.T) { @@ -105,7 +105,7 @@ func TestTravel_timeTime(t *testing.T) { ) date := time.Date(year, month, day, hour, minute, second, nano, time.Local) timecop.Travel(t, date) - got := clock.TimeNow() + got := clock.Now() assert.Equal(t, time.Local, got.Location()) assert.Equal(t, year, got.Year()) assert.Equal(t, month, got.Month()) @@ -129,10 +129,10 @@ func TestTravel_timeTime(t *testing.T) { date := time.Date(year, month, day, hour, minute, second, nano, time.Local) timecop.Travel(t, date, timecop.Freeze()) time.Sleep(time.Millisecond) - got := clock.TimeNow() + got := clock.Now() assert.True(t, date.Equal(got)) assert.Waiter{WaitDuration: time.Second}.Wait() - assert.True(t, date.Equal(clock.TimeNow())) + assert.True(t, date.Equal(clock.Now())) }) t.Run("on travel with freeze, then unfreeze", func(t *testing.T) { now := time.Now() @@ -148,13 +148,13 @@ func TestTravel_timeTime(t *testing.T) { date := time.Date(year, month, day, hour, minute, second, nano, time.Local) timecop.Travel(t, date, timecop.Freeze()) time.Sleep(time.Millisecond) - got := clock.TimeNow() + got := clock.Now() assert.True(t, date.Equal(got)) assert.Waiter{WaitDuration: time.Second}.Wait() - assert.True(t, date.Equal(clock.TimeNow())) - timecop.Travel(t, clock.TimeNow(), timecop.Unfreeze()) + assert.True(t, date.Equal(clock.Now())) + timecop.Travel(t, clock.Now(), timecop.Unfreeze()) assert.MakeRetry(time.Second).Assert(t, func(it assert.It) { - it.Must.False(date.Equal(clock.TimeNow())) + it.Must.False(date.Equal(clock.Now())) }) }) } @@ -163,8 +163,8 @@ func TestTravel_cleanup(t *testing.T) { date := time.Now().AddDate(-10, 0, 0) t.Run("", func(t *testing.T) { timecop.Travel(t, date, timecop.Freeze()) - assert.Equal(t, date.Year(), clock.TimeNow().Year()) + assert.Equal(t, date.Year(), clock.Now().Year()) }) const msg = "was not expected that timecop travel leak out from the sub test" - assert.NotEqual(t, date.Year(), clock.TimeNow().Year(), msg) + assert.NotEqual(t, date.Year(), clock.Now().Year(), msg) } diff --git a/examples_test.go b/examples_test.go index c43975f..1d6a6c5 100644 --- a/examples_test.go +++ b/examples_test.go @@ -1442,12 +1442,12 @@ func Example_clockTimeNow_withTimecop() { MyFunc := func() Entity { return Entity{ - CreatedAt: clock.TimeNow(), + CreatedAt: clock.Now(), } } expected := Entity{ - CreatedAt: clock.TimeNow(), + CreatedAt: clock.Now(), } timecop.Travel(tb, expected.CreatedAt, timecop.Freeze()) @@ -1458,9 +1458,9 @@ func Example_clockTimeNow_withTimecop() { func Example_clockTimeNow_withTravelByDuration() { var tb testing.TB - _ = clock.TimeNow() // now + _ = clock.Now() // now timecop.Travel(tb, time.Hour) - _ = clock.TimeNow() // now + 1 hour + _ = clock.Now() // now + 1 hour } func Example_clockTimeNow_withTravelByDate() { @@ -1469,7 +1469,7 @@ func Example_clockTimeNow_withTravelByDate() { date := time.Date(2022, 01, 01, 12, 0, 0, 0, time.Local) timecop.Travel(tb, date, timecop.Freeze()) // freeze the time until it is read time.Sleep(time.Second) - _ = clock.TimeNow() // equals with date + _ = clock.Now() // equals with date } func Example_clockAfter() { diff --git a/pp/PP.go b/pp/PP.go index d0d9cbd..d8fcf3d 100644 --- a/pp/PP.go +++ b/pp/PP.go @@ -6,13 +6,17 @@ import ( "os" "runtime" "strings" + "sync" "go.llib.dev/testcase/internal/caller" ) var defaultWriter io.Writer = os.Stderr +var l sync.Mutex func PP(vs ...any) { + l.Lock() + defer l.Unlock() _, file, line, _ := runtime.Caller(1) _, _ = fmt.Fprintf(defaultWriter, "%s ", caller.AsLocation(true, file, line)) _, _ = fpp(defaultWriter, vs...) diff --git a/random/Unique.go b/random/Unique.go index ffa09ed..edd9f41 100644 --- a/random/Unique.go +++ b/random/Unique.go @@ -20,8 +20,8 @@ func Unique[T any](blk func() T, excludeList ...T) T { if len(excludeList) == 0 { return blk() } - deadline := clock.TimeNow().Add(5 * time.Second) - for clock.TimeNow().Before(deadline) { + deadline := clock.Now().Add(5 * time.Second) + for clock.Now().Before(deadline) { var ( v T = blk() ok bool = true