github.com/Jeffail/benthos/v3@v3.65.0/lib/input/wrap_with_pipeline_test.go (about) 1 package input 2 3 import ( 4 "errors" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/Jeffail/benthos/v3/lib/log" 10 "github.com/Jeffail/benthos/v3/lib/message" 11 "github.com/Jeffail/benthos/v3/lib/metrics" 12 "github.com/Jeffail/benthos/v3/lib/pipeline" 13 "github.com/Jeffail/benthos/v3/lib/response" 14 "github.com/Jeffail/benthos/v3/lib/types" 15 ) 16 17 //------------------------------------------------------------------------------ 18 19 type mockInput struct { 20 closeOnce sync.Once 21 ts chan types.Transaction 22 } 23 24 func (m *mockInput) TransactionChan() <-chan types.Transaction { 25 return m.ts 26 } 27 28 func (m *mockInput) Connected() bool { 29 return true 30 } 31 32 func (m *mockInput) CloseAsync() { 33 m.closeOnce.Do(func() { 34 close(m.ts) 35 }) 36 } 37 38 func (m *mockInput) WaitForClose(time.Duration) error { 39 return errors.New("wasnt expecting to ever see this tbh") 40 } 41 42 //------------------------------------------------------------------------------ 43 44 type mockPipe struct { 45 tsIn <-chan types.Transaction 46 ts chan types.Transaction 47 } 48 49 func (m *mockPipe) Consume(ts <-chan types.Transaction) error { 50 m.tsIn = ts 51 return nil 52 } 53 54 func (m *mockPipe) TransactionChan() <-chan types.Transaction { 55 return m.ts 56 } 57 58 func (m *mockPipe) CloseAsync() { 59 close(m.ts) 60 } 61 62 func (m *mockPipe) WaitForClose(time.Duration) error { 63 return nil 64 } 65 66 //------------------------------------------------------------------------------ 67 68 func TestBasicWrapPipeline(t *testing.T) { 69 mockIn := &mockInput{ts: make(chan types.Transaction)} 70 mockPi := &mockPipe{ 71 ts: make(chan types.Transaction), 72 } 73 74 procs := 0 75 _, err := WrapWithPipeline(&procs, mockIn, func(i *int) (types.Pipeline, error) { 76 return nil, errors.New("nope") 77 }) 78 79 if err == nil { 80 t.Error("Expected error from back constructor") 81 } 82 83 newInput, err := WrapWithPipeline(&procs, mockIn, func(i *int) (types.Pipeline, error) { 84 return mockPi, nil 85 }) 86 if err != nil { 87 t.Fatal(err) 88 } 89 90 if newInput.TransactionChan() != mockPi.ts { 91 t.Error("Wrong transaction chan in new input type") 92 } 93 94 if mockIn.ts != mockPi.tsIn { 95 t.Error("Wrong transactions chan in mock pipe") 96 } 97 98 newInput.CloseAsync() 99 if err = newInput.WaitForClose(time.Second); err != nil { 100 t.Error(err) 101 } 102 103 select { 104 case _, open := <-mockIn.ts: 105 if open { 106 t.Error("mock input is still open after close") 107 } 108 case _, open := <-mockPi.ts: 109 if open { 110 t.Error("mock pipe is still open after close") 111 } 112 default: 113 t.Error("neither type was closed") 114 } 115 } 116 117 func TestWrapZeroPipelines(t *testing.T) { 118 mockIn := &mockInput{ts: make(chan types.Transaction)} 119 newInput, err := WrapWithPipelines(mockIn) 120 if err != nil { 121 t.Error(err) 122 } 123 124 if newInput != mockIn { 125 t.Errorf("Wrong input obj returned: %v != %v", newInput, mockIn) 126 } 127 } 128 129 func TestBasicWrapMultiPipelines(t *testing.T) { 130 mockIn := &mockInput{ts: make(chan types.Transaction)} 131 mockPi1 := &mockPipe{ 132 ts: make(chan types.Transaction), 133 } 134 mockPi2 := &mockPipe{ 135 ts: make(chan types.Transaction), 136 } 137 138 _, err := WrapWithPipelines(mockIn, func(i *int) (types.Pipeline, error) { 139 return nil, errors.New("nope") 140 }) 141 if err == nil { 142 t.Error("Expected error from back constructor") 143 } 144 145 newInput, err := WrapWithPipelines(mockIn, func(i *int) (types.Pipeline, error) { 146 return mockPi1, nil 147 }, func(i *int) (types.Pipeline, error) { 148 return mockPi2, nil 149 }) 150 if err != nil { 151 t.Fatal(err) 152 } 153 154 if newInput.TransactionChan() != mockPi2.ts { 155 t.Error("Wrong message chan in new input type") 156 } 157 if mockPi2.tsIn != mockPi1.ts { 158 t.Error("Wrong message chan in mock pipe 2") 159 } 160 161 if mockIn.ts != mockPi1.tsIn { 162 t.Error("Wrong messages chan in mock pipe 1") 163 } 164 if mockPi1.ts != mockPi2.tsIn { 165 t.Error("Wrong messages chan in mock pipe 2") 166 } 167 168 newInput.CloseAsync() 169 if err = newInput.WaitForClose(time.Second); err != nil { 170 t.Error(err) 171 } 172 173 select { 174 case _, open := <-mockIn.ts: 175 if open { 176 t.Error("mock input is still open after close") 177 } 178 case _, open := <-mockPi1.ts: 179 if open { 180 t.Error("mock pipe is still open after close") 181 } 182 case _, open := <-mockPi2.ts: 183 if open { 184 t.Error("mock pipe is still open after close") 185 } 186 default: 187 t.Error("neither type was closed") 188 } 189 } 190 191 //------------------------------------------------------------------------------ 192 193 type mockProc struct { 194 value string 195 } 196 197 func (m mockProc) ProcessMessage(msg types.Message) ([]types.Message, types.Response) { 198 if string(msg.Get(0).Get()) == m.value { 199 return nil, response.NewUnack() 200 } 201 msgs := [1]types.Message{msg} 202 return msgs[:], nil 203 } 204 205 // CloseAsync shuts down the processor and stops processing requests. 206 func (m mockProc) CloseAsync() { 207 // Do nothing as our processor doesn't require resource cleanup. 208 } 209 210 // WaitForClose blocks until the processor has closed down. 211 func (m mockProc) WaitForClose(timeout time.Duration) error { 212 // Do nothing as our processor doesn't require resource cleanup. 213 return nil 214 } 215 216 //------------------------------------------------------------------------------ 217 218 func TestBasicWrapProcessors(t *testing.T) { 219 mockIn := &mockInput{ts: make(chan types.Transaction)} 220 221 l := log.Noop() 222 s := metrics.Noop() 223 224 pipe1 := pipeline.NewProcessor(l, s, mockProc{value: "foo"}) 225 pipe2 := pipeline.NewProcessor(l, s, mockProc{value: "bar"}) 226 227 newInput, err := WrapWithPipelines(mockIn, func(i *int) (types.Pipeline, error) { 228 return pipe1, nil 229 }, func(i *int) (types.Pipeline, error) { 230 return pipe2, nil 231 }) 232 if err != nil { 233 t.Error(err) 234 } 235 236 resChan := make(chan types.Response) 237 238 msg := message.New([][]byte{[]byte("foo")}) 239 240 select { 241 case mockIn.ts <- types.NewTransaction(msg, resChan): 242 case <-time.After(time.Second): 243 t.Error("action timed out") 244 } 245 246 // Message should be discarded 247 select { 248 case res, open := <-resChan: 249 if !open { 250 t.Error("Channel was closed") 251 } 252 if !res.SkipAck() { 253 t.Error("expected skip ack") 254 } 255 case <-time.After(time.Second): 256 t.Error("action timed out") 257 } 258 259 msg = message.New([][]byte{[]byte("bar")}) 260 261 select { 262 case mockIn.ts <- types.NewTransaction(msg, resChan): 263 case <-time.After(time.Second): 264 t.Error("action timed out") 265 } 266 267 // Message should also be discarded 268 select { 269 case res, open := <-resChan: 270 if !open { 271 t.Error("Channel was closed") 272 } 273 if !res.SkipAck() { 274 t.Error("expected skip ack") 275 } 276 case <-time.After(time.Second): 277 t.Error("action timed out") 278 } 279 280 msg = message.New([][]byte{[]byte("baz")}) 281 282 select { 283 case mockIn.ts <- types.NewTransaction(msg, resChan): 284 case <-time.After(time.Second): 285 t.Error("action timed out") 286 } 287 288 // Message should not be discarded 289 var ts types.Transaction 290 var open bool 291 select { 292 case res, open := <-resChan: 293 if !open { 294 t.Error("Channel was closed") 295 } 296 t.Errorf("Unexpected response: %v", res.Error()) 297 case ts, open = <-newInput.TransactionChan(): 298 if !open { 299 t.Error("channel was closed") 300 } else if exp, act := "baz", string(ts.Payload.Get(0).Get()); exp != act { 301 t.Errorf("Wrong message received: %v != %v", act, exp) 302 } 303 case <-time.After(time.Second): 304 t.Error("action timed out") 305 } 306 307 errFailed := errors.New("derp, failed") 308 309 // Send error 310 go func() { 311 select { 312 case ts.ResponseChan <- response.NewError(errFailed): 313 case <-time.After(time.Second): 314 t.Error("action timed out") 315 } 316 }() 317 318 // Receive again 319 select { 320 case res, open := <-resChan: 321 if !open { 322 t.Error("Channel was closed") 323 } 324 if res.Error() != errFailed { 325 t.Error(res.Error()) 326 } 327 case <-time.After(time.Second): 328 t.Error("action timed out") 329 } 330 331 newInput.CloseAsync() 332 if err = newInput.WaitForClose(time.Second); err != nil { 333 t.Error(err) 334 } 335 } 336 337 func TestBasicWrapDoubleProcessors(t *testing.T) { 338 mockIn := &mockInput{ts: make(chan types.Transaction)} 339 340 l := log.Noop() 341 s := metrics.Noop() 342 343 pipe1 := pipeline.NewProcessor(l, s, mockProc{value: "foo"}, mockProc{value: "bar"}) 344 345 newInput, err := WrapWithPipelines(mockIn, func(i *int) (types.Pipeline, error) { 346 return pipe1, nil 347 }) 348 if err != nil { 349 t.Error(err) 350 } 351 352 resChan := make(chan types.Response) 353 354 msg := message.New([][]byte{[]byte("foo")}) 355 356 select { 357 case mockIn.ts <- types.NewTransaction(msg, resChan): 358 case <-time.After(time.Second): 359 t.Error("action timed out") 360 } 361 362 // Message should be discarded 363 select { 364 case res, open := <-resChan: 365 if !open { 366 t.Error("Channel was closed") 367 } 368 if !res.SkipAck() { 369 t.Error("expected skip ack") 370 } 371 case <-time.After(time.Second): 372 t.Error("action timed out") 373 } 374 375 msg = message.New([][]byte{[]byte("bar")}) 376 377 select { 378 case mockIn.ts <- types.NewTransaction(msg, resChan): 379 case <-time.After(time.Second): 380 t.Error("action timed out") 381 } 382 383 // Message should also be discarded 384 select { 385 case res, open := <-resChan: 386 if !open { 387 t.Error("Channel was closed") 388 } 389 if !res.SkipAck() { 390 t.Error("expected skip ack") 391 } 392 case <-time.After(time.Second): 393 t.Error("action timed out") 394 } 395 396 msg = message.New([][]byte{[]byte("baz")}) 397 398 select { 399 case mockIn.ts <- types.NewTransaction(msg, resChan): 400 case <-time.After(time.Second): 401 t.Error("action timed out") 402 } 403 404 // Message should not be discarded 405 var ts types.Transaction 406 var open bool 407 select { 408 case res, open := <-resChan: 409 if !open { 410 t.Error("Channel was closed") 411 } 412 t.Errorf("Unexpected response: %v", res.Error()) 413 case ts, open = <-newInput.TransactionChan(): 414 if !open { 415 t.Error("channel was closed") 416 } else if exp, act := "baz", string(ts.Payload.Get(0).Get()); exp != act { 417 t.Errorf("Wrong message received: %v != %v", act, exp) 418 } 419 case <-time.After(time.Second): 420 t.Error("action timed out") 421 } 422 423 errFailed := errors.New("derp, failed") 424 425 // Send error 426 go func() { 427 select { 428 case ts.ResponseChan <- response.NewError(errFailed): 429 case <-time.After(time.Second): 430 t.Error("action timed out") 431 } 432 }() 433 434 // Receive again 435 select { 436 case res, open := <-resChan: 437 if !open { 438 t.Error("Channel was closed") 439 } 440 if res.Error() != errFailed { 441 t.Error(res.Error()) 442 } 443 case <-time.After(time.Second): 444 t.Error("action timed out") 445 } 446 447 newInput.CloseAsync() 448 if err = newInput.WaitForClose(time.Second); err != nil { 449 t.Error(err) 450 } 451 } 452 453 //------------------------------------------------------------------------------