File: task_test.go

package info (click to toggle)
incus 6.0.5-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 24,428 kB
  • sloc: sh: 16,313; ansic: 3,121; python: 457; makefile: 337; ruby: 51; sql: 50; lisp: 6
file content (138 lines) | stat: -rw-r--r-- 4,117 bytes parent folder | download | duplicates (3)
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))
	}
}