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  }