Skip to content

Commit ef1c046

Browse files
committed
test: replace custom WaitForCondition with testify assertions
Signed-off-by: Marc Nuri <[email protected]>
1 parent c9e57bb commit ef1c046

File tree

3 files changed

+59
-74
lines changed

3 files changed

+59
-74
lines changed

internal/test/test.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -71,24 +71,3 @@ func WaitForHealthz(tcpAddr *net.TCPAddr) error {
7171
}
7272
return fmt.Errorf("healthz endpoint returned 404 after retries")
7373
}
74-
75-
func WaitForCondition(timeout time.Duration, condition func() bool) error {
76-
done := make(chan struct{})
77-
go func() {
78-
for {
79-
if condition() {
80-
close(done)
81-
return
82-
}
83-
time.Sleep(time.Millisecond)
84-
}
85-
}()
86-
87-
select {
88-
case <-done:
89-
// Condition met
90-
case <-time.After(timeout):
91-
return fmt.Errorf("timeout waiting for condition")
92-
}
93-
return nil
94-
}

pkg/kubernetes/watcher/cluster_test.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ import (
77
"testing"
88
"time"
99

10-
"github.com/containers/kubernetes-mcp-server/internal/test"
1110
"github.com/stretchr/testify/suite"
1211
"k8s.io/client-go/discovery"
1312
"k8s.io/client-go/discovery/cached/memory"
13+
14+
"github.com/containers/kubernetes-mcp-server/internal/test"
15+
)
16+
17+
const (
18+
// eventuallyTick is the polling interval for Eventually assertions
19+
eventuallyTick = time.Millisecond
1420
)
1521

1622
const (
@@ -35,11 +41,11 @@ func (s *ClusterStateTestSuite) TearDownTest() {
3541

3642
// waitForWatcherState waits for the watcher to capture initial state
3743
func (s *ClusterStateTestSuite) waitForWatcherState(watcher *ClusterState) {
38-
s.NoError(test.WaitForCondition(watcherStateTimeout, func() bool {
44+
s.Eventually(func() bool {
3945
watcher.mu.Lock()
4046
defer watcher.mu.Unlock()
4147
return len(watcher.lastKnownState.apiGroups) > 0
42-
}), "timeout waiting for watcher to capture initial state")
48+
}, watcherStateTimeout, eventuallyTick, "timeout waiting for watcher to capture initial state")
4349
}
4450

4551
func (s *ClusterStateTestSuite) TestNewClusterState() {
@@ -221,9 +227,9 @@ func (s *ClusterStateTestSuite) TestWatch() {
221227
}
222228

223229
// Wait for change detection - the watcher invalidates the cache on each poll
224-
s.Require().NoError(test.WaitForCondition(500*time.Millisecond, func() bool {
230+
s.Eventually(func() bool {
225231
return callCount.Load() >= 1
226-
}), "timeout waiting for onChange callback")
232+
}, 500*time.Millisecond, eventuallyTick, "timeout waiting for onChange callback")
227233

228234
s.GreaterOrEqual(callCount.Load(), int32(1), "onChange should be called at least once")
229235
})
@@ -288,9 +294,9 @@ func (s *ClusterStateTestSuite) TestWatch() {
288294
}
289295

290296
// Wait for onChange to be called (which returns an error)
291-
s.Require().NoError(test.WaitForCondition(500*time.Millisecond, func() bool {
297+
s.Eventually(func() bool {
292298
return errorCallCount.Load() >= 1
293-
}), "timeout waiting for onChange callback")
299+
}, 500*time.Millisecond, eventuallyTick, "timeout waiting for onChange callback")
294300

295301
s.GreaterOrEqual(errorCallCount.Load(), int32(1), "onChange should be called even when it returns an error")
296302
})
@@ -322,11 +328,10 @@ func (s *ClusterStateTestSuite) TestClose() {
322328
s.Run("stops polling", func() {
323329
beforeCount := callCount.Load()
324330
// Wait longer than poll interval to verify no more polling
325-
// We expect this to timeout because no callbacks should be triggered after close
326-
err := test.WaitForCondition(150*time.Millisecond, func() bool {
331+
// We expect this to never happen because no callbacks should be triggered after close
332+
s.Never(func() bool {
327333
return callCount.Load() > beforeCount
328-
})
329-
s.Error(err, "should timeout because no polling occurs after close")
334+
}, 150*time.Millisecond, eventuallyTick, "should not poll after close")
330335
afterCount := callCount.Load()
331336
s.Equal(beforeCount, afterCount, "should not poll after close")
332337
})
@@ -375,11 +380,11 @@ func (s *ClusterStateTestSuite) TestClose() {
375380
}
376381

377382
// Wait for the change to be detected (debounce timer starts)
378-
s.Require().NoError(test.WaitForCondition(200*time.Millisecond, func() bool {
383+
s.Eventually(func() bool {
379384
watcher.mu.Lock()
380385
defer watcher.mu.Unlock()
381386
return watcher.debounceTimer != nil
382-
}), "timeout waiting for debounce timer to start")
387+
}, 200*time.Millisecond, eventuallyTick, "timeout waiting for debounce timer to start")
383388

384389
// Close the watcher before debounce window expires
385390
watcher.Close()

pkg/kubernetes/watcher/kubeconfig_test.go

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ import (
66
"testing"
77
"time"
88

9-
"github.com/containers/kubernetes-mcp-server/internal/test"
109
"github.com/stretchr/testify/suite"
1110
"k8s.io/client-go/tools/clientcmd"
11+
12+
"github.com/containers/kubernetes-mcp-server/internal/test"
1213
)
1314

1415
const (
1516
// kubeconfigTestTimeout is the maximum time to wait for watcher operations
1617
kubeconfigTestTimeout = 500 * time.Millisecond
18+
// kubeconfigEventuallyTick is the polling interval for Eventually assertions
19+
kubeconfigEventuallyTick = time.Millisecond
1720
)
1821

1922
type KubeconfigTestSuite struct {
@@ -60,17 +63,17 @@ func (s *KubeconfigTestSuite) TestWatch() {
6063
watcher.Watch(onChange)
6164

6265
// Wait for the watcher to be ready
63-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
66+
s.Eventually(func() bool {
6467
return watcher.started
65-
}), "timeout waiting for watcher to be ready")
68+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for watcher to be ready")
6669

6770
// Modify the kubeconfig file
6871
s.Require().NoError(clientcmd.WriteToFile(*test.KubeConfigFake(), s.kubeconfigFile))
6972

7073
// Wait for change detection
71-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
74+
s.Eventually(func() bool {
7275
return changeDetected.Load()
73-
}), "timeout waiting for onChange callback")
76+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for onChange callback")
7477
})
7578

