| 224 | } |
| 225 | |
| 226 | func TestWatcher_ThreadSafety(t *testing.T) { |
| 227 | t.Run("concurrent Run calls should be safe", func(t *testing.T) { |
| 228 | callCount := 0 |
| 229 | var mu sync.Mutex |
| 230 | |
| 231 | watcher := &Watcher{ |
| 232 | readyCh: make(chan struct{}), |
| 233 | watchingFunc: func() error { |
| 234 | mu.Lock() |
| 235 | callCount++ |
| 236 | mu.Unlock() |
| 237 | time.Sleep(50 * time.Millisecond) |
| 238 | return nil |
| 239 | }, |
| 240 | isReady: false, |
| 241 | } |
| 242 | |
| 243 | var wg sync.WaitGroup |
| 244 | // Start multiple goroutines calling Run |
| 245 | for i := 0; i < 10; i++ { |
| 246 | wg.Add(1) |
| 247 | go func() { |
| 248 | defer wg.Done() |
| 249 | err := watcher.Run() |
| 250 | assert.NoError(t, err) |
| 251 | }() |
| 252 | } |
| 253 | |
| 254 | wg.Wait() |
| 255 | |
| 256 | mu.Lock() |
| 257 | assert.Equal(t, 1, callCount, "watchingFunc should only be called once despite concurrent calls") |
| 258 | mu.Unlock() |
| 259 | }) |
| 260 | |
| 261 | t.Run("concurrent WaitReady and Run should work", func(t *testing.T) { |
| 262 | watcher := &Watcher{ |
| 263 | readyCh: make(chan struct{}), |
| 264 | watchingFunc: func() error { |
| 265 | time.Sleep(50 * time.Millisecond) |
| 266 | return nil |
| 267 | }, |
| 268 | isReady: false, |
| 269 | } |
| 270 | |
| 271 | var wg sync.WaitGroup |
| 272 | |
| 273 | // Multiple WaitReady calls |
| 274 | for i := 0; i < 5; i++ { |
| 275 | wg.Add(1) |
| 276 | go func() { |
| 277 | defer wg.Done() |
| 278 | err := watcher.WaitReady(2 * time.Second) |
| 279 | assert.NoError(t, err) |
| 280 | }() |
| 281 | } |
| 282 | |
| 283 | // One Run call |