103 lines
2.3 KiB
Go
103 lines
2.3 KiB
Go
package schedule
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// run starts RunOnInterval in a goroutine and returns a channel that closes
|
|
// when RunOnInterval returns.
|
|
func run(ctx context.Context, interval time.Duration, fn func(), cleanup func()) <-chan struct{} {
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
RunOnInterval(ctx, interval, fn, cleanup)
|
|
close(done)
|
|
}()
|
|
|
|
return done
|
|
}
|
|
|
|
func TestRunOnInterval_CallsFnOnTick(t *testing.T) {
|
|
t.Parallel()
|
|
var calls atomic.Int32
|
|
|
|
run(t.Context(), time.Millisecond, func() { calls.Add(1) }, nil)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return calls.Load() >= 3
|
|
}, time.Second, time.Millisecond)
|
|
}
|
|
|
|
func TestRunOnInterval_StopsWhenContextCancelled(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
cancel()
|
|
|
|
done := run(ctx, time.Hour, func() {}, nil)
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("RunOnInterval did not return after context cancellation")
|
|
}
|
|
}
|
|
|
|
func TestRunOnInterval_CallsCleanupOnCancellation(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
|
|
var cleanupCalled atomic.Bool
|
|
done := run(ctx, time.Hour, func() {}, func() { cleanupCalled.Store(true) })
|
|
|
|
cancel()
|
|
<-done
|
|
|
|
assert.True(t, cleanupCalled.Load())
|
|
}
|
|
|
|
func TestRunOnInterval_CleanupCalledExactlyOnce(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
|
|
var cleanupCount atomic.Int32
|
|
done := run(ctx, time.Millisecond, func() {}, func() { cleanupCount.Add(1) })
|
|
|
|
// Let fn tick a few times before cancelling.
|
|
assert.Eventually(t, func() bool { return cleanupCount.Load() == 0 }, time.Second, time.Millisecond)
|
|
cancel()
|
|
<-done
|
|
|
|
assert.Equal(t, int32(1), cleanupCount.Load())
|
|
}
|
|
|
|
func TestRunOnInterval_NilCleanupDoesNotPanic(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
cancel()
|
|
|
|
done := run(ctx, time.Hour, func() {}, nil)
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("RunOnInterval did not return after context cancellation")
|
|
}
|
|
}
|
|
|
|
func TestRunOnInterval_FnNotCalledAfterCancellation(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
cancel()
|
|
|
|
var calls atomic.Int32
|
|
done := run(ctx, time.Millisecond, func() { calls.Add(1) }, nil)
|
|
<-done
|
|
|
|
assert.Equal(t, int32(0), calls.Load())
|
|
}
|