7679
s.Run("does not block when no kubeconfig files exist", func() {
@@ -86,9 +89,9 @@ func (s *KubeconfigTestSuite) TestWatch() {
8689
completed.Store(true)
8790
}()
8891

89-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
92+
s.Eventually(func() bool {
9093
return completed.Load()
91-
}), "Watch blocked when no kubeconfig files exist")
94+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "Watch blocked when no kubeconfig files exist")
9295
})
9396

9497
s.Run("handles multiple file changes with debouncing", func() {
@@ -104,18 +107,18 @@ func (s *KubeconfigTestSuite) TestWatch() {
104107
watcher.Watch(onChange)
105108

106109
// Wait for the watcher to be ready
107-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
110+
s.Eventually(func() bool {
108111
return watcher.started
109-
}), "timeout waiting for watcher to be ready")
112+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for watcher to be ready")
110113

111114
// Modify the kubeconfig file multiple times, waiting for each callback
112115
// to ensure we're past the debounce window before the next write
113116
for i := 0; i < 3; i++ {
114117
expectedCount := int32(i + 1)
115118
s.Require().NoError(clientcmd.WriteToFile(*test.KubeConfigFake(), s.kubeconfigFile))
116-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
119+
s.Eventuallyf(func() bool {
117120
return callCount.Load() >= expectedCount
118-
}), "timeout waiting for onChange callback on iteration %d", i)
121+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for onChange callback on iteration %d", i)
119122
}
120123

121124
s.GreaterOrEqual(callCount.Load(), int32(3), "onChange should be called at least 3 times")
@@ -134,17 +137,17 @@ func (s *KubeconfigTestSuite) TestWatch() {
134137
watcher.Watch(onChange)
135138

136139
// Wait for the watcher to be ready
137-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
140+
s.Eventually(func() bool {
138141
return watcher.started
139-
}), "timeout waiting for watcher to be ready")
142+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for watcher to be ready")
140143

141144
// Modify the kubeconfig file
142145
s.Require().NoError(clientcmd.WriteToFile(*test.KubeConfigFake(), s.kubeconfigFile))
143146

144147
// Wait for error to be returned (watcher should not panic)
145-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
148+
s.Eventually(func() bool {
146149
return errorReturned.Load()
147-
}), "timeout waiting for onChange callback")
150+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for onChange callback")
148151
})
149152

150153
s.Run("ignores subsequent Watch calls when already started", func() {
@@ -161,9 +164,9 @@ func (s *KubeconfigTestSuite) TestWatch() {
161164
})
162165

163166
// Wait for the first watcher to be ready
164-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
167+
s.Eventually(func() bool {
165168
return watcher.started
166-
}), "timeout waiting for first watcher to be ready")
169+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for first watcher to be ready")
167170

