github.com/koko1123/flow-go-1@v0.29.6/engine/enqueue_test.go (about) 1 package engine_test 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/rs/zerolog" 11 "github.com/stretchr/testify/require" 12 13 "github.com/koko1123/flow-go-1/engine" 14 "github.com/koko1123/flow-go-1/engine/common/fifoqueue" 15 "github.com/koko1123/flow-go-1/model/flow" 16 "github.com/koko1123/flow-go-1/utils/unittest" 17 ) 18 19 // TestEngine tests the integration of MessageHandler and FifoQueue that buffer and deliver 20 // matched messages to corresponding handlers 21 type TestEngine struct { 22 unit *engine.Unit 23 log zerolog.Logger 24 ready sync.WaitGroup 25 messageHandler *engine.MessageHandler 26 queueA *engine.FifoMessageStore 27 queueB *engine.FifoMessageStore 28 29 mu sync.RWMutex 30 messages []interface{} 31 } 32 33 type messageA struct { 34 n int 35 } 36 37 type messageB struct { 38 n int 39 } 40 41 type messageC struct { 42 s string 43 } 44 45 func NewEngine(log zerolog.Logger, capacity int) (*TestEngine, error) { 46 fifoQueueA, err := fifoqueue.NewFifoQueue(capacity) 47 if err != nil { 48 return nil, fmt.Errorf("failed to create queue A: %w", err) 49 } 50 51 fifoQueueB, err := fifoqueue.NewFifoQueue(capacity) 52 if err != nil { 53 return nil, fmt.Errorf("failed to create queue B: %w", err) 54 } 55 56 queueA := &engine.FifoMessageStore{ 57 FifoQueue: fifoQueueA, 58 } 59 60 queueB := &engine.FifoMessageStore{ 61 FifoQueue: fifoQueueB, 62 } 63 64 // define message queueing behaviour 65 handler := engine.NewMessageHandler( 66 log, 67 engine.NewNotifier(), 68 engine.Pattern{ 69 Match: func(msg *engine.Message) bool { 70 switch msg.Payload.(type) { 71 case *messageA: 72 return true 73 default: 74 return false 75 } 76 }, 77 Store: queueA, 78 }, 79 engine.Pattern{ 80 Match: func(msg *engine.Message) bool { 81 switch msg.Payload.(type) { 82 case *messageB: 83 return true 84 default: 85 return false 86 } 87 }, 88 Map: func(msg *engine.Message) (*engine.Message, bool) { 89 b := msg.Payload.(*messageB) 90 msg = &engine.Message{ 91 OriginID: msg.OriginID, 92 Payload: &messageC{ 93 s: fmt.Sprintf("c-%v", b.n), 94 }, 95 } 96 return msg, true 97 }, 98 Store: queueB, 99 }, 100 ) 101 102 eng := &TestEngine{ 103 unit: engine.NewUnit(), 104 log: log, 105 messageHandler: handler, 106 queueA: queueA, 107 queueB: queueB, 108 } 109 110 return eng, nil 111 } 112 113 func (e *TestEngine) Process(originID flow.Identifier, event interface{}) error { 114 return e.messageHandler.Process(originID, event) 115 } 116 117 func (e *TestEngine) Ready() <-chan struct{} { 118 e.ready.Add(1) 119 e.unit.Launch(e.loop) 120 return e.unit.Ready(func() { 121 e.ready.Wait() 122 }) 123 } 124 125 func (e *TestEngine) Done() <-chan struct{} { 126 return e.unit.Done() 127 } 128 129 func (e *TestEngine) loop() { 130 // let Ready() wait until the loop has started 131 // otherwise the message producer's doNotify will not be able to push messages 132 // to the e.notify channel 133 e.ready.Done() 134 135 for { 136 select { 137 case <-e.unit.Quit(): 138 return 139 case <-e.messageHandler.GetNotifier(): 140 e.log.Info().Msg("new message arrived") 141 err := e.processAvailableMessages() 142 if err != nil { 143 e.log.Fatal().Err(err).Msg("internal error processing message from the fifo queue") 144 } 145 e.log.Info().Msg("message processed") 146 } 147 } 148 } 149 150 func (e *TestEngine) processAvailableMessages() error { 151 for { 152 msg, ok := e.queueA.Get() 153 if ok { 154 err := e.OnProcessA(msg.OriginID, msg.Payload.(*messageA)) 155 if err != nil { 156 return fmt.Errorf("could not handle block proposal: %w", err) 157 } 158 continue 159 } 160 161 msg, ok = e.queueB.Get() 162 if ok { 163 err := e.OnProcessC(msg.OriginID, msg.Payload.(*messageC)) 164 if err != nil { 165 return fmt.Errorf("could not handle block vote: %w", err) 166 } 167 continue 168 } 169 170 // when there is no more messages in the queue, back to the loop to wait 171 // for the next incoming message to arrive. 172 return nil 173 } 174 175 } 176 177 func (e *TestEngine) OnProcessA(originID flow.Identifier, msg *messageA) error { 178 e.mu.Lock() 179 defer e.mu.Unlock() 180 e.messages = append(e.messages, msg) 181 return nil 182 } 183 184 func (e *TestEngine) OnProcessC(originID flow.Identifier, msg *messageC) error { 185 e.mu.Lock() 186 defer e.mu.Unlock() 187 e.messages = append(e.messages, msg) 188 return nil 189 } 190 191 func (e *TestEngine) MessageCount() int { 192 e.mu.RLock() 193 defer e.mu.RUnlock() 194 return len(e.messages) 195 } 196 197 func (e *TestEngine) AllCount() (int, int, int) { 198 e.mu.RLock() 199 defer e.mu.RUnlock() 200 return len(e.messages), e.queueA.Len(), e.queueB.Len() 201 } 202 203 func WithEngine(t *testing.T, f func(*TestEngine)) { 204 lg := unittest.Logger() 205 eng, err := NewEngine(lg, 150) 206 require.NoError(t, err) 207 <-eng.Ready() 208 f(eng) 209 <-eng.Done() 210 } 211 212 // if TestEngine receives messages of same type, the engine will handle them message 213 func TestProcessMessageSameType(t *testing.T) { 214 id1 := unittest.IdentifierFixture() 215 id2 := unittest.IdentifierFixture() 216 m1 := &messageA{n: 1} 217 m2 := &messageA{n: 2} 218 m3 := &messageA{n: 3} 219 m4 := &messageA{n: 4} 220 221 WithEngine(t, func(eng *TestEngine) { 222 require.NoError(t, eng.Process(id1, m1)) 223 require.NoError(t, eng.Process(id2, m2)) 224 require.NoError(t, eng.Process(id1, m3)) 225 require.NoError(t, eng.Process(id2, m4)) 226 227 require.Eventuallyf(t, func() bool { 228 return eng.MessageCount() == 4 229 }, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages", 230 4, eng.MessageCount()) 231 232 eng.mu.Lock() 233 defer eng.mu.Unlock() 234 require.Equal(t, m1, eng.messages[0]) 235 require.Equal(t, m2, eng.messages[1]) 236 require.Equal(t, m3, eng.messages[2]) 237 require.Equal(t, m4, eng.messages[3]) 238 }) 239 } 240 241 // if TestEngine receives messages of different types, the engine will handle them message 242 func TestProcessMessageDifferentType(t *testing.T) { 243 id1 := unittest.IdentifierFixture() 244 id2 := unittest.IdentifierFixture() 245 m1 := &messageA{n: 1} 246 m2 := &messageA{n: 2} 247 m3 := &messageB{n: 3} 248 m4 := &messageB{n: 4} 249 250 WithEngine(t, func(eng *TestEngine) { 251 require.NoError(t, eng.Process(id1, m1)) 252 require.NoError(t, eng.Process(id2, m2)) 253 require.NoError(t, eng.Process(id1, m3)) 254 require.NoError(t, eng.Process(id2, m4)) 255 256 require.Eventuallyf(t, func() bool { 257 return eng.MessageCount() == 4 258 }, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages", 259 4, eng.MessageCount()) 260 261 eng.mu.Lock() 262 defer eng.mu.Unlock() 263 require.Equal(t, m1, eng.messages[0]) 264 require.Equal(t, m2, eng.messages[1]) 265 require.Equal(t, &messageC{s: "c-3"}, eng.messages[2]) 266 require.Equal(t, &messageC{s: "c-4"}, eng.messages[3]) 267 }) 268 } 269 270 // if TestEngine receives messages in a period of time, they all will be handled 271 func TestProcessMessageInterval(t *testing.T) { 272 id1 := unittest.IdentifierFixture() 273 id2 := unittest.IdentifierFixture() 274 m1 := &messageA{n: 1} 275 m2 := &messageA{n: 2} 276 m3 := &messageA{n: 3} 277 m4 := &messageA{n: 4} 278 279 WithEngine(t, func(eng *TestEngine) { 280 require.NoError(t, eng.Process(id1, m1)) 281 time.Sleep(3 * time.Millisecond) 282 require.NoError(t, eng.Process(id2, m2)) 283 time.Sleep(3 * time.Millisecond) 284 require.NoError(t, eng.Process(id1, m3)) 285 time.Sleep(3 * time.Millisecond) 286 require.NoError(t, eng.Process(id2, m4)) 287 288 require.Eventuallyf(t, func() bool { 289 return eng.MessageCount() == 4 290 }, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages", 291 4, eng.MessageCount()) 292 eng.mu.Lock() 293 defer eng.mu.Unlock() 294 295 require.Equal(t, m1, eng.messages[0]) 296 require.Equal(t, m2, eng.messages[1]) 297 require.Equal(t, m3, eng.messages[2]) 298 require.Equal(t, m4, eng.messages[3]) 299 }) 300 } 301 302 // if TestEngine receives 100 messages for each type, the engine will handle them all 303 func TestProcessMessageMultiAll(t *testing.T) { 304 305 WithEngine(t, func(eng *TestEngine) { 306 count := 100 307 for i := 0; i < count; i++ { 308 require.NoError(t, eng.Process(unittest.IdentifierFixture(), &messageA{n: i})) 309 } 310 311 require.Eventuallyf(t, func() bool { 312 return eng.MessageCount() == count 313 }, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages", 314 count, eng.MessageCount()) 315 require.Equal(t, count, eng.MessageCount()) 316 }) 317 } 318 319 // if TestEngine receives 100 messages for each type with interval, the engine will handle them all 320 func TestProcessMessageMultiInterval(t *testing.T) { 321 322 WithEngine(t, func(eng *TestEngine) { 323 count := 100 324 for i := 0; i < count; i++ { 325 time.Sleep(1 * time.Millisecond) 326 require.NoError(t, eng.Process(unittest.IdentifierFixture(), &messageB{n: i})) 327 } 328 329 require.Eventuallyf(t, func() bool { 330 return eng.MessageCount() == count 331 }, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages", 332 count, eng.MessageCount()) 333 }) 334 } 335 336 // if TestEngine receives 100 messages for each type concurrently, the engine will handle them all one after another 337 func TestProcessMessageMultiConcurrent(t *testing.T) { 338 339 WithEngine(t, func(eng *TestEngine) { 340 count := 100 341 var sent sync.WaitGroup 342 for i := 0; i < count; i++ { 343 sent.Add(1) 344 go func(i int) { 345 require.NoError(t, eng.Process(unittest.IdentifierFixture(), &messageA{n: i})) 346 sent.Done() 347 }(i) 348 } 349 sent.Wait() 350 351 require.Eventuallyf(t, func() bool { 352 return eng.MessageCount() == count 353 }, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages", 354 count, eng.MessageCount()) 355 }) 356 } 357 358 // TestUnknownMessageType verifies that the message handler returns an 359 // IncompatibleInputTypeError when receiving a message of unknown type 360 func TestUnknownMessageType(t *testing.T) { 361 WithEngine(t, func(eng *TestEngine) { 362 id := unittest.IdentifierFixture() 363 unknownType := struct{ n int }{n: 10} 364 365 err := eng.Process(id, unknownType) 366 require.Error(t, err) 367 require.True(t, errors.Is(err, engine.IncompatibleInputTypeError)) 368 }) 369 }