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  }