fix(tests): fix flaky time-based tests (#699)

* fix(tests): fix flaky time-based tests

* test: replace time package with a mock
Using a dedicated dependency would have been awesome, but i try to keep this repo with minimal dependencies
This commit is contained in:
Samuel Berthe
2025-10-05 00:34:07 +02:00
committed by GitHub
parent dedd62f639
commit 380e1a0ae2
15 changed files with 353 additions and 238 deletions

View File

@@ -8,6 +8,8 @@ jobs:
golangci:
name: lint
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/setup-go@v6
with:

View File

@@ -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

View File

@@ -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 ./...

View File

@@ -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)

View File

@@ -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)
})
}

6
internal/xtime/README.md Normal file
View File

@@ -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.

40
internal/xtime/fake.go Normal file
View File

@@ -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)
}

14
internal/xtime/noCopy.go Normal file
View File

@@ -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() {}

30
internal/xtime/real.go Normal file
View File

@@ -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)
}

33
internal/xtime/time.go Normal file
View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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")

View File

@@ -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

View File

@@ -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)