github.com/Jeffail/benthos/v3@v3.65.0/lib/pipeline/processor_test.go (about) 1 package pipeline 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/Jeffail/benthos/v3/lib/log" 12 "github.com/Jeffail/benthos/v3/lib/message" 13 "github.com/Jeffail/benthos/v3/lib/metrics" 14 "github.com/Jeffail/benthos/v3/lib/response" 15 "github.com/Jeffail/benthos/v3/lib/types" 16 ) 17 18 var errMockProc = errors.New("this is an error from mock processor") 19 20 type mockMsgProcessor struct { 21 dropChan chan bool 22 hasClosedAsync bool 23 hasWaitedForClose bool 24 mut sync.Mutex 25 } 26 27 func (m *mockMsgProcessor) ProcessMessage(msg types.Message) ([]types.Message, types.Response) { 28 if drop := <-m.dropChan; drop { 29 return nil, response.NewError(errMockProc) 30 } 31 newMsg := message.New([][]byte{ 32 []byte("foo"), 33 []byte("bar"), 34 }) 35 msgs := [1]types.Message{newMsg} 36 return msgs[:], nil 37 } 38 39 // CloseAsync shuts down the processor and stops processing requests. 40 func (m *mockMsgProcessor) CloseAsync() { 41 m.mut.Lock() 42 m.hasClosedAsync = true 43 m.mut.Unlock() 44 } 45 46 // WaitForClose blocks until the processor has closed down. 47 func (m *mockMsgProcessor) WaitForClose(timeout time.Duration) error { 48 m.mut.Lock() 49 m.hasWaitedForClose = true 50 m.mut.Unlock() 51 return nil 52 } 53 54 func TestProcessorPipeline(t *testing.T) { 55 mockProc := &mockMsgProcessor{dropChan: make(chan bool)} 56 57 // Drop first message 58 go func() { 59 mockProc.dropChan <- true 60 }() 61 62 proc := NewProcessor( 63 log.Noop(), 64 metrics.Noop(), 65 mockProc, 66 ) 67 68 tChan, resChan := make(chan types.Transaction), make(chan types.Response) 69 70 if err := proc.Consume(tChan); err != nil { 71 t.Error(err) 72 } 73 if err := proc.Consume(tChan); err == nil { 74 t.Error("Expected error from dupe listening") 75 } 76 77 msg := message.New([][]byte{ 78 []byte(`one`), 79 []byte(`two`), 80 }) 81 82 // First message should be dropped and return immediately 83 select { 84 case tChan <- types.NewTransaction(msg, resChan): 85 case <-time.After(time.Second): 86 t.Error("Timed out") 87 } 88 select { 89 case _, open := <-proc.TransactionChan(): 90 if !open { 91 t.Error("Closed early") 92 } else { 93 t.Error("Message was not dropped") 94 } 95 case res, open := <-resChan: 96 if !open { 97 t.Error("Closed early") 98 } 99 if res.Error() != errMockProc { 100 t.Error(res.Error()) 101 } 102 case <-time.After(time.Second): 103 t.Error("Timed out") 104 } 105 106 // Do not drop next message 107 go func() { 108 mockProc.dropChan <- false 109 }() 110 111 // Send message 112 select { 113 case tChan <- types.NewTransaction(msg, resChan): 114 case <-time.After(time.Second): 115 t.Error("Timed out") 116 } 117 118 var procT types.Transaction 119 var open bool 120 select { 121 case procT, open = <-proc.TransactionChan(): 122 if !open { 123 t.Error("Closed early") 124 } 125 if exp, act := [][]byte{[]byte("foo"), []byte("bar")}, message.GetAllBytes(procT.Payload); !reflect.DeepEqual(exp, act) { 126 t.Errorf("Wrong message received: %s != %s", act, exp) 127 } 128 case res, open := <-resChan: 129 if !open { 130 t.Error("Closed early") 131 } 132 if res.Error() != nil { 133 t.Error(res.Error()) 134 } else { 135 t.Error("Message was dropped") 136 } 137 case <-time.After(time.Second): 138 t.Error("Timed out") 139 } 140 141 // Respond without error 142 go func() { 143 select { 144 case procT.ResponseChan <- response.NewAck(): 145 case _, open := <-resChan: 146 if !open { 147 t.Error("Closed early") 148 } else { 149 t.Error("Premature response prop") 150 } 151 case <-time.After(time.Second): 152 t.Error("Timed out") 153 } 154 }() 155 156 // Receive response 157 select { 158 case res, open := <-resChan: 159 if !open { 160 t.Error("Closed early") 161 } else if res.Error() != nil { 162 t.Error(res.Error()) 163 } 164 case <-time.After(time.Second): 165 t.Error("Timed out") 166 } 167 168 proc.CloseAsync() 169 if err := proc.WaitForClose(time.Second * 5); err != nil { 170 t.Error(err) 171 } 172 if !mockProc.hasClosedAsync { 173 t.Error("Expected mockproc to have closed asynchronously") 174 } 175 if !mockProc.hasWaitedForClose { 176 t.Error("Expected mockproc to have waited for close") 177 } 178 } 179 180 type mockMultiMsgProcessor struct { 181 N int 182 hasClosedAsync bool 183 hasWaitedForClose bool 184 mut sync.Mutex 185 } 186 187 func (m *mockMultiMsgProcessor) ProcessMessage(msg types.Message) ([]types.Message, types.Response) { 188 var msgs []types.Message 189 for i := 0; i < m.N; i++ { 190 newMsg := message.New([][]byte{ 191 []byte(fmt.Sprintf("test%v", i)), 192 }) 193 msgs = append(msgs, newMsg) 194 } 195 return msgs, nil 196 } 197 198 // CloseAsync shuts down the processor and stops processing requests. 199 func (m *mockMultiMsgProcessor) CloseAsync() { 200 m.mut.Lock() 201 m.hasClosedAsync = true 202 m.mut.Unlock() 203 } 204 205 // WaitForClose blocks until the processor has closed down. 206 func (m *mockMultiMsgProcessor) WaitForClose(timeout time.Duration) error { 207 m.mut.Lock() 208 m.hasWaitedForClose = true 209 m.mut.Unlock() 210 return nil 211 } 212 213 func TestProcessorMultiMsgs(t *testing.T) { 214 mockProc := &mockMultiMsgProcessor{N: 3} 215 216 proc := NewProcessor( 217 log.Noop(), 218 metrics.Noop(), 219 mockProc, 220 ) 221 222 tChan, resChan := make(chan types.Transaction), make(chan types.Response) 223 224 if err := proc.Consume(tChan); err != nil { 225 t.Error(err) 226 } 227 228 // Send message 229 select { 230 case tChan <- types.NewTransaction(message.New(nil), resChan): 231 case <-time.After(time.Second): 232 t.Error("Timed out") 233 } 234 235 expMsgs := map[string]struct{}{} 236 for i := 0; i < mockProc.N; i++ { 237 expMsgs[fmt.Sprintf("test%v", i)] = struct{}{} 238 } 239 240 resChans := []chan<- types.Response{} 241 242 // Receive N messages 243 for i := 0; i < mockProc.N; i++ { 244 select { 245 case procT, open := <-proc.TransactionChan(): 246 if !open { 247 t.Error("Closed early") 248 } 249 act := string(procT.Payload.Get(0).Get()) 250 if _, exists := expMsgs[act]; !exists { 251 t.Errorf("Unexpected result: %v", act) 252 } else { 253 delete(expMsgs, act) 254 } 255 resChans = append(resChans, procT.ResponseChan) 256 case <-time.After(time.Second): 257 t.Error("Timed out") 258 } 259 } 260 261 if len(expMsgs) != 0 { 262 t.Errorf("Expected messages were not received: %v", expMsgs) 263 } 264 265 // Respond without error N times 266 for i := 0; i < mockProc.N; i++ { 267 select { 268 case resChans[i] <- response.NewAck(): 269 case <-time.After(time.Second): 270 t.Error("Timed out") 271 } 272 } 273 274 // Receive error 275 select { 276 case res, open := <-resChan: 277 if !open { 278 t.Error("Closed early") 279 } else if res.Error() != nil { 280 t.Error(res.Error()) 281 } 282 case <-time.After(time.Second): 283 t.Error("Timed out") 284 } 285 286 proc.CloseAsync() 287 if err := proc.WaitForClose(time.Second * 5); err != nil { 288 t.Error(err) 289 } 290 if !mockProc.hasClosedAsync { 291 t.Error("Expected mockproc to have closed asynchronously") 292 } 293 if !mockProc.hasWaitedForClose { 294 t.Error("Expected mockproc to have waited for close") 295 } 296 } 297 298 func TestProcessorMultiMsgsOddSync(t *testing.T) { 299 mockProc := &mockMultiMsgProcessor{N: 3} 300 301 proc := NewProcessor( 302 log.Noop(), 303 metrics.Noop(), 304 mockProc, 305 ) 306 307 tChan, resChan := make(chan types.Transaction), make(chan types.Response) 308 309 if err := proc.Consume(tChan); err != nil { 310 t.Error(err) 311 } 312 313 expMsgs := map[string]struct{}{} 314 for i := 0; i < mockProc.N; i++ { 315 expMsgs[fmt.Sprintf("test%v", i)] = struct{}{} 316 } 317 318 // Send message 319 select { 320 case tChan <- types.NewTransaction(message.New(nil), resChan): 321 case <-time.After(time.Second): 322 t.Error("Timed out") 323 } 324 325 var errResChan chan<- types.Response 326 327 // Receive 1 message 328 select { 329 case procT, open := <-proc.TransactionChan(): 330 if !open { 331 t.Error("Closed early") 332 } 333 act := string(procT.Payload.Get(0).Get()) 334 if _, exists := expMsgs[act]; !exists { 335 t.Errorf("Unexpected result: %v", act) 336 } 337 errResChan = procT.ResponseChan 338 case <-time.After(time.Second): 339 t.Error("Timed out") 340 } 341 342 // Respond with 1 error 343 select { 344 case errResChan <- response.NewError(errors.New("foo")): 345 case <-time.After(time.Second): 346 t.Error("Timed out") 347 } 348 349 resChans := []chan<- types.Response{} 350 351 // Receive N messages 352 for i := 0; i < mockProc.N; i++ { 353 select { 354 case procT, open := <-proc.TransactionChan(): 355 if !open { 356 t.Error("Closed early") 357 } 358 act := string(procT.Payload.Get(0).Get()) 359 if _, exists := expMsgs[act]; !exists { 360 t.Errorf("Unexpected result: %v", act) 361 } else { 362 delete(expMsgs, act) 363 } 364 resChans = append(resChans, procT.ResponseChan) 365 case <-time.After(time.Second): 366 t.Error("Timed out") 367 } 368 } 369 370 if len(expMsgs) != 0 { 371 t.Errorf("Expected messages were not received: %v", expMsgs) 372 } 373 374 // Respond without error N times 375 for i := 0; i < mockProc.N; i++ { 376 select { 377 case resChans[i] <- response.NewAck(): 378 case <-time.After(time.Second): 379 t.Error("Timed out") 380 } 381 } 382 383 // Receive error 384 select { 385 case res, open := <-resChan: 386 if !open { 387 t.Error("Closed early") 388 } else if res.Error() != nil { 389 t.Error(res.Error()) 390 } 391 case <-time.After(time.Second): 392 t.Error("Timed out") 393 } 394 395 proc.CloseAsync() 396 if err := proc.WaitForClose(time.Second * 5); err != nil { 397 t.Error(err) 398 } 399 if !mockProc.hasClosedAsync { 400 t.Error("Expected mockproc to have closed asynchronously") 401 } 402 if !mockProc.hasWaitedForClose { 403 t.Error("Expected mockproc to have waited for close") 404 } 405 }