1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
package task_test
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/lxc/incus/v6/internal/server/task"
)
// The given task is executed immediately by the scheduler.
func TestTask_ExecuteImmediately(t *testing.T) {
f, wait := newFunc(t, 1)
defer startTask(t, f, task.Every(time.Second))()
wait(100 * time.Millisecond)
}
// The given task is executed again after the specified time interval has
// elapsed.
func TestTask_ExecutePeriodically(t *testing.T) {
f, wait := newFunc(t, 2)
defer startTask(t, f, task.Every(250*time.Millisecond))()
wait(100 * time.Millisecond)
wait(400 * time.Millisecond)
}
// If the scheduler is reset, the task is re-executed immediately and then
// again after the interval.
func TestTask_Reset(t *testing.T) {
f, wait := newFunc(t, 3)
stop, reset := task.Start(context.Background(), f, task.Every(250*time.Millisecond))
defer func() { _ = stop(time.Second) }()
wait(50 * time.Millisecond) // First execution, immediately
reset() // Trigger a reset
wait(50 * time.Millisecond) // Second execution, immediately after reset
wait(400 * time.Millisecond) // Third execution, after the timeout
}
// If the interval is zero, the task function is never run.
func TestTask_ZeroInterval(t *testing.T) {
f, _ := newFunc(t, 0)
defer startTask(t, f, task.Every(0*time.Millisecond))()
// Sleep a little bit to prove that the task function does not get run.
time.Sleep(100 * time.Millisecond)
}
// If the schedule returns an error, the task is aborted.
func TestTask_ScheduleError(t *testing.T) {
schedule := func() (time.Duration, error) {
return 0, errors.New("boom")
}
f, _ := newFunc(t, 0)
defer startTask(t, f, schedule)()
// Sleep a little bit to prove that the task function does not get run.
time.Sleep(100 * time.Millisecond)
}
// If the schedule returns an error, but its interval is positive, the task will
// try again to invoke the schedule function after that interval.
func TestTask_ScheduleTemporaryError(t *testing.T) {
errored := false
schedule := func() (time.Duration, error) {
if !errored {
errored = true
return time.Millisecond, errors.New("boom")
}
return time.Second, nil
}
f, wait := newFunc(t, 1)
defer startTask(t, f, schedule)()
// The task gets executed since the schedule error is temporary and gets
// resolved.
wait(50 * time.Millisecond)
}
// If SkipFirst is passed, the given task is only executed at the second round.
func TestTask_SkipFirst(t *testing.T) {
i := 0
f := func(context.Context) {
i++
}
defer startTask(t, f, task.Every(250*time.Millisecond, task.SkipFirst))()
time.Sleep(400 * time.Millisecond)
assert.Equal(t, 1, i) // The function got executed only once, not twice.
}
// Create a new task function that sends a notification to a channel every time
// it's run.
//
// Return the task function, along with a "wait" function which will block
// until one notification is received through such channel, or fails the test
// if no notification is received within the given timeout.
//
// The n parameter can be used to limit the number of times the task function
// is allowed run: when that number is reached the task function will trigger a
// test failure (zero means that the task function will make the test fail as
// soon as it is invoked).
func newFunc(t *testing.T, n int) (task.Func, func(time.Duration)) {
i := 0
notifications := make(chan struct{})
f := func(context.Context) {
if i == n {
t.Errorf("task was supposed to be called at most %d times", n)
}
notifications <- struct{}{}
i++
}
wait := func(timeout time.Duration) {
select {
case <-notifications:
case <-time.After(timeout):
t.Errorf("no notification received in %s", timeout)
}
}
return f, wait
}
// Convenience around task.Start which also makes sure that the stop function
// of the task actually terminates.
func startTask(t *testing.T, f task.Func, schedule task.Schedule) func() {
stop, _ := task.Start(context.Background(), f, schedule)
return func() {
assert.NoError(t, stop(time.Second))
}
}
|