github.com/Jeffail/benthos/v3@v3.65.0/lib/output/batcher_test.go (about)

     1  package output
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	batchInternal "github.com/Jeffail/benthos/v3/internal/batch"
    12  	"github.com/Jeffail/benthos/v3/lib/log"
    13  	"github.com/Jeffail/benthos/v3/lib/message"
    14  	"github.com/Jeffail/benthos/v3/lib/message/batch"
    15  	"github.com/Jeffail/benthos/v3/lib/metrics"
    16  	"github.com/Jeffail/benthos/v3/lib/response"
    17  	"github.com/Jeffail/benthos/v3/lib/types"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  //------------------------------------------------------------------------------
    23  
    24  func TestBatcherEarlyTermination(t *testing.T) {
    25  	tInChan := make(chan types.Transaction)
    26  	resChan := make(chan types.Response)
    27  
    28  	policyConf := batch.NewPolicyConfig()
    29  	policyConf.Count = 10
    30  	policyConf.Period = "50ms"
    31  	batcher, err := batch.NewPolicy(policyConf, nil, log.Noop(), metrics.Noop())
    32  	require.NoError(t, err)
    33  
    34  	out := &mockOutput{}
    35  
    36  	b := NewBatcher(batcher, out, log.Noop(), metrics.Noop())
    37  	require.NoError(t, b.Consume(tInChan))
    38  
    39  	require.Error(t, b.WaitForClose(time.Millisecond*100))
    40  
    41  	select {
    42  	case tInChan <- types.NewTransaction(message.New([][]byte{[]byte("foo")}), resChan):
    43  	case <-time.After(time.Second):
    44  		t.Error("unexpected")
    45  	}
    46  
    47  	require.Error(t, b.WaitForClose(time.Second))
    48  }
    49  
    50  func TestBatcherBasic(t *testing.T) {
    51  	tInChan := make(chan types.Transaction)
    52  	resChan := make(chan types.Response)
    53  
    54  	policyConf := batch.NewPolicyConfig()
    55  	policyConf.Count = 4
    56  	batcher, err := batch.NewPolicy(policyConf, nil, log.Noop(), metrics.Noop())
    57  	require.NoError(t, err)
    58  
    59  	out := &mockOutput{}
    60  
    61  	b := NewBatcher(batcher, out, log.Noop(), metrics.Noop())
    62  	require.NoError(t, b.Consume(tInChan))
    63  
    64  	tOutChan := out.ts
    65  
    66  	var firstBatchExpected [][]byte
    67  	var secondBatchExpected [][]byte
    68  	var finalBatchExpected [][]byte
    69  	for i := 0; i < 10; i++ {
    70  		inputBytes := []byte(fmt.Sprintf("foo %v", i))
    71  		if i < 4 {
    72  			firstBatchExpected = append(firstBatchExpected, inputBytes)
    73  		} else if i < 8 {
    74  			secondBatchExpected = append(secondBatchExpected, inputBytes)
    75  		} else {
    76  			finalBatchExpected = append(finalBatchExpected, inputBytes)
    77  		}
    78  	}
    79  
    80  	firstErr := errors.New("first error")
    81  	secondErr := errors.New("second error")
    82  	finalErr := errors.New("final error")
    83  
    84  	wg := sync.WaitGroup{}
    85  	wg.Add(1)
    86  	go func() {
    87  		defer wg.Done()
    88  		for _, batch := range firstBatchExpected {
    89  			select {
    90  			case tInChan <- types.NewTransaction(message.New([][]byte{batch}), resChan):
    91  			case <-time.After(time.Second):
    92  				t.Error("timed out")
    93  			}
    94  		}
    95  		for range firstBatchExpected {
    96  			select {
    97  			case actRes := <-resChan:
    98  				assert.Equal(t, firstErr, actRes.Error())
    99  			case <-time.After(time.Second):
   100  				t.Error("timed out")
   101  			}
   102  		}
   103  		for _, batch := range secondBatchExpected {
   104  			select {
   105  			case tInChan <- types.NewTransaction(message.New([][]byte{batch}), resChan):
   106  			case <-time.After(time.Second):
   107  				t.Error("timed out")
   108  			}
   109  		}
   110  		for range secondBatchExpected {
   111  			select {
   112  			case actRes := <-resChan:
   113  				assert.Equal(t, secondErr, actRes.Error())
   114  			case <-time.After(time.Second):
   115  				t.Error("timed out")
   116  			}
   117  		}
   118  		for _, batch := range finalBatchExpected {
   119  			select {
   120  			case tInChan <- types.NewTransaction(message.New([][]byte{batch}), resChan):
   121  			case <-time.After(time.Second):
   122  				t.Error("timed out")
   123  			}
   124  		}
   125  		close(tInChan)
   126  		for range finalBatchExpected {
   127  			select {
   128  			case actRes := <-resChan:
   129  				assert.Equal(t, finalErr, actRes.Error())
   130  			case <-time.After(time.Second):
   131  				t.Error("timed out")
   132  			}
   133  		}
   134  	}()
   135  
   136  	sendResponse := func(rChan chan<- types.Response, err error) {
   137  		defer wg.Done()
   138  		select {
   139  		case rChan <- response.NewError(err):
   140  		case <-time.After(time.Second):
   141  			t.Error("timed out")
   142  		}
   143  	}
   144  
   145  	// Receive first batch on output
   146  	var outTr types.Transaction
   147  	select {
   148  	case outTr = <-tOutChan:
   149  	case <-time.After(time.Second):
   150  		t.Fatal("Timed out waiting for message read")
   151  	}
   152  	if exp, act := firstBatchExpected, message.GetAllBytes(outTr.Payload); !reflect.DeepEqual(exp, act) {
   153  		t.Errorf("Wrong result from batch: %s != %s", act, exp)
   154  	}
   155  	wg.Add(1)
   156  	go sendResponse(outTr.ResponseChan, firstErr)
   157  
   158  	// Receive second batch on output
   159  	select {
   160  	case outTr = <-tOutChan:
   161  	case <-time.After(time.Second):
   162  		t.Fatal("Timed out waiting for message read")
   163  	}
   164  	if exp, act := secondBatchExpected, message.GetAllBytes(outTr.Payload); !reflect.DeepEqual(exp, act) {
   165  		t.Errorf("Wrong result from batch: %s != %s", act, exp)
   166  	}
   167  	wg.Add(1)
   168  	go sendResponse(outTr.ResponseChan, secondErr)
   169  
   170  	// Receive final batch on output
   171  	select {
   172  	case outTr = <-tOutChan:
   173  	case <-time.After(time.Second):
   174  		t.Fatal("Timed out waiting for message read")
   175  	}
   176  	if exp, act := finalBatchExpected, message.GetAllBytes(outTr.Payload); !reflect.DeepEqual(exp, act) {
   177  		t.Errorf("Wrong result from batch: %s != %s", act, exp)
   178  	}
   179  	wg.Add(1)
   180  	go sendResponse(outTr.ResponseChan, finalErr)
   181  
   182  	require.NoError(t, b.WaitForClose(time.Second*10))
   183  	wg.Wait()
   184  }
   185  
   186  func TestBatcherBatchError(t *testing.T) {
   187  	tInChan := make(chan types.Transaction)
   188  	resChan := make(chan types.Response)
   189  
   190  	policyConf := batch.NewPolicyConfig()
   191  	policyConf.Count = 4
   192  	batcher, err := batch.NewPolicy(policyConf, nil, log.Noop(), metrics.Noop())
   193  	require.NoError(t, err)
   194  
   195  	out := &mockOutput{}
   196  
   197  	b := NewBatcher(batcher, out, log.Noop(), metrics.Noop())
   198  	require.NoError(t, b.Consume(tInChan))
   199  
   200  	tOutChan := out.ts
   201  
   202  	wg := sync.WaitGroup{}
   203  	wg.Add(1)
   204  
   205  	go func() {
   206  		defer wg.Done()
   207  		firstErr := errors.New("first error")
   208  		thirdErr := errors.New("third error")
   209  
   210  		// Receive first batch on output
   211  		var outTr types.Transaction
   212  		select {
   213  		case outTr = <-tOutChan:
   214  		case <-time.After(time.Second):
   215  			t.Error("Timed out waiting for message read")
   216  		}
   217  		assert.Equal(t, [][]byte{
   218  			[]byte("foo0"),
   219  			[]byte("foo1"),
   220  			[]byte("foo2"),
   221  			[]byte("foo3"),
   222  		}, message.GetAllBytes(outTr.Payload))
   223  
   224  		batchErr := batchInternal.NewError(outTr.Payload, errors.New("foo")).
   225  			Failed(0, firstErr).Failed(2, thirdErr)
   226  
   227  		select {
   228  		case outTr.ResponseChan <- response.NewError(batchErr):
   229  		case <-time.After(time.Second):
   230  			t.Error("timed out")
   231  		}
   232  	}()
   233  
   234  	for i := 0; i < 4; i++ {
   235  		data := []byte(fmt.Sprintf("foo%v", i))
   236  		select {
   237  		case tInChan <- types.NewTransaction(message.New([][]byte{data}), resChan):
   238  		case <-time.After(time.Second):
   239  			t.Fatal("timed out")
   240  		}
   241  	}
   242  	for i := 0; i < 4; i++ {
   243  		var act error
   244  		select {
   245  		case actRes := <-resChan:
   246  			act = actRes.Error()
   247  		case <-time.After(time.Second):
   248  			t.Fatal("timed out")
   249  		}
   250  		switch i {
   251  		case 0:
   252  			assert.EqualError(t, act, "first error")
   253  		case 2:
   254  			assert.EqualError(t, act, "third error")
   255  		default:
   256  			assert.Nil(t, act)
   257  		}
   258  	}
   259  
   260  	close(tInChan)
   261  	b.CloseAsync()
   262  
   263  	if err = b.WaitForClose(time.Second * 5); err != nil {
   264  		t.Error(err)
   265  	}
   266  	wg.Wait()
   267  }
   268  
   269  func TestBatcherTimed(t *testing.T) {
   270  	tInChan := make(chan types.Transaction)
   271  	resChan := make(chan types.Response)
   272  
   273  	policyConf := batch.NewPolicyConfig()
   274  	policyConf.Period = "100ms"
   275  	batcher, err := batch.NewPolicy(policyConf, nil, log.Noop(), metrics.Noop())
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  
   280  	out := &mockOutput{}
   281  
   282  	b := NewBatcher(batcher, out, log.Noop(), metrics.Noop())
   283  	if err := b.Consume(tInChan); err != nil {
   284  		t.Fatal(err)
   285  	}
   286  
   287  	tOutChan := out.ts
   288  
   289  	batchExpected := [][]byte{
   290  		[]byte("foo1"),
   291  		[]byte("foo2"),
   292  		[]byte("foo3"),
   293  	}
   294  
   295  	select {
   296  	case tInChan <- types.NewTransaction(message.New(batchExpected), resChan):
   297  	case <-time.After(time.Second):
   298  		t.Fatal("Timed out waiting for message send")
   299  	}
   300  
   301  	// Receive first batch on output
   302  	var outTr types.Transaction
   303  	select {
   304  	case outTr = <-tOutChan:
   305  	case <-time.After(time.Second):
   306  		t.Fatal("Timed out waiting for message read")
   307  	}
   308  	if exp, act := batchExpected, message.GetAllBytes(outTr.Payload); !reflect.DeepEqual(exp, act) {
   309  		t.Errorf("Wrong result from batch: %s != %s", act, exp)
   310  	}
   311  
   312  	close(tInChan)
   313  	b.CloseAsync()
   314  	if err = b.WaitForClose(time.Second); err != nil {
   315  		t.Error(err)
   316  	}
   317  
   318  	close(resChan)
   319  }
   320  
   321  //------------------------------------------------------------------------------