168171
// Try to start second watcher (should be ignored since already started)
169172
watcher.Watch(func() error {
@@ -175,9 +178,9 @@ func (s *KubeconfigTestSuite) TestWatch() {
175178
s.Require().NoError(clientcmd.WriteToFile(*test.KubeConfigFake(), s.kubeconfigFile))
176179

177180
// Wait for first watcher to trigger
178-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
181+
s.Eventually(func() bool {
179182
return firstWatcherActive.Load()
180-
}), "timeout waiting for first watcher")
183+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for first watcher")
181184

182185
// Verify second watcher was never activated
183186
s.False(secondWatcherActive.Load(), "second watcher should not be activated")
@@ -199,9 +202,9 @@ func (s *KubeconfigTestSuite) TestClose() {
199202
watcher.Watch(func() error { return nil })
200203

201204
// Wait for the watcher to be ready
202-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
205+
s.Eventually(func() bool {
203206
return watcher.started
204-
}), "timeout waiting for watcher to be ready")
207+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for watcher to be ready")
205208

206209
s.NotPanics(func() {
207210
watcher.Close()
@@ -218,9 +221,9 @@ func (s *KubeconfigTestSuite) TestClose() {
218221
})
219222

220223
// Wait for the watcher to be ready
221-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
224+
s.Eventually(func() bool {
222225
return watcher.started
223-
}), "timeout waiting for watcher to be ready")
226+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for watcher to be ready")
224227

225228
watcher.Close()
226229

@@ -230,12 +233,10 @@ func (s *KubeconfigTestSuite) TestClose() {
230233
s.Require().NoError(clientcmd.WriteToFile(*test.KubeConfigFake(), s.kubeconfigFile))
231234

232235
// Wait a reasonable amount of time to verify no callbacks are triggered
233-
// Using WaitForCondition with a condition that should NOT become true
234-
err := test.WaitForCondition(50*time.Millisecond, func() bool {
236+
// We expect this to never happen because no callbacks should be triggered after close
237+
s.Never(func() bool {
235238
return callCount.Load() > countAfterClose
236-
})
237-
// We expect this to timeout (return error) because no callbacks should be triggered
238-
s.Error(err, "no callbacks should be triggered after close")
239+
}, 50*time.Millisecond, kubeconfigEventuallyTick, "no callbacks should be triggered after close")
239240
s.Equal(countAfterClose, callCount.Load(), "call count should remain unchanged after close")
240241
})
241242

@@ -245,9 +246,9 @@ func (s *KubeconfigTestSuite) TestClose() {
245246
watcher.Watch(func() error { return nil })
246247

247248
// Wait for the watcher to be ready
248-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
249+
s.Eventually(func() bool {
249250
return watcher.started
250-
}), "timeout waiting for watcher to be ready")
251+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for watcher to be ready")
251252

252253
s.NotPanics(func() {
253254
watcher.Close()
@@ -261,9 +262,9 @@ func (s *KubeconfigTestSuite) TestClose() {
261262
watcher.Watch(func() error { return nil })
262263

263264
// Wait for the watcher to be ready
264-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
265+
s.Eventually(func() bool {
265266
return watcher.started
266-
}), "timeout waiting for watcher to be ready")
267+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for watcher to be ready")
267268

268269
// First close - normal operation
269270
watcher.Close()
@@ -299,9 +300,9 @@ func (s *KubeconfigTestSuite) TestClose() {
299300
})
300301

301302
// Wait for the watcher to be ready
302-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
303+
s.Eventually(func() bool {
303304
return watcher.started
304-
}), "timeout waiting for watcher to be ready")
305+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for watcher to be ready")
305306

306307
// Trigger a file change to start the debounce timer
307308
s.Require().NoError(clientcmd.WriteToFile(*test.KubeConfigFake(), s.kubeconfigFile))
@@ -331,9 +332,9 @@ func (s *KubeconfigTestSuite) TestClose() {
331332
})
332333

333334
// Wait for the watcher to be ready
334-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
335+
s.Eventually(func() bool {
335336
return watcher.started
336-
}), "timeout waiting for watcher to be ready")
337+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for watcher to be ready")
337338

338339
// Close the watcher
339340
watcher.Close()
@@ -350,17 +351,17 @@ func (s *KubeconfigTestSuite) TestClose() {
350351
})
351352

352353
// Wait for the new watcher to be ready
353-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
354+
s.Eventually(func() bool {
354355
return watcher.started
355-
}), "timeout waiting for restarted watcher to be ready")
356+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for restarted watcher to be ready")
356357

357358
// Trigger a file change
358359
s.Require().NoError(clientcmd.WriteToFile(*test.KubeConfigFake(), s.kubeconfigFile))
359360

360361
// Wait for callback
361-
s.Require().NoError(test.WaitForCondition(kubeconfigTestTimeout, func() bool {
362+
s.Eventually(func() bool {
362363
return secondCallbackTriggered.Load()
363-
}), "timeout waiting for restarted watcher callback")
364+
}, kubeconfigTestTimeout, kubeconfigEventuallyTick, "timeout waiting for restarted watcher callback")
364365

365366
watcher.Close()
366367
})

0 commit comments

Comments
 (0)