forked from open-cluster-management-io/sdk-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbase_controller_test.go
More file actions
363 lines (312 loc) · 8.99 KB
/
Copy pathbase_controller_test.go
File metadata and controls
363 lines (312 loc) · 8.99 KB
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
package factory
import (
"context"
"errors"
"sync"
"testing"
"time"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
)
// mockInformer is a mock implementation of Informer for testing
type mockInformer struct {
handlers []cache.ResourceEventHandler
synced bool
}
func (m *mockInformer) AddEventHandler(handler cache.ResourceEventHandler) (cache.ResourceEventHandlerRegistration, error) {
m.handlers = append(m.handlers, handler)
return nil, nil
}
func (m *mockInformer) HasSynced() bool {
return m.synced
}
// TestBaseControllerRun tests the basic Run functionality of baseController
func TestBaseControllerRun(t *testing.T) {
tests := []struct {
name string
workers int
cacheSynced bool
expectSync bool
resyncInterval time.Duration
addToQueue bool
}{
{
name: "controller runs with synced caches",
workers: 1,
cacheSynced: true,
expectSync: true,
addToQueue: true,
},
{
name: "controller runs with multiple workers",
workers: 3,
cacheSynced: true,
expectSync: true,
addToQueue: true,
},
{
name: "controller with periodic resync",
workers: 1,
cacheSynced: true,
expectSync: true,
resyncInterval: 100 * time.Millisecond,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := klog.NewContext(context.Background(), klog.Background())
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
syncCalled := &sync.WaitGroup{}
if tt.expectSync {
syncCalled.Add(1)
}
syncFunc := func(ctx context.Context, syncCtx SyncContext, key string) error {
syncCalled.Done()
return nil
}
mockInf := &mockInformer{synced: tt.cacheSynced}
controller := &baseController{
name: "test-controller",
sync: syncFunc,
syncContext: NewSyncContext("test"),
resyncEvery: tt.resyncInterval,
cachesToSync: []cache.InformerSynced{mockInf.HasSynced},
cacheSyncTimeout: 1 * time.Second,
}
// Add item to queue if needed
if tt.addToQueue {
controller.syncContext.Queue().Add("test-key")
}
// Run controller in background
go controller.Run(ctx, tt.workers)
// Wait for sync to be called or timeout
done := make(chan struct{})
go func() {
syncCalled.Wait()
close(done)
}()
select {
case <-done:
// Success - sync was called
case <-time.After(1500 * time.Millisecond):
if tt.expectSync {
t.Error("timeout waiting for sync to be called")
}
}
})
}
}
// TestBaseControllerProcessNextWorkItem tests the processNextWorkItem function
func TestBaseControllerProcessNextWorkItem(t *testing.T) {
tests := []struct {
name string
queueKey string
syncError error
expectRequeue bool
}{
{
name: "successful sync with default queue key",
queueKey: DefaultQueueKey,
syncError: nil,
expectRequeue: false,
},
{
name: "successful sync with custom queue key",
queueKey: "custom-key",
syncError: nil,
expectRequeue: false,
},
{
name: "failed sync with default queue key",
queueKey: DefaultQueueKey,
syncError: errors.New("sync failed"),
expectRequeue: true,
},
{
name: "failed sync with custom queue key",
queueKey: "namespace/name",
syncError: errors.New("sync failed"),
expectRequeue: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := klog.NewContext(context.Background(), klog.Background())
syncCalled := false
syncFunc := func(ctx context.Context, syncCtx SyncContext, key string) error {
syncCalled = true
if key != tt.queueKey {
t.Errorf("expected key %s, got %s", tt.queueKey, key)
}
return tt.syncError
}
controller := &baseController{
name: "test-controller",
sync: syncFunc,
syncContext: NewSyncContext("test"),
}
// Add item to queue
controller.syncContext.Queue().Add(tt.queueKey)
// Process the item
controller.processNextWorkItem(ctx)
if !syncCalled {
t.Error("sync function was not called")
}
// For rate limited items, we need to wait a bit
if tt.expectRequeue {
time.Sleep(10 * time.Millisecond)
}
// Check if item was requeued on error
queueLen := controller.syncContext.Queue().Len()
if tt.expectRequeue && queueLen == 0 {
t.Error("expected item to be requeued but queue is empty")
}
if !tt.expectRequeue && queueLen > 0 {
t.Errorf("expected queue to be empty but has %d items", queueLen)
}
// Cleanup
controller.syncContext.Queue().ShutDown()
})
}
}
// TestBaseControllerDefaultQueueKey tests the error logging behavior with DefaultQueueKey
func TestBaseControllerDefaultQueueKey(t *testing.T) {
ctx := klog.NewContext(context.Background(), klog.Background())
syncError := errors.New("test error")
syncFunc := func(ctx context.Context, syncCtx SyncContext, key string) error {
return syncError
}
controller := &baseController{
name: "test-controller",
sync: syncFunc,
syncContext: NewSyncContext("test"),
}
// Test with DefaultQueueKey
controller.syncContext.Queue().Add(DefaultQueueKey)
controller.processNextWorkItem(ctx)
// Wait for rate limited item
time.Sleep(10 * time.Millisecond)
// Verify item was requeued
if controller.syncContext.Queue().Len() == 0 {
t.Error("expected item to be requeued")
}
// Test with custom key
controller.syncContext.Queue().Add("custom/key")
controller.processNextWorkItem(ctx)
// Wait for rate limited item
time.Sleep(10 * time.Millisecond)
// Verify item was requeued
if controller.syncContext.Queue().Len() == 0 {
t.Error("expected item to be requeued")
}
// Cleanup
controller.syncContext.Queue().ShutDown()
}
// TestBaseControllerName tests the Name() method
func TestBaseControllerName(t *testing.T) {
controller := &baseController{
name: "test-controller-name",
}
if controller.Name() != "test-controller-name" {
t.Errorf("expected name 'test-controller-name', got '%s'", controller.Name())
}
}
// TestBaseControllerSyncContext tests the SyncContext() method
func TestBaseControllerSyncContext(t *testing.T) {
syncCtx := NewSyncContext("test")
controller := &baseController{
syncContext: syncCtx,
}
if controller.SyncContext() != syncCtx {
t.Error("SyncContext() returned different context than expected")
}
}
// TestBaseControllerSync tests the Sync() method
func TestBaseControllerSync(t *testing.T) {
ctx := klog.NewContext(context.Background(), klog.Background())
syncCalled := false
expectedKey := "test-key"
syncFunc := func(ctx context.Context, syncCtx SyncContext, key string) error {
syncCalled = true
if key != expectedKey {
t.Errorf("expected key %s, got %s", expectedKey, key)
}
return nil
}
syncCtx := NewSyncContext("test")
controller := &baseController{
sync: syncFunc,
syncContext: syncCtx,
}
err := controller.Sync(ctx, syncCtx, expectedKey)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !syncCalled {
t.Error("sync function was not called")
}
}
// TestBaseControllerRunPeriodicalResync tests periodic resync functionality
func TestBaseControllerRunPeriodicalResync(t *testing.T) {
ctx := klog.NewContext(context.Background(), klog.Background())
ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
defer cancel()
syncCtx := NewSyncContext("test")
controller := &baseController{
syncContext: syncCtx,
resyncEvery: 50 * time.Millisecond,
}
// Run periodic resync
go controller.runPeriodicalResync(ctx, controller.resyncEvery)
// Wait for items to be added to queue
time.Sleep(400 * time.Millisecond)
// Check that queue has items (should have at least 1-2 items added due to timing variations)
queueLen := controller.syncContext.Queue().Len()
if queueLen < 1 {
t.Errorf("expected at least 1 item in queue from periodic resync, got %d", queueLen)
}
// Verify the items are DefaultQueueKey
for i := 0; i < queueLen; i++ {
item, _ := controller.syncContext.Queue().Get()
if item != DefaultQueueKey {
t.Errorf("expected DefaultQueueKey, got %v", item)
}
controller.syncContext.Queue().Done(item)
}
// Cleanup
controller.syncContext.Queue().ShutDown()
}
// TestDefaultQueueKeysFunc tests the DefaultQueueKeysFunc function
func TestDefaultQueueKeysFunc(t *testing.T) {
tests := []struct {
name string
obj runtime.Object
expect []string
}{
{
name: "nil object",
obj: nil,
expect: []string{DefaultQueueKey},
},
{
name: "non-nil object",
obj: &runtime.Unknown{},
expect: []string{DefaultQueueKey},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := DefaultQueueKeysFunc(tt.obj)
if len(result) != len(tt.expect) {
t.Errorf("expected %d keys, got %d", len(tt.expect), len(result))
}
for i, key := range result {
if key != tt.expect[i] {
t.Errorf("expected key %s at index %d, got %s", tt.expect[i], i, key)
}
}
})
}
}