github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/eventloop/event_loop_test.go (about) 1 package eventloop 2 3 import ( 4 "context" 5 "io" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/rs/zerolog" 11 "github.com/stretchr/testify/mock" 12 "github.com/stretchr/testify/require" 13 "github.com/stretchr/testify/suite" 14 "go.uber.org/atomic" 15 16 "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks" 17 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 18 "github.com/koko1123/flow-go-1/module/irrecoverable" 19 "github.com/koko1123/flow-go-1/module/metrics" 20 "github.com/koko1123/flow-go-1/utils/unittest" 21 ) 22 23 // TestEventLoop performs unit testing of event loop, checks if submitted events are propagated 24 // to event handler as well as handling of timeouts. 25 func TestEventLoop(t *testing.T) { 26 suite.Run(t, new(EventLoopTestSuite)) 27 } 28 29 type EventLoopTestSuite struct { 30 suite.Suite 31 32 eh *mocks.EventHandlerV2 33 cancel context.CancelFunc 34 35 eventLoop *EventLoop 36 } 37 38 func (s *EventLoopTestSuite) SetupTest() { 39 s.eh = &mocks.EventHandlerV2{} 40 s.eh.On("Start").Return(nil).Maybe() 41 s.eh.On("TimeoutChannel").Return(time.NewTimer(10 * time.Second).C).Maybe() 42 s.eh.On("OnLocalTimeout").Return(nil).Maybe() 43 44 log := zerolog.New(io.Discard) 45 46 eventLoop, err := NewEventLoop(log, metrics.NewNoopCollector(), s.eh, time.Time{}) 47 require.NoError(s.T(), err) 48 s.eventLoop = eventLoop 49 50 ctx, cancel := context.WithCancel(context.Background()) 51 s.cancel = cancel 52 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 53 54 s.eventLoop.Start(signalerCtx) 55 unittest.RequireCloseBefore(s.T(), s.eventLoop.Ready(), 100*time.Millisecond, "event loop not started") 56 } 57 58 func (s *EventLoopTestSuite) TearDownTest() { 59 s.cancel() 60 unittest.RequireCloseBefore(s.T(), s.eventLoop.Done(), 100*time.Millisecond, "event loop not stopped") 61 } 62 63 // TestReadyDone tests if event loop stops internal worker thread 64 func (s *EventLoopTestSuite) TestReadyDone() { 65 time.Sleep(1 * time.Second) 66 go func() { 67 s.cancel() 68 }() 69 70 unittest.RequireCloseBefore(s.T(), s.eventLoop.Done(), 100*time.Millisecond, "event loop not stopped") 71 } 72 73 // Test_SubmitQC tests that submitted proposal is eventually sent to event handler for processing 74 func (s *EventLoopTestSuite) Test_SubmitProposal() { 75 proposal := unittest.BlockHeaderFixture() 76 expectedProposal := model.ProposalFromFlow(proposal, proposal.View-1) 77 processed := atomic.NewBool(false) 78 s.eh.On("OnReceiveProposal", expectedProposal).Run(func(args mock.Arguments) { 79 processed.Store(true) 80 }).Return(nil).Once() 81 s.eventLoop.SubmitProposal(proposal, proposal.View-1) 82 require.Eventually(s.T(), processed.Load, time.Millisecond*100, time.Millisecond*10) 83 s.eh.AssertExpectations(s.T()) 84 } 85 86 // Test_SubmitQC tests that submitted QC is eventually sent to event handler for processing 87 func (s *EventLoopTestSuite) Test_SubmitQC() { 88 qc := unittest.QuorumCertificateFixture() 89 processed := atomic.NewBool(false) 90 s.eh.On("OnQCConstructed", qc).Run(func(args mock.Arguments) { 91 processed.Store(true) 92 }).Return(nil).Once() 93 s.eventLoop.SubmitTrustedQC(qc) 94 require.Eventually(s.T(), processed.Load, time.Millisecond*100, time.Millisecond*10) 95 s.eh.AssertExpectations(s.T()) 96 } 97 98 // TestEventLoop_Timeout tests that event loop delivers timeout events to event handler under pressure 99 func TestEventLoop_Timeout(t *testing.T) { 100 eh := &mocks.EventHandlerV2{} 101 processed := atomic.NewBool(false) 102 eh.On("Start").Return(nil).Once() 103 eh.On("TimeoutChannel").Return(time.NewTimer(100 * time.Millisecond).C) 104 eh.On("OnQCConstructed", mock.Anything).Return(nil).Maybe() 105 eh.On("OnReceiveProposal", mock.Anything).Return(nil).Maybe() 106 eh.On("OnLocalTimeout").Run(func(args mock.Arguments) { 107 processed.Store(true) 108 }).Return(nil).Once() 109 110 log := zerolog.New(io.Discard) 111 112 eventLoop, err := NewEventLoop(log, metrics.NewNoopCollector(), eh, time.Time{}) 113 require.NoError(t, err) 114 115 ctx, cancel := context.WithCancel(context.Background()) 116 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 117 eventLoop.Start(signalerCtx) 118 119 unittest.RequireCloseBefore(t, eventLoop.Ready(), 100*time.Millisecond, "event loop not stopped") 120 121 time.Sleep(10 * time.Millisecond) 122 123 var wg sync.WaitGroup 124 wg.Add(2) 125 126 // spam with proposals and QCs 127 go func() { 128 defer wg.Done() 129 for !processed.Load() { 130 qc := unittest.QuorumCertificateFixture() 131 eventLoop.SubmitTrustedQC(qc) 132 } 133 }() 134 135 go func() { 136 defer wg.Done() 137 for !processed.Load() { 138 proposal := unittest.BlockHeaderFixture() 139 eventLoop.SubmitProposal(proposal, proposal.View-1) 140 } 141 }() 142 143 require.Eventually(t, processed.Load, time.Millisecond*200, time.Millisecond*10) 144 wg.Wait() 145 146 cancel() 147 unittest.RequireCloseBefore(t, eventLoop.Done(), 100*time.Millisecond, "event loop not stopped") 148 } 149 150 // TestReadyDoneWithStartTime tests that event loop correctly starts and schedules start of processing 151 // when startTime argument is used 152 func TestReadyDoneWithStartTime(t *testing.T) { 153 eh := &mocks.EventHandlerV2{} 154 eh.On("Start").Return(nil) 155 eh.On("TimeoutChannel").Return(time.NewTimer(10 * time.Second).C) 156 eh.On("OnLocalTimeout").Return(nil) 157 158 metrics := metrics.NewNoopCollector() 159 160 log := zerolog.New(io.Discard) 161 162 startTimeDuration := 2 * time.Second 163 startTime := time.Now().Add(startTimeDuration) 164 eventLoop, err := NewEventLoop(log, metrics, eh, startTime) 165 require.NoError(t, err) 166 167 done := make(chan struct{}) 168 eh.On("OnReceiveProposal", mock.AnythingOfType("*model.Proposal")).Run(func(args mock.Arguments) { 169 require.True(t, time.Now().After(startTime)) 170 close(done) 171 }).Return(nil).Once() 172 173 ctx, cancel := context.WithCancel(context.Background()) 174 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 175 eventLoop.Start(signalerCtx) 176 177 unittest.RequireCloseBefore(t, eventLoop.Ready(), 100*time.Millisecond, "event loop not started") 178 179 parentBlock := unittest.BlockHeaderFixture() 180 block := unittest.BlockHeaderWithParentFixture(parentBlock) 181 eventLoop.SubmitProposal(block, parentBlock.View) 182 183 unittest.RequireCloseBefore(t, done, startTimeDuration+100*time.Millisecond, "proposal wasn't received") 184 cancel() 185 unittest.RequireCloseBefore(t, eventLoop.Done(), 100*time.Millisecond, "event loop not stopped") 186 }