Skip to content

Commit e991017

Browse files
authored
add tests for performance monitor (#55)
1 parent 76e3610 commit e991017

File tree

1 file changed

+272
-0
lines changed

1 file changed

+272
-0
lines changed

pkg/perf/monitor_test.go

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
package perf
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/alicebob/miniredis/v2"
9+
"github.com/go-co-op/gocron"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"github.com/threefoldtech/zosbase/pkg/mocks"
13+
"github.com/threefoldtech/zosbase/pkg/utils"
14+
"go.uber.org/mock/gomock"
15+
)
16+
17+
// MockTask for testing
18+
type MockTask struct {
19+
id string
20+
cron string
21+
description string
22+
jitter uint32
23+
runFunc func(context.Context) (interface{}, error)
24+
}
25+
26+
func (m *MockTask) ID() string {
27+
return m.id
28+
}
29+
30+
func (m *MockTask) Cron() string {
31+
return m.cron
32+
}
33+
34+
func (m *MockTask) Description() string {
35+
return m.description
36+
}
37+
38+
func (m *MockTask) Jitter() uint32 {
39+
return m.jitter
40+
}
41+
42+
func (m *MockTask) Run(ctx context.Context) (interface{}, error) {
43+
if m.runFunc != nil {
44+
return m.runFunc(ctx)
45+
}
46+
return "test-result", nil
47+
}
48+
49+
func TestNewPerformanceMonitor(t *testing.T) {
50+
t.Run("create PerformanceMonitor successfully", func(t *testing.T) {
51+
ctrl := gomock.NewController(t)
52+
defer ctrl.Finish()
53+
54+
server, err := miniredis.Run()
55+
require.NoError(t, err)
56+
defer server.Close()
57+
58+
redisAddr := "tcp://" + server.Addr()
59+
pm, err := NewPerformanceMonitor(redisAddr)
60+
require.NoError(t, err)
61+
assert.NotNil(t, pm)
62+
assert.NotNil(t, pm.pool)
63+
assert.NotNil(t, pm.zbusClient)
64+
assert.NotNil(t, pm.scheduler)
65+
assert.Empty(t, pm.tasks)
66+
})
67+
68+
t.Run("fail to create PerformanceMonitor with invalid redis address", func(t *testing.T) {
69+
_, err := NewPerformanceMonitor("invalid-redis-address")
70+
assert.Error(t, err)
71+
assert.Contains(t, err.Error(), "failed creating new redis pool")
72+
})
73+
}
74+
75+
func TestPerformanceMonitor_AddTask(t *testing.T) {
76+
pm := &PerformanceMonitor{
77+
scheduler: gocron.NewScheduler(time.UTC),
78+
tasks: []Task{},
79+
}
80+
81+
task1 := &MockTask{id: "task1", cron: "* * * * * *"}
82+
task2 := &MockTask{id: "task2", cron: "0 * * * * *"}
83+
84+
pm.AddTask(task1)
85+
assert.Len(t, pm.tasks, 1)
86+
assert.Equal(t, "task1", pm.tasks[0].ID())
87+
88+
pm.AddTask(task2)
89+
assert.Len(t, pm.tasks, 2)
90+
assert.Equal(t, "task2", pm.tasks[1].ID())
91+
}
92+
func TestPerformanceMonitor_Run(t *testing.T) {
93+
server, err := miniredis.Run()
94+
require.NoError(t, err)
95+
defer server.Close()
96+
97+
redisAddr := "tcp://" + server.Addr()
98+
99+
pool, err := utils.NewRedisPool(redisAddr)
100+
require.NoError(t, err)
101+
102+
createMonitor := func(t *testing.T) (*PerformanceMonitor, *mocks.MockClient) {
103+
ctrl := gomock.NewController(t)
104+
t.Cleanup(ctrl.Finish)
105+
106+
mockZbus := mocks.NewMockClient(ctrl)
107+
108+
return &PerformanceMonitor{
109+
scheduler: gocron.NewScheduler(time.UTC),
110+
pool: pool,
111+
zbusClient: mockZbus,
112+
tasks: []Task{},
113+
}, mockZbus
114+
}
115+
116+
t.Run("run with no tasks", func(t *testing.T) {
117+
pm, _ := createMonitor(t)
118+
ctx := context.Background()
119+
120+
err := pm.Run(ctx)
121+
assert.NoError(t, err)
122+
123+
assert.True(t, pm.scheduler.IsRunning())
124+
pm.scheduler.Stop()
125+
126+
assert.False(t, pm.scheduler.IsRunning())
127+
})
128+
129+
t.Run("run with invalid cron expression", func(t *testing.T) {
130+
pm, _ := createMonitor(t)
131+
132+
task := &MockTask{
133+
id: "invalid-task",
134+
cron: "invalid-cron",
135+
description: "Invalid Task",
136+
}
137+
138+
pm.AddTask(task)
139+
140+
ctx := context.Background()
141+
142+
err := pm.Run(ctx)
143+
assert.Error(t, err)
144+
assert.Contains(t, err.Error(), "failed to schedule the task")
145+
})
146+
147+
t.Run("run with task that doesn't exist in cache", func(t *testing.T) {
148+
pm, _ := createMonitor(t)
149+
150+
taskExecuted := false
151+
task := &MockTask{
152+
id: "new-task",
153+
cron: "0 0 */6 * * *", // Every 6 hours (won't trigger during test)
154+
description: "New Task",
155+
jitter: 0,
156+
runFunc: func(ctx context.Context) (interface{}, error) {
157+
taskExecuted = true
158+
return "immediate-execution-result", nil
159+
},
160+
}
161+
162+
pm.AddTask(task)
163+
164+
ctx := context.Background()
165+
166+
err = pm.Run(ctx)
167+
assert.NoError(t, err)
168+
169+
jobs := pm.scheduler.Jobs()
170+
assert.Len(t, jobs, 1)
171+
172+
time.Sleep(100 * time.Millisecond)
173+
174+
assert.True(t, taskExecuted)
175+
176+
assert.True(t, pm.scheduler.IsRunning())
177+
178+
pm.scheduler.Stop()
179+
})
180+
181+
t.Run("run with task that exists in cache", func(t *testing.T) {
182+
pm, _ := createMonitor(t)
183+
184+
conn := pool.Get()
185+
_, err = conn.Do("SET", "perf.existing-task", "cached-result") // Use the correct key format with module prefix
186+
require.NoError(t, err)
187+
conn.Close()
188+
189+
taskExecuted := false
190+
task := &MockTask{
191+
id: "existing-task",
192+
cron: "0 0 */6 * * *", // Every 6 hours (won't trigger during test)
193+
description: "Existing Task",
194+
jitter: 0,
195+
runFunc: func(ctx context.Context) (interface{}, error) {
196+
taskExecuted = true
197+
return "should-not-execute-immediately", nil
198+
},
199+
}
200+
201+
pm.AddTask(task)
202+
203+
ctx := context.Background()
204+
205+
err = pm.Run(ctx)
206+
assert.NoError(t, err)
207+
208+
time.Sleep(100 * time.Millisecond)
209+
210+
assert.False(t, taskExecuted)
211+
212+
assert.True(t, pm.scheduler.IsRunning())
213+
214+
pm.scheduler.Stop()
215+
})
216+
217+
t.Run("run with multiple tasks - some scheduled, some immediate", func(t *testing.T) {
218+
pm, _ := createMonitor(t)
219+
220+
conn := pool.Get()
221+
_, err = conn.Do("SET", "perf.cached-task", "cached-result")
222+
require.NoError(t, err)
223+
conn.Close()
224+
225+
task1Executed := false
226+
task2Executed := false
227+
228+
// Task that exists in cache - should not execute immediately
229+
task1 := &MockTask{
230+
id: "cached-task",
231+
cron: "0 0 */12 * * *", // Every 12 hours
232+
description: "Cached Task",
233+
jitter: 0,
234+
runFunc: func(ctx context.Context) (interface{}, error) {
235+
task1Executed = true
236+
return "cached-task-result", nil
237+
},
238+
}
239+
240+
// Task that doesn't exist in cache - should execute immediately
241+
task2 := &MockTask{
242+
id: "new-task-1",
243+
cron: "0 0 */8 * * *", // Every 8 hours
244+
description: "New Task 1",
245+
jitter: 0,
246+
runFunc: func(ctx context.Context) (interface{}, error) {
247+
task2Executed = true
248+
return "new-task-1-result", nil
249+
},
250+
}
251+
252+
pm.AddTask(task1)
253+
pm.AddTask(task2)
254+
255+
ctx := context.Background()
256+
257+
err = pm.Run(ctx)
258+
assert.NoError(t, err)
259+
260+
jobs := pm.scheduler.Jobs()
261+
assert.Len(t, jobs, 2)
262+
263+
assert.True(t, pm.scheduler.IsRunning())
264+
265+
time.Sleep(200 * time.Millisecond)
266+
267+
assert.False(t, task1Executed, "cached task should not execute immediately")
268+
assert.True(t, task2Executed, "new task 2 should execute immediately")
269+
270+
pm.scheduler.Stop()
271+
})
272+
}

0 commit comments

Comments
 (0)