github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/changestream/eventqueue/eventqueue_test.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package eventqueue 5 6 import ( 7 "time" 8 9 jc "github.com/juju/testing/checkers" 10 "github.com/juju/worker/v3/workertest" 11 gc "gopkg.in/check.v1" 12 13 "github.com/juju/juju/core/changestream" 14 "github.com/juju/juju/testing" 15 ) 16 17 type eventQueueSuite struct { 18 baseSuite 19 } 20 21 var _ = gc.Suite(&eventQueueSuite{}) 22 23 func (s *eventQueueSuite) TestSubscribe(c *gc.C) { 24 defer s.setupMocks(c).Finish() 25 26 s.expectAnyLogs() 27 28 changes := make(chan changestream.ChangeEvent) 29 defer close(changes) 30 31 s.stream.EXPECT().Changes().Return(changes).AnyTimes() 32 33 queue, err := New(s.stream, s.logger) 34 c.Assert(err, jc.ErrorIsNil) 35 defer workertest.DirtyKill(c, queue) 36 37 sub, err := queue.Subscribe(changestream.Namespace("topic", changestream.Create)) 38 c.Assert(err, jc.ErrorIsNil) 39 40 s.unsubscribe(c, sub) 41 42 workertest.CleanKill(c, queue) 43 } 44 45 func (s *eventQueueSuite) TestDispatch(c *gc.C) { 46 defer s.setupMocks(c).Finish() 47 48 s.expectAnyLogs() 49 50 changes := make(chan changestream.ChangeEvent) 51 defer close(changes) 52 53 s.stream.EXPECT().Changes().Return(changes).MinTimes(1) 54 55 queue, err := New(s.stream, s.logger) 56 c.Assert(err, jc.ErrorIsNil) 57 defer workertest.DirtyKill(c, queue) 58 59 sub, err := queue.Subscribe(changestream.Namespace("topic", changestream.Create)) 60 c.Assert(err, jc.ErrorIsNil) 61 62 s.expectChangeEvent(changestream.Create, "topic") 63 s.dispatchEvent(c, changes) 64 65 select { 66 case event := <-sub.Changes(): 67 c.Assert(event.Type(), jc.DeepEquals, changestream.Create) 68 c.Assert(event.Namespace(), jc.DeepEquals, "topic") 69 case <-time.After(testing.ShortWait): 70 c.Fatal("timed out waiting for event") 71 } 72 73 s.unsubscribe(c, sub) 74 75 workertest.CleanKill(c, queue) 76 } 77 78 func (s *eventQueueSuite) TestUnsubscribeDuringDispatch(c *gc.C) { 79 defer s.setupMocks(c).Finish() 80 81 s.expectAnyLogs() 82 83 changes := make(chan changestream.ChangeEvent) 84 defer close(changes) 85 86 s.stream.EXPECT().Changes().Return(changes).MinTimes(1) 87 88 queue, err := New(s.stream, s.logger) 89 c.Assert(err, jc.ErrorIsNil) 90 defer workertest.DirtyKill(c, queue) 91 92 sub, err := queue.Subscribe(changestream.Namespace("topic", changestream.Create)) 93 c.Assert(err, jc.ErrorIsNil) 94 95 s.expectChangeEvent(changestream.Create, "topic") 96 s.dispatchEvent(c, changes) 97 98 select { 99 case <-sub.Changes(): 100 s.unsubscribe(c, sub) 101 case <-time.After(testing.ShortWait): 102 c.Fatal("timed out waiting for event") 103 } 104 105 select { 106 case <-sub.Done(): 107 case <-time.After(testing.ShortWait): 108 c.Fatal("timed out waiting for event") 109 } 110 111 workertest.CleanKill(c, queue) 112 } 113 114 func (s *eventQueueSuite) TestMultipleDispatch(c *gc.C) { 115 s.testMultipleDispatch(c, changestream.Namespace("topic", changestream.Update)) 116 } 117 118 func (s *eventQueueSuite) TestDispatchWithNoOptions(c *gc.C) { 119 s.testMultipleDispatch(c) 120 } 121 122 func (s *eventQueueSuite) TestMultipleDispatchWithMultipleMasks(c *gc.C) { 123 s.testMultipleDispatch(c, changestream.Namespace("topic", changestream.Create|changestream.Update)) 124 } 125 126 func (s *eventQueueSuite) TestMultipleDispatchWithMultipleOptions(c *gc.C) { 127 s.testMultipleDispatch(c, changestream.Namespace("topic", changestream.Update), changestream.Namespace("topic", changestream.Create)) 128 } 129 130 func (s *eventQueueSuite) TestMultipleDispatchWithOverlappingOptions(c *gc.C) { 131 s.testMultipleDispatch(c, changestream.Namespace("topic", changestream.Update), changestream.Namespace("topic", changestream.Update|changestream.Create)) 132 } 133 134 func (s *eventQueueSuite) TestSubscribeWithMatchingFilter(c *gc.C) { 135 s.testMultipleDispatch(c, changestream.FilteredNamespace("topic", changestream.Update, func(event changestream.ChangeEvent) bool { 136 return event.Namespace() == "topic" 137 })) 138 } 139 140 func (s *eventQueueSuite) testMultipleDispatch(c *gc.C, opts ...changestream.SubscriptionOption) { 141 defer s.setupMocks(c).Finish() 142 143 s.expectAnyLogs() 144 145 changes := make(chan changestream.ChangeEvent) 146 defer close(changes) 147 148 s.stream.EXPECT().Changes().Return(changes).MinTimes(1) 149 150 queue, err := New(s.stream, s.logger) 151 c.Assert(err, jc.ErrorIsNil) 152 defer workertest.DirtyKill(c, queue) 153 154 s.expectChangeEvent(changestream.Update, "topic") 155 156 subs := make([]changestream.Subscription, 10) 157 for i := 0; i < len(subs); i++ { 158 sub, err := queue.Subscribe(opts...) 159 c.Assert(err, jc.ErrorIsNil) 160 161 subs[i] = sub 162 } 163 164 done := s.dispatchEvent(c, changes) 165 select { 166 case <-done: 167 case <-time.After(testing.ShortWait): 168 c.Fatal("timed out waiting for dispatching event") 169 } 170 171 // The subscriptions are guaranteed to be out of order, so we need to just 172 // wait on them all, and then check that they all got the event. 173 wg := newWaitGroup(uint64(len(subs))) 174 for i, sub := range subs { 175 go func(sub changestream.Subscription, i int) { 176 defer wg.Done() 177 178 select { 179 case event := <-sub.Changes(): 180 c.Assert(event.Type(), jc.DeepEquals, changestream.Update) 181 c.Assert(event.Namespace(), jc.DeepEquals, "topic") 182 case <-time.After(testing.ShortWait): 183 c.Fatalf("timed out waiting for sub %d event", i) 184 } 185 }(sub, i) 186 } 187 188 select { 189 case <-wg.Wait(): 190 case <-time.After(testing.ShortWait): 191 c.Fatal("timed out waiting for all events") 192 } 193 194 for _, sub := range subs { 195 s.unsubscribe(c, sub) 196 } 197 198 workertest.CleanKill(c, queue) 199 } 200 201 func (s *eventQueueSuite) TestUnsubscribeTwice(c *gc.C) { 202 defer s.setupMocks(c).Finish() 203 204 s.expectAnyLogs() 205 206 changes := make(chan changestream.ChangeEvent) 207 defer close(changes) 208 209 s.stream.EXPECT().Changes().Return(changes).MinTimes(1) 210 211 queue, err := New(s.stream, s.logger) 212 c.Assert(err, jc.ErrorIsNil) 213 defer workertest.DirtyKill(c, queue) 214 215 sub, err := queue.Subscribe(changestream.Namespace("topic", changestream.Create)) 216 c.Assert(err, jc.ErrorIsNil) 217 218 s.expectChangeEvent(changestream.Create, "topic") 219 s.dispatchEvent(c, changes) 220 221 select { 222 case <-sub.Changes(): 223 case <-time.After(testing.ShortWait): 224 c.Fatal("timed out waiting for event") 225 } 226 227 s.unsubscribe(c, sub) 228 s.unsubscribe(c, sub) 229 230 workertest.CleanKill(c, queue) 231 } 232 233 func (s *eventQueueSuite) TestTopicDoesNotMatch(c *gc.C) { 234 defer s.setupMocks(c).Finish() 235 236 s.expectAnyLogs() 237 238 changes := make(chan changestream.ChangeEvent) 239 defer close(changes) 240 241 s.stream.EXPECT().Changes().Return(changes).MinTimes(1) 242 243 queue, err := New(s.stream, s.logger) 244 c.Assert(err, jc.ErrorIsNil) 245 defer workertest.DirtyKill(c, queue) 246 247 sub, err := queue.Subscribe(changestream.Namespace("topic", changestream.Create)) 248 c.Assert(err, jc.ErrorIsNil) 249 250 s.changeEvent.EXPECT().Namespace().Return("foo").MinTimes(1) 251 252 done := s.dispatchEvent(c, changes) 253 select { 254 case <-done: 255 case <-time.After(testing.ShortWait): 256 c.Fatal("timed out waiting for event") 257 } 258 259 s.unsubscribe(c, sub) 260 261 workertest.CleanKill(c, queue) 262 } 263 264 func (s *eventQueueSuite) TestTopicMatchesOne(c *gc.C) { 265 defer s.setupMocks(c).Finish() 266 267 s.expectAnyLogs() 268 269 changes := make(chan changestream.ChangeEvent) 270 defer close(changes) 271 272 s.stream.EXPECT().Changes().Return(changes).MinTimes(1) 273 274 queue, err := New(s.stream, s.logger) 275 c.Assert(err, jc.ErrorIsNil) 276 defer workertest.DirtyKill(c, queue) 277 278 sub0, err := queue.Subscribe(changestream.Namespace("foo", changestream.Create)) 279 c.Assert(err, jc.ErrorIsNil) 280 281 sub1, err := queue.Subscribe(changestream.Namespace("topic", changestream.Create)) 282 c.Assert(err, jc.ErrorIsNil) 283 284 s.expectChangeEvent(changestream.Create, "topic") 285 done := s.dispatchEvent(c, changes) 286 287 select { 288 case <-done: 289 case <-time.After(testing.ShortWait): 290 c.Fatal("timed out waiting for event") 291 } 292 293 select { 294 case <-sub1.Changes(): 295 case <-time.After(testing.ShortWait): 296 c.Fatal("timed out waiting for event") 297 } 298 299 select { 300 case <-sub0.Changes(): 301 c.Fatal("unexpected event on sub0") 302 case <-time.After(time.Second): 303 } 304 305 s.unsubscribe(c, sub0) 306 s.unsubscribe(c, sub1) 307 308 workertest.CleanKill(c, queue) 309 } 310 311 func (s *eventQueueSuite) TestSubscriptionDoneWhenEventQueueKilled(c *gc.C) { 312 defer s.setupMocks(c).Finish() 313 314 s.expectAnyLogs() 315 316 changes := make(chan changestream.ChangeEvent) 317 defer close(changes) 318 319 s.stream.EXPECT().Changes().Return(changes).MinTimes(1) 320 321 queue, err := New(s.stream, s.logger) 322 c.Assert(err, jc.ErrorIsNil) 323 defer workertest.DirtyKill(c, queue) 324 325 sub, err := queue.Subscribe(changestream.Namespace("topic", changestream.Create)) 326 c.Assert(err, jc.ErrorIsNil) 327 328 s.expectChangeEvent(changestream.Create, "topic") 329 done := s.dispatchEvent(c, changes) 330 331 select { 332 case <-done: 333 case <-time.After(testing.ShortWait): 334 c.Fatal("timed out waiting for event") 335 } 336 337 workertest.CleanKill(c, queue) 338 339 select { 340 case <-sub.Done(): 341 case <-time.After(testing.ShortWait): 342 c.Fatal("timed out waiting for event") 343 } 344 } 345 346 func (s *eventQueueSuite) TestUnsubscribeOfOtherSubscription(c *gc.C) { 347 defer s.setupMocks(c).Finish() 348 349 s.expectAnyLogs() 350 351 changes := make(chan changestream.ChangeEvent) 352 defer close(changes) 353 354 s.stream.EXPECT().Changes().Return(changes).MinTimes(1) 355 356 queue, err := New(s.stream, s.logger) 357 c.Assert(err, jc.ErrorIsNil) 358 defer workertest.DirtyKill(c, queue) 359 360 subs := make([]changestream.Subscription, 2) 361 for i := 0; i < len(subs); i++ { 362 363 sub, err := queue.Subscribe(changestream.Namespace("topic", changestream.Create)) 364 c.Assert(err, jc.ErrorIsNil) 365 subs[i] = sub 366 } 367 368 s.expectChangeEvent(changestream.Create, "topic") 369 s.dispatchEvent(c, changes) 370 371 // The subscriptions are guaranteed to be out of order, so we need to just 372 // wait on them all, and then check that they all got the event. 373 wg := newWaitGroup(uint64(len(subs))) 374 for i, sub := range subs { 375 go func(sub changestream.Subscription, i int) { 376 defer wg.Done() 377 378 select { 379 case <-sub.Changes(): 380 subs[len(subs)-1-i].Unsubscribe() 381 case <-time.After(testing.ShortWait): 382 c.Fatalf("timed out waiting for sub %d event", i) 383 } 384 }(sub, i) 385 } 386 387 select { 388 case <-wg.Wait(): 389 case <-time.After(testing.ShortWait): 390 c.Fatal("timed out waiting for all events") 391 } 392 393 for _, sub := range subs { 394 select { 395 case <-sub.Done(): 396 case <-time.After(testing.LongWait): 397 c.Fatal("timed out waiting for event") 398 } 399 } 400 401 workertest.CleanKill(c, queue) 402 } 403 404 func (s *eventQueueSuite) TestUnsubscribeOfOtherSubscriptionInAnotherGoroutine(c *gc.C) { 405 defer s.setupMocks(c).Finish() 406 407 s.expectAnyLogs() 408 409 changes := make(chan changestream.ChangeEvent) 410 defer close(changes) 411 412 s.stream.EXPECT().Changes().Return(changes).MinTimes(1) 413 414 queue, err := New(s.stream, s.logger) 415 c.Assert(err, jc.ErrorIsNil) 416 defer workertest.DirtyKill(c, queue) 417 418 subs := make([]changestream.Subscription, 2) 419 for i := 0; i < len(subs); i++ { 420 421 sub, err := queue.Subscribe(changestream.Namespace("topic", changestream.Create)) 422 c.Assert(err, jc.ErrorIsNil) 423 subs[i] = sub 424 } 425 426 s.expectChangeEvent(changestream.Create, "topic") 427 s.dispatchEvent(c, changes) 428 429 // The subscriptions are guaranteed to be out of order, so we need to just 430 // wait on them all, and then check that they all got the event. 431 wg := newWaitGroup(uint64(len(subs))) 432 for i, sub := range subs { 433 go func(sub changestream.Subscription, i int) { 434 select { 435 case <-sub.Changes(): 436 go func() { 437 defer wg.Done() 438 439 subs[len(subs)-1-i].Unsubscribe() 440 }() 441 case <-time.After(testing.ShortWait): 442 c.Fatalf("timed out waiting for sub %d event", i) 443 } 444 }(sub, i) 445 } 446 447 select { 448 case <-wg.Wait(): 449 case <-time.After(testing.ShortWait): 450 c.Fatal("timed out waiting for all events") 451 } 452 453 for _, sub := range subs { 454 select { 455 case <-sub.Done(): 456 case <-time.After(testing.LongWait): 457 c.Fatal("timed out waiting for event") 458 } 459 } 460 461 workertest.CleanKill(c, queue) 462 } 463 464 func (s *eventQueueSuite) unsubscribe(c *gc.C, sub changestream.Subscription) { 465 sub.Unsubscribe() 466 467 select { 468 case <-sub.Done(): 469 case <-time.After(testing.ShortWait): 470 c.Fatal("timed out waiting for event") 471 } 472 }