github.com/lirm/aeron-go@v0.0.0-20230415210743-920325491dc4/aeron/subscription_test.go (about)

     1  // Copyright 2022 Steven Stern
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package aeron
    16  
    17  import (
    18  	"github.com/lirm/aeron-go/aeron/atomic"
    19  	"github.com/lirm/aeron-go/aeron/counters"
    20  	"github.com/lirm/aeron-go/aeron/logbuffer"
    21  	"github.com/lirm/aeron-go/aeron/logbuffer/term"
    22  	"github.com/stretchr/testify/mock"
    23  	"github.com/stretchr/testify/suite"
    24  	"math"
    25  	"testing"
    26  )
    27  
    28  const (
    29  	Channel            = "aeron:udp?endpoint=localhost:40124"
    30  	StreamId           = int32(1002)
    31  	RegistrationId     = int64(10)
    32  	ChannelStatusId    = int32(100)
    33  	ReadBufferCapacity = 1024
    34  	FragmentCountLimit = math.MaxInt32
    35  )
    36  
    37  type SubscriptionTestSuite struct {
    38  	suite.Suite
    39  	headerLength        int32 // Effectively a const, but DataFrameHeader declares it in a struct
    40  	atomicReadBuffer    *atomic.Buffer
    41  	cc                  *MockReceivingConductor
    42  	fragmentHandlerMock *term.MockFragmentHandler
    43  	fragmentHandler     term.FragmentHandler // References the mock's func.  Helps readability
    44  	imageOne            *MockImage
    45  	imageTwo            *MockImage
    46  	header              *logbuffer.Header
    47  	sub                 *Subscription
    48  }
    49  
    50  func (s *SubscriptionTestSuite) SetupTest() {
    51  	s.headerLength = logbuffer.DataFrameHeader.Length
    52  	s.atomicReadBuffer = atomic.MakeBuffer(make([]byte, s.headerLength), s.headerLength)
    53  	s.cc = NewMockReceivingConductor(s.T())
    54  	s.fragmentHandlerMock = term.NewMockFragmentHandler(s.T())
    55  	s.fragmentHandler = s.fragmentHandlerMock.Execute
    56  	s.imageOne = NewMockImage(s.T())
    57  	s.imageTwo = NewMockImage(s.T())
    58  	s.header = new(logbuffer.Header) // Unused so no need to initialize
    59  	s.sub = NewSubscription(s.cc, Channel, RegistrationId, StreamId, ChannelStatusId, nil, nil)
    60  }
    61  
    62  func (s *SubscriptionTestSuite) TestShouldEnsureTheSubscriptionIsOpenWhenPolling() {
    63  	s.cc.On("releaseSubscription", RegistrationId, mock.Anything).Return(nil)
    64  
    65  	s.Require().NoError(s.sub.Close())
    66  	s.Assert().True(s.sub.IsClosed())
    67  }
    68  
    69  func (s *SubscriptionTestSuite) TestShouldReadNothingWhenNoImages() {
    70  	fragments := s.sub.Poll(s.fragmentHandler, 1)
    71  	s.Assert().Equal(0, fragments)
    72  }
    73  
    74  func (s *SubscriptionTestSuite) TestShouldReadNothingWhenThereIsNoData() {
    75  	s.sub.addImage(s.imageOne)
    76  	s.imageOne.On("Poll", mock.Anything, mock.Anything).Return(0, nil)
    77  
    78  	fragments := s.sub.Poll(s.fragmentHandler, 1)
    79  	s.Assert().Equal(0, fragments)
    80  }
    81  
    82  func (s *SubscriptionTestSuite) TestShouldReadData() {
    83  	s.sub.addImage(s.imageOne)
    84  
    85  	s.fragmentHandlerMock.On("Execute",
    86  		s.atomicReadBuffer, s.headerLength, ReadBufferCapacity-s.headerLength, s.header)
    87  
    88  	s.imageOne.On("Poll", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
    89  		handler := args.Get(0).(term.FragmentHandler)
    90  		handler(s.atomicReadBuffer, s.headerLength, ReadBufferCapacity-s.headerLength, s.header)
    91  	}).Return(1, nil)
    92  
    93  	fragments := s.sub.Poll(s.fragmentHandler, FragmentCountLimit)
    94  	s.Assert().Equal(1, fragments)
    95  }
    96  
    97  func (s *SubscriptionTestSuite) TestShouldReadDataFromMultipleSources() {
    98  	s.sub.addImage(s.imageOne)
    99  	s.sub.addImage(s.imageTwo)
   100  
   101  	s.imageOne.On("Poll", mock.Anything, mock.Anything).Return(1, nil)
   102  	s.imageTwo.On("Poll", mock.Anything, mock.Anything).Return(1, nil)
   103  
   104  	fragments := s.sub.Poll(s.fragmentHandler, FragmentCountLimit)
   105  	s.Assert().Equal(2, fragments)
   106  }
   107  
   108  // TODO: Implement resolveChannel set of tests.
   109  
   110  func TestSubscription(t *testing.T) {
   111  	suite.Run(t, new(SubscriptionTestSuite))
   112  }
   113  
   114  // Everything below is auto generated by mockery using this command:
   115  // mockery --name=ReceivingConductor --inpackage --structname=MockReceivingConductor --print
   116  
   117  // MockReceivingConductor is an autogenerated mock type for the ReceivingConductor type
   118  type MockReceivingConductor struct {
   119  	mock.Mock
   120  }
   121  
   122  // AddRcvDestination provides a mock function with given fields: registrationID, endpointChannel
   123  func (_m *MockReceivingConductor) AddRcvDestination(registrationID int64, endpointChannel string) error {
   124  	ret := _m.Called(registrationID, endpointChannel)
   125  
   126  	var r0 error
   127  	if rf, ok := ret.Get(0).(func(int64, string) error); ok {
   128  		r0 = rf(registrationID, endpointChannel)
   129  	} else {
   130  		r0 = ret.Error(0)
   131  	}
   132  
   133  	return r0
   134  }
   135  
   136  // CounterReader provides a mock function with given fields:
   137  func (_m *MockReceivingConductor) CounterReader() *counters.Reader {
   138  	ret := _m.Called()
   139  
   140  	var r0 *counters.Reader
   141  	if rf, ok := ret.Get(0).(func() *counters.Reader); ok {
   142  		r0 = rf()
   143  	} else {
   144  		if ret.Get(0) != nil {
   145  			r0 = ret.Get(0).(*counters.Reader)
   146  		}
   147  	}
   148  
   149  	return r0
   150  }
   151  
   152  // RemoveRcvDestination provides a mock function with given fields: registrationID, endpointChannel
   153  func (_m *MockReceivingConductor) RemoveRcvDestination(registrationID int64, endpointChannel string) error {
   154  	ret := _m.Called(registrationID, endpointChannel)
   155  
   156  	var r0 error
   157  	if rf, ok := ret.Get(0).(func(int64, string) error); ok {
   158  		r0 = rf(registrationID, endpointChannel)
   159  	} else {
   160  		r0 = ret.Error(0)
   161  	}
   162  
   163  	return r0
   164  }
   165  
   166  // releaseSubscription provides a mock function with given fields: regID, images
   167  func (_m *MockReceivingConductor) releaseSubscription(regID int64, images []Image) error {
   168  	ret := _m.Called(regID, images)
   169  
   170  	var r0 error
   171  	if rf, ok := ret.Get(0).(func(int64, []Image) error); ok {
   172  		r0 = rf(regID, images)
   173  	} else {
   174  		r0 = ret.Error(0)
   175  	}
   176  
   177  	return r0
   178  }
   179  
   180  type mockConstructorTestingTNewMockReceivingConductor interface {
   181  	mock.TestingT
   182  	Cleanup(func())
   183  }
   184  
   185  // NewMockReceivingConductor creates a new instance of MockReceivingConductor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
   186  func NewMockReceivingConductor(t mockConstructorTestingTNewMockReceivingConductor) *MockReceivingConductor {
   187  	mock := &MockReceivingConductor{}
   188  	mock.Mock.Test(t)
   189  
   190  	t.Cleanup(func() { mock.AssertExpectations(t) })
   191  
   192  	return mock
   193  }