diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d74cf7c..b9c4964 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,6 +8,8 @@ jobs: golangci: name: lint runs-on: ubuntu-latest + strategy: + fail-fast: false steps: - uses: actions/setup-go@v6 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index da1d0e5..9e8a843 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,6 +10,7 @@ jobs: test: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: go: - "1.18" @@ -19,6 +20,7 @@ jobs: - "1.22" - "1.23" - "1.24" + - "1.25" - "1.x" steps: - uses: actions/checkout@v5 diff --git a/Makefile b/Makefile index 8badbda..ae6d5e6 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,9 @@ build: go build -v ./... test: - go test -race -v ./... + go test -race ./... watch-test: - reflex -t 50ms -s -- sh -c 'gotest -race -v ./...' + reflex -t 50ms -s -- sh -c 'gotest -race ./...' bench: go test -benchmem -count 3 -bench ./... diff --git a/channel_test.go b/channel_test.go index 6157219..398a95a 100644 --- a/channel_test.go +++ b/channel_test.go @@ -98,7 +98,7 @@ func TestChannelDispatcher(t *testing.T) { func TestDispatchingStrategyRoundRobin(t *testing.T) { t.Parallel() - testWithTimeout(t, 10*time.Millisecond) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) children := createChannels[int](3, 2) @@ -113,7 +113,7 @@ func TestDispatchingStrategyRoundRobin(t *testing.T) { func TestDispatchingStrategyRandom(t *testing.T) { t.Parallel() - testWithTimeout(t, 10*time.Millisecond) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) children := createChannels[int](2, 2) @@ -129,7 +129,7 @@ func TestDispatchingStrategyRandom(t *testing.T) { func TestDispatchingStrategyWeightedRandom(t *testing.T) { t.Parallel() - testWithTimeout(t, 10*time.Millisecond) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) children := createChannels[int](2, 2) @@ -147,7 +147,7 @@ func TestDispatchingStrategyWeightedRandom(t *testing.T) { func TestDispatchingStrategyFirst(t *testing.T) { t.Parallel() - testWithTimeout(t, 10*time.Millisecond) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) children := createChannels[int](2, 2) @@ -163,7 +163,7 @@ func TestDispatchingStrategyFirst(t *testing.T) { func TestDispatchingStrategyLeast(t *testing.T) { t.Parallel() - testWithTimeout(t, 10*time.Millisecond) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) children := createChannels[int](2, 2) @@ -183,7 +183,7 @@ func TestDispatchingStrategyLeast(t *testing.T) { func TestDispatchingStrategyMost(t *testing.T) { t.Parallel() - testWithTimeout(t, 10*time.Millisecond) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) children := createChannels[int](2, 2) @@ -203,7 +203,7 @@ func TestDispatchingStrategyMost(t *testing.T) { func TestSliceToChannel(t *testing.T) { t.Parallel() - testWithTimeout(t, 10*time.Millisecond) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) ch := SliceToChannel(2, []int{1, 2, 3}) @@ -224,7 +224,7 @@ func TestSliceToChannel(t *testing.T) { func TestChannelToSlice(t *testing.T) { t.Parallel() - testWithTimeout(t, 10*time.Millisecond) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) ch := SliceToChannel(2, []int{1, 2, 3}) @@ -235,7 +235,7 @@ func TestChannelToSlice(t *testing.T) { func TestGenerate(t *testing.T) { t.Parallel() - testWithTimeout(t, 10*time.Millisecond) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) generator := func(yield func(int)) { @@ -257,7 +257,7 @@ func TestGenerate(t *testing.T) { func TestBuffer(t *testing.T) { t.Parallel() - testWithTimeout(t, 10*time.Millisecond) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) ch := SliceToChannel(2, []int{1, 2, 3}) @@ -277,8 +277,8 @@ func TestBuffer(t *testing.T) { is.False(ok3) } -func TestBufferWithContext(t *testing.T) { - t.Parallel() +func TestBufferWithContext(t *testing.T) { //nolint:paralleltest + // t.Parallel() testWithTimeout(t, 200*time.Millisecond) is := assert.New(t) @@ -313,47 +313,60 @@ func TestBufferWithContext(t *testing.T) { is.True(ok2) } -func TestBufferWithTimeout(t *testing.T) { - t.Parallel() - testWithTimeout(t, 200*time.Millisecond) +func TestBufferWithTimeout(t *testing.T) { //nolint:paralleltest + // t.Parallel() + testWithTimeout(t, 2000*time.Millisecond) is := assert.New(t) - generator := func(yield func(int)) { - for i := 0; i < 5; i++ { - yield(i) - time.Sleep(10 * time.Millisecond) + generator := func(n ...int) func(yield func(int)) { + return func(yield func(int)) { + for i := 0; i < len(n); i++ { + yield(n[i]) + time.Sleep(100 * time.Millisecond) + } } } - ch := Generator(0, generator) - items1, length1, _, ok1 := BufferWithTimeout(ch, 20, 15*time.Millisecond) + ch := Generator(0, generator(0, 1, 2, 3, 4)) + items1, length1, duration1, ok1 := BufferWithTimeout(ch, 20, 150*time.Millisecond) is.Equal([]int{0, 1}, items1) is.Equal(2, length1) + is.InDelta(150*time.Millisecond, duration1, float64(20*time.Millisecond)) is.True(ok1) - items2, length2, _, ok2 := BufferWithTimeout(ch, 20, 2*time.Millisecond) + items2, length2, duration2, ok2 := BufferWithTimeout(ch, 20, 10*time.Millisecond) is.Empty(items2) is.Zero(length2) + is.InDelta(10*time.Millisecond, duration2, float64(10*time.Millisecond)) is.True(ok2) - items3, length3, _, ok3 := BufferWithTimeout(ch, 1, 30*time.Millisecond) + items3, length3, duration3, ok3 := BufferWithTimeout(ch, 1, 300*time.Millisecond) is.Equal([]int{2}, items3) is.Equal(1, length3) + is.InDelta(50*time.Millisecond, duration3, float64(20*time.Millisecond)) is.True(ok3) - items4, length4, _, ok4 := BufferWithTimeout(ch, 2, 25*time.Millisecond) + items4, length4, duration4, ok4 := BufferWithTimeout(ch, 2, 250*time.Millisecond) is.Equal([]int{3, 4}, items4) is.Equal(2, length4) + is.InDelta(200*time.Millisecond, duration4, float64(50*time.Millisecond)) is.True(ok4) - items5, length5, _, ok5 := BufferWithTimeout(ch, 3, 25*time.Millisecond) + items5, length5, duration5, ok5 := BufferWithTimeout(ch, 3, 250*time.Millisecond) is.Empty(items5) is.Zero(length5) + is.InDelta(100*time.Millisecond, duration5, float64(50*time.Millisecond)) is.False(ok5) + + items6, length6, duration6, ok6 := BufferWithTimeout(ch, 3, 250*time.Millisecond) + is.Empty(items6) + is.Zero(length6) + is.InDelta(1*time.Millisecond, duration6, float64(10*time.Millisecond)) + is.False(ok6) } -func TestFanIn(t *testing.T) { - t.Parallel() +func TestFanIn(t *testing.T) { //nolint:paralleltest + // t.Parallel() testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) @@ -392,8 +405,8 @@ func TestFanIn(t *testing.T) { is.Zero(msg0) } -func TestFanOut(t *testing.T) { - t.Parallel() +func TestFanOut(t *testing.T) { //nolint:paralleltest + // t.Parallel() testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) diff --git a/concurrency_test.go b/concurrency_test.go index 887ba86..382932f 100644 --- a/concurrency_test.go +++ b/concurrency_test.go @@ -9,9 +9,9 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSynchronize(t *testing.T) { - t.Parallel() - testWithTimeout(t, 100*time.Millisecond) +func TestSynchronize(t *testing.T) { //nolint:paralleltest + // t.Parallel() + testWithTimeout(t, 1000*time.Millisecond) is := assert.New(t) // check that callbacks are not executed concurrently @@ -25,7 +25,7 @@ func TestSynchronize(t *testing.T) { for i := 0; i < 10; i++ { go s.Do(func() { - time.Sleep(5 * time.Millisecond) + time.Sleep(50 * time.Millisecond) wg.Done() }) } @@ -33,9 +33,7 @@ func TestSynchronize(t *testing.T) { wg.Wait() duration := time.Since(start) - - is.Greater(duration, 50*time.Millisecond) - is.Less(duration, 60*time.Millisecond) + is.InDelta(500*time.Millisecond, duration, float64(40*time.Millisecond)) } // check locker is locked @@ -62,8 +60,8 @@ func TestSynchronize(t *testing.T) { } } -func TestAsync(t *testing.T) { - t.Parallel() +func TestAsync(t *testing.T) { //nolint:paralleltest + // t.Parallel() testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) @@ -84,8 +82,8 @@ func TestAsync(t *testing.T) { } } -func TestAsyncX(t *testing.T) { - t.Parallel() +func TestAsyncX(t *testing.T) { //nolint:paralleltest + // t.Parallel() testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) @@ -214,33 +212,29 @@ func TestAsyncX(t *testing.T) { } } -func TestWaitFor(t *testing.T) { - t.Parallel() +func TestWaitFor(t *testing.T) { //nolint:paralleltest + // t.Parallel() - testTimeout := 100 * time.Millisecond - longTimeout := 2 * testTimeout - shortTimeout := 4 * time.Millisecond + t.Run("exist condition works", func(t *testing.T) { //nolint:paralleltest + // t.Parallel() - t.Run("exist condition works", func(t *testing.T) { - t.Parallel() - - testWithTimeout(t, testTimeout) + testWithTimeout(t, 300*time.Millisecond) is := assert.New(t) laterTrue := func(i int) bool { return i >= 5 } - iter, duration, ok := WaitFor(laterTrue, longTimeout, time.Millisecond) + iter, duration, ok := WaitFor(laterTrue, 200*time.Millisecond, 10*time.Millisecond) is.Equal(6, iter, "unexpected iteration count") - is.InEpsilon(6*time.Millisecond, duration, float64(500*time.Microsecond)) + is.InDelta(60*time.Millisecond, duration, float64(5*time.Millisecond)) is.True(ok) }) - t.Run("counter is incremented", func(t *testing.T) { - t.Parallel() + t.Run("counter is incremented", func(t *testing.T) { //nolint:paralleltest + // t.Parallel() - testWithTimeout(t, testTimeout) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) counter := 0 @@ -250,80 +244,63 @@ func TestWaitFor(t *testing.T) { return false } - iter, duration, ok := WaitFor(alwaysFalse, shortTimeout, 1050*time.Microsecond) + iter, duration, ok := WaitFor(alwaysFalse, 40*time.Millisecond, 10*time.Millisecond) is.Equal(counter, iter, "unexpected iteration count") - is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + is.InDelta(40*time.Millisecond, duration, float64(5*time.Millisecond)) is.False(ok) }) alwaysTrue := func(_ int) bool { return true } alwaysFalse := func(_ int) bool { return false } - t.Run("short timeout works", func(t *testing.T) { - t.Parallel() + t.Run("timeout works", func(t *testing.T) { //nolint:paralleltest + // t.Parallel() - testWithTimeout(t, testTimeout) + testWithTimeout(t, 200*time.Millisecond) is := assert.New(t) - iter, duration, ok := WaitFor(alwaysFalse, shortTimeout, 10*time.Millisecond) + iter, duration, ok := WaitFor(alwaysFalse, 50*time.Millisecond, 100*time.Millisecond) is.Zero(iter, "unexpected iteration count") - is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + is.InDelta(50*time.Millisecond, duration, float64(10*time.Millisecond)) is.False(ok) }) - t.Run("timeout works", func(t *testing.T) { - t.Parallel() + t.Run("exist on first condition", func(t *testing.T) { //nolint:paralleltest + // t.Parallel() - testWithTimeout(t, testTimeout) + testWithTimeout(t, 200*time.Millisecond) is := assert.New(t) - shortTimeout := 4 * time.Millisecond - iter, duration, ok := WaitFor(alwaysFalse, shortTimeout, 10*time.Millisecond) - is.Zero(iter, "unexpected iteration count") - is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) - is.False(ok) - }) - - t.Run("exist on first condition", func(t *testing.T) { - t.Parallel() - - testWithTimeout(t, testTimeout) - is := assert.New(t) - - iter, duration, ok := WaitFor(alwaysTrue, 10*time.Millisecond, time.Millisecond) + iter, duration, ok := WaitFor(alwaysTrue, 100*time.Millisecond, 30*time.Millisecond) is.Equal(1, iter, "unexpected iteration count") - is.InEpsilon(time.Millisecond, duration, float64(5*time.Microsecond)) + is.InDelta(30*time.Millisecond, duration, float64(10*time.Millisecond)) is.True(ok) }) } -func TestWaitForWithContext(t *testing.T) { - t.Parallel() +func TestWaitForWithContext(t *testing.T) { //nolint:paralleltest + // t.Parallel() - testTimeout := 100 * time.Millisecond - longTimeout := 2 * testTimeout - shortTimeout := 4 * time.Millisecond + t.Run("exist condition works", func(t *testing.T) { //nolint:paralleltest + // t.Parallel() - t.Run("exist condition works", func(t *testing.T) { - t.Parallel() - - testWithTimeout(t, testTimeout) + testWithTimeout(t, 200*time.Millisecond) is := assert.New(t) laterTrue := func(_ context.Context, i int) bool { return i >= 5 } - iter, duration, ok := WaitForWithContext(context.Background(), laterTrue, longTimeout, time.Millisecond) + iter, duration, ok := WaitForWithContext(context.Background(), laterTrue, 200*time.Millisecond, 10*time.Millisecond) is.Equal(6, iter, "unexpected iteration count") - is.InEpsilon(6*time.Millisecond, duration, float64(500*time.Microsecond)) + is.InDelta(60*time.Millisecond, duration, float64(5*time.Millisecond)) is.True(ok) }) - t.Run("counter is incremented", func(t *testing.T) { - t.Parallel() + t.Run("counter is incremented", func(t *testing.T) { //nolint:paralleltest + // t.Parallel() - testWithTimeout(t, testTimeout) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) counter := 0 @@ -333,81 +310,68 @@ func TestWaitForWithContext(t *testing.T) { return false } - iter, duration, ok := WaitForWithContext(context.Background(), alwaysFalse, shortTimeout, 1050*time.Microsecond) + iter, duration, ok := WaitForWithContext(context.Background(), alwaysFalse, 40*time.Millisecond, 10*time.Millisecond) is.Equal(counter, iter, "unexpected iteration count") - is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + is.InDelta(40*time.Millisecond, duration, float64(5*time.Millisecond)) is.False(ok) }) alwaysTrue := func(_ context.Context, _ int) bool { return true } alwaysFalse := func(_ context.Context, _ int) bool { return false } - t.Run("short timeout works", func(t *testing.T) { - t.Parallel() + t.Run("timeout works", func(t *testing.T) { //nolint:paralleltest + // t.Parallel() - testWithTimeout(t, testTimeout) + testWithTimeout(t, 200*time.Millisecond) is := assert.New(t) - iter, duration, ok := WaitForWithContext(context.Background(), alwaysFalse, shortTimeout, 10*time.Millisecond) + iter, duration, ok := WaitForWithContext(context.Background(), alwaysFalse, 50*time.Millisecond, 100*time.Millisecond) is.Zero(iter, "unexpected iteration count") - is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + is.InDelta(50*time.Millisecond, duration, float64(10*time.Millisecond)) is.False(ok) }) - t.Run("timeout works", func(t *testing.T) { - t.Parallel() + t.Run("exist on first condition", func(t *testing.T) { //nolint:paralleltest + // t.Parallel() - testWithTimeout(t, testTimeout) + testWithTimeout(t, 200*time.Millisecond) is := assert.New(t) - shortTimeout := 4 * time.Millisecond - iter, duration, ok := WaitForWithContext(context.Background(), alwaysFalse, shortTimeout, 10*time.Millisecond) - is.Zero(iter, "unexpected iteration count") - is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) - is.False(ok) - }) - - t.Run("exist on first condition", func(t *testing.T) { - t.Parallel() - - testWithTimeout(t, testTimeout) - is := assert.New(t) - - iter, duration, ok := WaitForWithContext(context.Background(), alwaysTrue, 10*time.Millisecond, time.Millisecond) + iter, duration, ok := WaitForWithContext(context.Background(), alwaysTrue, 100*time.Millisecond, 10*time.Millisecond) is.Equal(1, iter, "unexpected iteration count") - is.InEpsilon(time.Millisecond, duration, float64(5*time.Microsecond)) + is.InDelta(10*time.Millisecond, duration, float64(5*time.Millisecond)) is.True(ok) }) - t.Run("context cancellation stops everything", func(t *testing.T) { - t.Parallel() + t.Run("context cancellation stops everything", func(t *testing.T) { //nolint:paralleltest + // t.Parallel() - testWithTimeout(t, testTimeout) + testWithTimeout(t, 100*time.Millisecond) is := assert.New(t) - expiringCtx, clean := context.WithTimeout(context.Background(), 8*time.Millisecond) + expiringCtx, clean := context.WithTimeout(context.Background(), 45*time.Millisecond) t.Cleanup(func() { clean() }) - iter, duration, ok := WaitForWithContext(expiringCtx, alwaysFalse, 100*time.Millisecond, 3*time.Millisecond) - is.Equal(2, iter, "unexpected iteration count") - is.InEpsilon(10*time.Millisecond, duration, float64(500*time.Microsecond)) + iter, duration, ok := WaitForWithContext(expiringCtx, alwaysFalse, 100*time.Millisecond, 30*time.Millisecond) + is.Equal(1, iter, "unexpected iteration count") + is.InDelta(45*time.Millisecond, duration, float64(10*time.Millisecond)) is.False(ok) }) - t.Run("canceled context stops everything", func(t *testing.T) { - t.Parallel() + t.Run("canceled context stops everything", func(t *testing.T) { //nolint:paralleltest + // t.Parallel() - testWithTimeout(t, testTimeout) + testWithTimeout(t, 200*time.Millisecond) is := assert.New(t) canceledCtx, cancel := context.WithCancel(context.Background()) cancel() - iter, duration, ok := WaitForWithContext(canceledCtx, alwaysFalse, 100*time.Millisecond, 1050*time.Microsecond) + iter, duration, ok := WaitForWithContext(canceledCtx, alwaysFalse, 100*time.Millisecond, 30*time.Millisecond) is.Zero(iter, "unexpected iteration count") - is.InEpsilon(1*time.Millisecond, duration, float64(5*time.Microsecond)) + is.InDelta(1*time.Millisecond, duration, float64(1*time.Millisecond)) is.False(ok) }) } diff --git a/internal/xtime/README.md b/internal/xtime/README.md new file mode 100644 index 0000000..73acd1e --- /dev/null +++ b/internal/xtime/README.md @@ -0,0 +1,6 @@ + +# xtime + +Lightweight mock for time package. + +A dedicated package such as [jonboulle/clockwork](https://github.com/jonboulle/clockwork/) would be better, but I would rather limit dependencies for this package. `clockwork` does not support Go 1.18 anymore. diff --git a/internal/xtime/fake.go b/internal/xtime/fake.go new file mode 100644 index 0000000..380e238 --- /dev/null +++ b/internal/xtime/fake.go @@ -0,0 +1,40 @@ +//nolint:revive +package xtime + +import ( + "time" +) + +func NewFakeClock() *FakeClock { + return NewFakeClockAt(time.Now()) +} + +func NewFakeClockAt(t time.Time) *FakeClock { + return &FakeClock{ + time: t, + } +} + +type FakeClock struct { + _ noCopy + + // Not protected by a mutex. If a warning is thrown in your tests, + // just disable parallel tests. + time time.Time +} + +func (c *FakeClock) Now() time.Time { + return c.time +} + +func (c *FakeClock) Since(t time.Time) time.Duration { + return c.time.Sub(t) +} + +func (c *FakeClock) Until(t time.Time) time.Duration { + return t.Sub(c.time) +} + +func (c *FakeClock) Sleep(d time.Duration) { + c.time = c.time.Add(d) +} diff --git a/internal/xtime/noCopy.go b/internal/xtime/noCopy.go new file mode 100644 index 0000000..9d425ad --- /dev/null +++ b/internal/xtime/noCopy.go @@ -0,0 +1,14 @@ +package xtime + +// noCopy may be added to structs which must not be copied +// after the first use. +// +// See https://golang.org/issues/8005#issuecomment-190753527 +// for details. +// +// Note that it must not be embedded, due to the Lock and Unlock methods. +type noCopy struct{} + +// Lock is a no-op used by -copylocks checker from `go vet`. +func (*noCopy) Lock() {} +func (*noCopy) Unlock() {} diff --git a/internal/xtime/real.go b/internal/xtime/real.go new file mode 100644 index 0000000..df5a8db --- /dev/null +++ b/internal/xtime/real.go @@ -0,0 +1,30 @@ +//nolint:revive +package xtime + +import ( + "time" +) + +func NewRealClock() *RealClock { + return &RealClock{} +} + +type RealClock struct { + _ noCopy +} + +func (c *RealClock) Now() time.Time { + return time.Now() +} + +func (c *RealClock) Since(t time.Time) time.Duration { + return time.Since(t) +} + +func (c *RealClock) Until(t time.Time) time.Duration { + return time.Until(t) +} + +func (c *RealClock) Sleep(d time.Duration) { + time.Sleep(d) +} diff --git a/internal/xtime/time.go b/internal/xtime/time.go new file mode 100644 index 0000000..093a959 --- /dev/null +++ b/internal/xtime/time.go @@ -0,0 +1,33 @@ +//nolint:revive +package xtime + +import "time" + +var clock Clock = &RealClock{} + +func SetClock(c Clock) { + clock = c +} + +func Now() time.Time { + return clock.Now() +} + +func Since(t time.Time) time.Duration { + return clock.Since(t) +} + +func Until(t time.Time) time.Duration { + return clock.Until(t) +} + +func Sleep(d time.Duration) { + clock.Sleep(d) +} + +type Clock interface { + Now() time.Time + Since(t time.Time) time.Duration + Until(t time.Time) time.Duration + Sleep(d time.Duration) +} diff --git a/main_test.go b/main_test.go index 911a9df..5595aee 100644 --- a/main_test.go +++ b/main_test.go @@ -3,9 +3,11 @@ package lo import ( "testing" + "github.com/samber/lo/internal/xtime" "go.uber.org/goleak" ) func TestMain(m *testing.M) { + xtime.SetClock(xtime.NewFakeClock()) goleak.VerifyTestMain(m) } diff --git a/retry.go b/retry.go index 0b7c979..e8b98c4 100644 --- a/retry.go +++ b/retry.go @@ -3,6 +3,8 @@ package lo import ( "sync" "time" + + "github.com/samber/lo/internal/xtime" ) type debounce struct { @@ -172,20 +174,20 @@ func Attempt(maxIteration int, f func(index int) error) (int, error) { func AttemptWithDelay(maxIteration int, delay time.Duration, f func(index int, duration time.Duration) error) (int, time.Duration, error) { var err error - start := time.Now() + start := xtime.Now() for i := 0; maxIteration <= 0 || i < maxIteration; i++ { - err = f(i, time.Since(start)) + err = f(i, xtime.Since(start)) if err == nil { - return i + 1, time.Since(start), nil + return i + 1, xtime.Since(start), nil } if maxIteration <= 0 || i+1 < maxIteration { - time.Sleep(delay) + xtime.Sleep(delay) } } - return maxIteration, time.Since(start), err + return maxIteration, xtime.Since(start), err } // AttemptWhile invokes a function N times until it returns valid output. @@ -224,23 +226,23 @@ func AttemptWhileWithDelay(maxIteration int, delay time.Duration, f func(int, ti var err error var shouldContinueInvoke bool - start := time.Now() + start := xtime.Now() for i := 0; maxIteration <= 0 || i < maxIteration; i++ { - err, shouldContinueInvoke = f(i, time.Since(start)) + err, shouldContinueInvoke = f(i, xtime.Since(start)) if !shouldContinueInvoke { // if shouldContinueInvoke is false, then return immediately - return i + 1, time.Since(start), err + return i + 1, xtime.Since(start), err } if err == nil { - return i + 1, time.Since(start), nil + return i + 1, xtime.Since(start), nil } if maxIteration <= 0 || i+1 < maxIteration { - time.Sleep(delay) + xtime.Sleep(delay) } } - return maxIteration, time.Since(start), err + return maxIteration, xtime.Since(start), err } type transactionStep[T any] struct { diff --git a/retry_test.go b/retry_test.go index 651ad4f..94cc27c 100644 --- a/retry_test.go +++ b/retry_test.go @@ -50,8 +50,8 @@ func TestAttempt(t *testing.T) { is.NoError(err4) } -func TestAttemptWithDelay(t *testing.T) { - t.Parallel() +func TestAttemptWithDelay(t *testing.T) { //nolint:paralleltest + // t.Parallel() is := assert.New(t) err := errors.New("failed") @@ -60,14 +60,14 @@ func TestAttemptWithDelay(t *testing.T) { return nil }) iter2, dur2, err2 := AttemptWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) error { - if i == 5 { + if i == 3 { return nil } return err }) iter3, dur3, err3 := AttemptWithDelay(2, 10*time.Millisecond, func(i int, d time.Duration) error { - if i == 5 { + if i == 3 { return nil } @@ -82,20 +82,19 @@ func TestAttemptWithDelay(t *testing.T) { }) is.Equal(1, iter1) - is.GreaterOrEqual(dur1, 0*time.Millisecond) - is.Less(dur1, 1*time.Millisecond) + is.InDelta(1*time.Microsecond, dur1, float64(1*time.Millisecond)) is.NoError(err1) - is.Equal(6, iter2) - is.Greater(dur2, 50*time.Millisecond) - is.Less(dur2, 60*time.Millisecond) + + is.Equal(4, iter2) + is.InDelta(30*time.Millisecond, dur2, float64(5*time.Millisecond)) is.NoError(err2) + is.Equal(2, iter3) - is.Greater(dur3, 10*time.Millisecond) - is.Less(dur3, 20*time.Millisecond) + is.InDelta(10*time.Millisecond, dur3, float64(5*time.Millisecond)) is.ErrorIs(err3, err) + is.Equal(11, iter4) - is.Greater(dur4, 100*time.Millisecond) - is.Less(dur4, 115*time.Millisecond) + is.InDelta(100*time.Millisecond, dur4, float64(5*time.Millisecond)) is.NoError(err4) } @@ -178,8 +177,8 @@ func TestAttemptWhile(t *testing.T) { is.NoError(err7) } -func TestAttemptWhileWithDelay(t *testing.T) { - t.Parallel() +func TestAttemptWhileWithDelay(t *testing.T) { //nolint:paralleltest + // t.Parallel() is := assert.New(t) err := errors.New("failed") @@ -189,21 +188,19 @@ func TestAttemptWhileWithDelay(t *testing.T) { }) is.Equal(1, iter1) - is.GreaterOrEqual(dur1, 0*time.Millisecond) - is.Less(dur1, 1*time.Millisecond) + is.InDelta(1*time.Microsecond, dur1, float64(3*time.Millisecond)) is.NoError(err1) iter2, dur2, err2 := AttemptWhileWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { - if i == 5 { + if i == 3 { return nil, true } return err, true }) - is.Equal(6, iter2) - is.Greater(dur2, 50*time.Millisecond) - is.Less(dur2, 60*time.Millisecond) + is.Equal(4, iter2) + is.InDelta(30*time.Millisecond, dur2, float64(5*time.Millisecond)) is.NoError(err2) iter3, dur3, err3 := AttemptWhileWithDelay(2, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { @@ -215,8 +212,7 @@ func TestAttemptWhileWithDelay(t *testing.T) { }) is.Equal(2, iter3) - is.Greater(dur3, 10*time.Millisecond) - is.Less(dur3, 20*time.Millisecond) + is.InDelta(10*time.Millisecond, dur3, float64(5*time.Millisecond)) is.ErrorIs(err3, err) iter4, dur4, err4 := AttemptWhileWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { @@ -228,21 +224,19 @@ func TestAttemptWhileWithDelay(t *testing.T) { }) is.Equal(11, iter4) - is.Greater(dur4, 100*time.Millisecond) - is.Less(dur4, 115*time.Millisecond) + is.InDelta(100*time.Millisecond, dur4, float64(5*time.Millisecond)) is.NoError(err4) iter5, dur5, err5 := AttemptWhileWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { - if i == 5 { + if i == 3 { return nil, false } return err, true }) - is.Equal(6, iter5) - is.Greater(dur5, 10*time.Millisecond) - is.Less(dur5, 115*time.Millisecond) + is.Equal(4, iter5) + is.InDelta(30*time.Millisecond, dur5, float64(5*time.Millisecond)) is.NoError(err5) iter6, dur6, err6 := AttemptWhileWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { @@ -250,8 +244,7 @@ func TestAttemptWhileWithDelay(t *testing.T) { }) is.Equal(1, iter6) - is.Less(dur6, 10*time.Millisecond) - is.Less(dur6, 115*time.Millisecond) + is.InDelta(1*time.Microsecond, dur6, float64(5*time.Millisecond)) is.NoError(err6) iter7, dur7, err7 := AttemptWhileWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) { @@ -266,12 +259,12 @@ func TestAttemptWhileWithDelay(t *testing.T) { }) is.Equal(42, iter7) - is.Less(dur7, 500*time.Millisecond) + is.InDelta(410*time.Millisecond, dur7, float64(5*time.Millisecond)) is.NoError(err7) } -func TestDebounce(t *testing.T) { - t.Parallel() +func TestDebounce(t *testing.T) { //nolint:paralleltest + // t.Parallel() f1 := func() { println("1. Called once after 10ms when func stopped invoking!") @@ -283,43 +276,43 @@ func TestDebounce(t *testing.T) { println("3. Called once after 10ms when func stopped invoking!") } - d1, _ := NewDebounce(10*time.Millisecond, f1) + d1, _ := NewDebounce(100*time.Millisecond, f1) // execute 3 times for i := 0; i < 3; i++ { for j := 0; j < 10; j++ { d1() } - time.Sleep(20 * time.Millisecond) + time.Sleep(200 * time.Millisecond) } - d2, _ := NewDebounce(10*time.Millisecond, f2) + d2, _ := NewDebounce(100*time.Millisecond, f2) // execute once because it is always invoked and only last invoke is worked after 100ms for i := 0; i < 3; i++ { for j := 0; j < 5; j++ { d2() } - time.Sleep(5 * time.Millisecond) + time.Sleep(50 * time.Millisecond) } - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) // execute once because it is canceled after 200ms. - d3, cancel := NewDebounce(10*time.Millisecond, f3) + d3, cancel := NewDebounce(100*time.Millisecond, f3) for i := 0; i < 3; i++ { for j := 0; j < 10; j++ { d3() } - time.Sleep(20 * time.Millisecond) + time.Sleep(200 * time.Millisecond) if i == 0 { cancel() } } } -func TestDebounceBy(t *testing.T) { - t.Parallel() +func TestDebounceBy(t *testing.T) { //nolint:paralleltest + // t.Parallel() is := assert.New(t) mu := sync.Mutex{} @@ -344,7 +337,7 @@ func TestDebounceBy(t *testing.T) { // fmt.Printf("[key=%d] 3. Called once after 10ms when func stopped invoking!\n", key) } - d1, _ := NewDebounceBy(10*time.Millisecond, f1) + d1, _ := NewDebounceBy(100*time.Millisecond, f1) // execute 3 times for i := 0; i < 3; i++ { @@ -353,16 +346,19 @@ func TestDebounceBy(t *testing.T) { d1(k) } } - time.Sleep(20 * time.Millisecond) + time.Sleep(200 * time.Millisecond) } + // Wait for debounced calls to complete + time.Sleep(150 * time.Millisecond) + mu.Lock() is.Equal(30, output[0]) is.Equal(30, output[1]) is.Equal(30, output[2]) mu.Unlock() - d2, _ := NewDebounceBy(10*time.Millisecond, f2) + d2, _ := NewDebounceBy(100*time.Millisecond, f2) // execute once because it is always invoked and only last invoke is worked after 100ms for i := 0; i < 3; i++ { @@ -371,10 +367,11 @@ func TestDebounceBy(t *testing.T) { d2(k) } } - time.Sleep(5 * time.Millisecond) + time.Sleep(50 * time.Millisecond) } - time.Sleep(10 * time.Millisecond) + // Wait for debounced calls to complete + time.Sleep(150 * time.Millisecond) mu.Lock() is.Equal(45, output[0]) @@ -383,7 +380,7 @@ func TestDebounceBy(t *testing.T) { mu.Unlock() // execute once because it is canceled after 200ms. - d3, cancel := NewDebounceBy(10*time.Millisecond, f3) + d3, cancel := NewDebounceBy(100*time.Millisecond, f3) for i := 0; i < 3; i++ { for j := 0; j < 10; j++ { for k := 0; k < 3; k++ { @@ -391,7 +388,7 @@ func TestDebounceBy(t *testing.T) { } } - time.Sleep(20 * time.Millisecond) + time.Sleep(200 * time.Millisecond) if i == 0 { for k := 0; k < 3; k++ { cancel(k) @@ -399,6 +396,9 @@ func TestDebounceBy(t *testing.T) { } } + // Wait for debounced calls to complete + time.Sleep(150 * time.Millisecond) + mu.Lock() is.Equal(75, output[0]) is.Equal(75, output[1]) @@ -502,14 +502,14 @@ func TestTransaction(t *testing.T) { } } -func TestNewThrottle(t *testing.T) { - t.Parallel() +func TestNewThrottle(t *testing.T) { //nolint:paralleltest + // t.Parallel() is := assert.New(t) callCount := 0 f1 := func() { callCount++ } - th, reset := NewThrottle(10*time.Millisecond, f1) + th, reset := NewThrottle(100*time.Millisecond, f1) is.Zero(callCount) for j := 0; j < 100; j++ { @@ -517,7 +517,7 @@ func TestNewThrottle(t *testing.T) { } is.Equal(1, callCount) - time.Sleep(15 * time.Millisecond) + time.Sleep(150 * time.Millisecond) for j := 0; j < 100; j++ { th() @@ -531,14 +531,14 @@ func TestNewThrottle(t *testing.T) { is.Equal(3, callCount) } -func TestNewThrottleWithCount(t *testing.T) { - t.Parallel() +func TestNewThrottleWithCount(t *testing.T) { //nolint:paralleltest + // t.Parallel() is := assert.New(t) callCount := 0 f1 := func() { callCount++ } - th, reset := NewThrottleWithCount(10*time.Millisecond, 3, f1) + th, reset := NewThrottleWithCount(100*time.Millisecond, 3, f1) // the function does not throttle for initial count number for i := 0; i < 20; i++ { @@ -546,7 +546,7 @@ func TestNewThrottleWithCount(t *testing.T) { } is.Equal(3, callCount) - time.Sleep(15 * time.Millisecond) + time.Sleep(150 * time.Millisecond) for i := 0; i < 20; i++ { th() @@ -562,8 +562,8 @@ func TestNewThrottleWithCount(t *testing.T) { is.Equal(9, callCount) } -func TestNewThrottleBy(t *testing.T) { - t.Parallel() +func TestNewThrottleBy(t *testing.T) { //nolint:paralleltest + // t.Parallel() is := assert.New(t) callCountA := 0 callCountB := 0 @@ -574,7 +574,7 @@ func TestNewThrottleBy(t *testing.T) { callCountB++ } } - th, reset := NewThrottleBy(10*time.Millisecond, f1) + th, reset := NewThrottleBy(100*time.Millisecond, f1) is.Zero(callCountA) is.Zero(callCountB) @@ -585,7 +585,7 @@ func TestNewThrottleBy(t *testing.T) { is.Equal(1, callCountA) is.Equal(1, callCountB) - time.Sleep(15 * time.Millisecond) + time.Sleep(150 * time.Millisecond) for j := 0; j < 100; j++ { th("a") @@ -602,8 +602,8 @@ func TestNewThrottleBy(t *testing.T) { is.Equal(2, callCountB) } -func TestNewThrottleByWithCount(t *testing.T) { - t.Parallel() +func TestNewThrottleByWithCount(t *testing.T) { //nolint:paralleltest + // t.Parallel() is := assert.New(t) callCountA := 0 @@ -615,7 +615,7 @@ func TestNewThrottleByWithCount(t *testing.T) { callCountB++ } } - th, reset := NewThrottleByWithCount(10*time.Millisecond, 3, f1) + th, reset := NewThrottleByWithCount(100*time.Millisecond, 3, f1) // the function does not throttle for initial count number for i := 0; i < 20; i++ { @@ -625,7 +625,7 @@ func TestNewThrottleByWithCount(t *testing.T) { is.Equal(3, callCountA) is.Equal(3, callCountB) - time.Sleep(15 * time.Millisecond) + time.Sleep(150 * time.Millisecond) for i := 0; i < 20; i++ { th("a") diff --git a/time.go b/time.go index 1e3b827..f295adb 100644 --- a/time.go +++ b/time.go @@ -1,6 +1,8 @@ package lo -import "time" +import ( + "time" +) // Duration returns the time taken to execute a function. // Play: https://go.dev/play/p/HQfbBbAXaFP diff --git a/time_test.go b/time_test.go index 3764fc7..c8a74ff 100644 --- a/time_test.go +++ b/time_test.go @@ -7,47 +7,52 @@ import ( "github.com/stretchr/testify/assert" ) -func TestDuration(t *testing.T) { - t.Parallel() +func TestDuration(t *testing.T) { //nolint:paralleltest + // t.Parallel() is := assert.New(t) + testWithTimeout(t, 200*time.Millisecond) - result := Duration(func() { time.Sleep(10 * time.Millisecond) }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + result := Duration(func() { time.Sleep(100 * time.Millisecond) }) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) } -func TestDurationX(t *testing.T) { - t.Parallel() +func TestDurationX(t *testing.T) { //nolint:paralleltest + // t.Parallel() is := assert.New(t) + testWithTimeout(t, 1500*time.Millisecond) { - result := Duration0(func() { time.Sleep(10 * time.Millisecond) }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + result := Duration0(func() { time.Sleep(100 * time.Millisecond) }) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) } { - a, result := Duration1(func() string { time.Sleep(10 * time.Millisecond); return "a" }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + a, result := Duration1(func() string { time.Sleep(100 * time.Millisecond); return "a" }) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) is.Equal("a", a) } { - a, b, result := Duration2(func() (string, string) { time.Sleep(10 * time.Millisecond); return "a", "b" }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + a, b, result := Duration2(func() (string, string) { time.Sleep(100 * time.Millisecond); return "a", "b" }) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) is.Equal("a", a) is.Equal("b", b) } { - a, b, c, result := Duration3(func() (string, string, string) { time.Sleep(10 * time.Millisecond); return "a", "b", "c" }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + a, b, c, result := Duration3(func() (string, string, string) { time.Sleep(100 * time.Millisecond); return "a", "b", "c" }) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) is.Equal("a", a) is.Equal("b", b) is.Equal("c", c) } { - a, b, c, d, result := Duration4(func() (string, string, string, string) { time.Sleep(10 * time.Millisecond); return "a", "b", "c", "d" }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + a, b, c, d, result := Duration4(func() (string, string, string, string) { + time.Sleep(100 * time.Millisecond) + return "a", "b", "c", "d" + }) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) is.Equal("a", a) is.Equal("b", b) is.Equal("c", c) @@ -56,10 +61,10 @@ func TestDurationX(t *testing.T) { { a, b, c, d, e, result := Duration5(func() (string, string, string, string, string) { - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) return "a", "b", "c", "d", "e" }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) is.Equal("a", a) is.Equal("b", b) is.Equal("c", c) @@ -69,10 +74,10 @@ func TestDurationX(t *testing.T) { { a, b, c, d, e, f, result := Duration6(func() (string, string, string, string, string, string) { - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) return "a", "b", "c", "d", "e", "f" }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) is.Equal("a", a) is.Equal("b", b) is.Equal("c", c) @@ -83,10 +88,10 @@ func TestDurationX(t *testing.T) { { a, b, c, d, e, f, g, result := Duration7(func() (string, string, string, string, string, string, string) { - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) return "a", "b", "c", "d", "e", "f", "g" }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) is.Equal("a", a) is.Equal("b", b) is.Equal("c", c) @@ -98,10 +103,10 @@ func TestDurationX(t *testing.T) { { a, b, c, d, e, f, g, h, result := Duration8(func() (string, string, string, string, string, string, string, string) { - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) return "a", "b", "c", "d", "e", "f", "g", "h" }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) is.Equal("a", a) is.Equal("b", b) is.Equal("c", c) @@ -114,10 +119,10 @@ func TestDurationX(t *testing.T) { { a, b, c, d, e, f, g, h, i, result := Duration9(func() (string, string, string, string, string, string, string, string, string) { - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) return "a", "b", "c", "d", "e", "f", "g", "h", "i" }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) is.Equal("a", a) is.Equal("b", b) is.Equal("c", c) @@ -131,10 +136,10 @@ func TestDurationX(t *testing.T) { { a, b, c, d, e, f, g, h, i, j, result := Duration10(func() (string, string, string, string, string, string, string, string, string, string) { - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) return "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" }) - is.InEpsilon(10*time.Millisecond, result, float64(2*time.Millisecond)) + is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond)) is.Equal("a", a) is.Equal("b", b) is.Equal("c", c)