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  //------------------------------------------------------------------------------