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

     1  package output
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sort"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/Jeffail/benthos/v3/internal/batch"
    13  	"github.com/Jeffail/benthos/v3/lib/log"
    14  	"github.com/Jeffail/benthos/v3/lib/message"
    15  	"github.com/Jeffail/benthos/v3/lib/metrics"
    16  	"github.com/Jeffail/benthos/v3/lib/types"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  type mockNBWriter struct {
    22  	t           *testing.T
    23  	written     []string
    24  	errorOn     []string
    25  	closeCalled bool
    26  	closeChan   chan error
    27  	mut         sync.Mutex
    28  }
    29  
    30  func (m *mockNBWriter) ConnectWithContext(context.Context) error {
    31  	return nil
    32  }
    33  
    34  func (m *mockNBWriter) WriteWithContext(ctx context.Context, msg types.Message) error {
    35  	m.mut.Lock()
    36  	defer m.mut.Unlock()
    37  
    38  	m.t.Helper()
    39  	assert.Equal(m.t, 1, msg.Len())
    40  	return msg.Iter(func(i int, p types.Part) error {
    41  		for _, eOn := range m.errorOn {
    42  			if eOn == string(p.Get()) {
    43  				return errors.New("test err")
    44  			}
    45  		}
    46  		m.written = append(m.written, string(p.Get()))
    47  		return nil
    48  	})
    49  }
    50  
    51  func (m *mockNBWriter) CloseAsync() {
    52  	m.mut.Lock()
    53  	m.closeCalled = true
    54  	m.mut.Unlock()
    55  }
    56  
    57  func (m *mockNBWriter) WaitForClose(time.Duration) error {
    58  	if m.closeChan == nil {
    59  		return nil
    60  	}
    61  	return <-m.closeChan
    62  }
    63  
    64  func TestNotBatchedSingleMessages(t *testing.T) {
    65  	msg := func(c string) types.Message {
    66  		p := message.NewPart([]byte(c))
    67  		msg := message.New(nil)
    68  		msg.Append(p)
    69  		return msg
    70  	}
    71  
    72  	w := &mockNBWriter{t: t}
    73  	out, err := NewAsyncWriter("foo", 1, w, log.Noop(), metrics.Noop())
    74  	require.NoError(t, err)
    75  
    76  	nbOut := OnlySinglePayloads(out)
    77  
    78  	resChan := make(chan types.Response)
    79  	tChan := make(chan types.Transaction)
    80  	require.NoError(t, nbOut.Consume(tChan))
    81  
    82  	for i := 0; i < 5; i++ {
    83  		select {
    84  		case tChan <- types.NewTransaction(msg(fmt.Sprintf("foo%v", i)), resChan):
    85  		case <-time.After(time.Second):
    86  			t.Fatal("timed out")
    87  		}
    88  		select {
    89  		case res := <-resChan:
    90  			assert.NoError(t, res.Error())
    91  		case <-time.After(time.Second):
    92  			t.Fatal("timed out")
    93  		}
    94  	}
    95  
    96  	nbOut.CloseAsync()
    97  	assert.NoError(t, nbOut.WaitForClose(time.Second))
    98  	assert.Equal(t, []string{
    99  		"foo0", "foo1", "foo2", "foo3", "foo4",
   100  	}, w.written)
   101  }
   102  
   103  func TestShutdown(t *testing.T) {
   104  	msg := func(c string) types.Message {
   105  		p := message.NewPart([]byte(c))
   106  		msg := message.New(nil)
   107  		msg.Append(p)
   108  		return msg
   109  	}
   110  
   111  	w := &mockNBWriter{t: t, closeChan: make(chan error)}
   112  	out, err := NewAsyncWriter("foo", 1, w, log.Noop(), metrics.Noop())
   113  	require.NoError(t, err)
   114  
   115  	nbOut := OnlySinglePayloads(out)
   116  
   117  	resChan := make(chan types.Response)
   118  	tChan := make(chan types.Transaction)
   119  	require.NoError(t, nbOut.Consume(tChan))
   120  
   121  	select {
   122  	case tChan <- types.NewTransaction(msg("foo"), resChan):
   123  	case <-time.After(time.Second):
   124  		t.Fatal("timed out")
   125  	}
   126  	select {
   127  	case res := <-resChan:
   128  		assert.NoError(t, res.Error())
   129  	case <-time.After(time.Second):
   130  		t.Fatal("timed out")
   131  	}
   132  
   133  	nbOut.CloseAsync()
   134  	assert.EqualError(t, nbOut.WaitForClose(time.Millisecond*100), "action timed out")
   135  
   136  	select {
   137  	case w.closeChan <- errors.New("custom err"):
   138  	case <-time.After(time.Second):
   139  		t.Error("timed out")
   140  	}
   141  
   142  	assert.NoError(t, nbOut.WaitForClose(time.Millisecond*100))
   143  	assert.Equal(t, []string{"foo"}, w.written)
   144  	w.mut.Lock()
   145  	assert.True(t, w.closeCalled)
   146  	w.mut.Unlock()
   147  }
   148  
   149  func TestNotBatchedBreakOutMessages(t *testing.T) {
   150  	msg := func(c ...string) types.Message {
   151  		msg := message.New(nil)
   152  		for _, str := range c {
   153  			p := message.NewPart([]byte(str))
   154  			msg.Append(p)
   155  		}
   156  		return msg
   157  	}
   158  
   159  	w := &mockNBWriter{t: t}
   160  	out, err := NewAsyncWriter("foo", 1, w, log.Noop(), metrics.Noop())
   161  	require.NoError(t, err)
   162  
   163  	nbOut := OnlySinglePayloads(out)
   164  
   165  	resChan := make(chan types.Response)
   166  	tChan := make(chan types.Transaction)
   167  	require.NoError(t, nbOut.Consume(tChan))
   168  
   169  	select {
   170  	case tChan <- types.NewTransaction(msg(
   171  		"foo0", "foo1", "foo2", "foo3", "foo4",
   172  	), resChan):
   173  	case <-time.After(time.Second):
   174  		t.Fatal("timed out")
   175  	}
   176  	select {
   177  	case res := <-resChan:
   178  		assert.NoError(t, res.Error())
   179  	case <-time.After(time.Second):
   180  		t.Fatal("timed out")
   181  	}
   182  
   183  	nbOut.CloseAsync()
   184  	assert.NoError(t, nbOut.WaitForClose(time.Second))
   185  	assert.Equal(t, []string{
   186  		"foo0", "foo1", "foo2", "foo3", "foo4",
   187  	}, w.written)
   188  }
   189  
   190  func TestNotBatchedBreakOutMessagesErrors(t *testing.T) {
   191  	msg := func(c ...string) types.Message {
   192  		msg := message.New(nil)
   193  		for _, str := range c {
   194  			p := message.NewPart([]byte(str))
   195  			msg.Append(p)
   196  		}
   197  		return msg
   198  	}
   199  
   200  	w := &mockNBWriter{t: t, errorOn: []string{"foo1", "foo3"}}
   201  	out, err := NewAsyncWriter("foo", 1, w, log.Noop(), metrics.Noop())
   202  	require.NoError(t, err)
   203  
   204  	nbOut := OnlySinglePayloads(out)
   205  
   206  	resChan := make(chan types.Response)
   207  	tChan := make(chan types.Transaction)
   208  	require.NoError(t, nbOut.Consume(tChan))
   209  
   210  	select {
   211  	case tChan <- types.NewTransaction(msg(
   212  		"foo0", "foo1", "foo2", "foo3", "foo4",
   213  	), resChan):
   214  	case <-time.After(time.Second):
   215  		t.Fatal("timed out")
   216  	}
   217  	select {
   218  	case res := <-resChan:
   219  		err := res.Error()
   220  		require.Error(t, err)
   221  
   222  		walkable, ok := err.(batch.WalkableError)
   223  		require.True(t, ok)
   224  
   225  		errs := map[int]string{}
   226  		walkable.WalkParts(func(i int, _ types.Part, err error) bool {
   227  			if err != nil {
   228  				errs[i] = err.Error()
   229  			}
   230  			return true
   231  		})
   232  		assert.Equal(t, map[int]string{
   233  			1: "test err",
   234  			3: "test err",
   235  		}, errs)
   236  	case <-time.After(time.Second):
   237  		t.Fatal("timed out")
   238  	}
   239  
   240  	nbOut.CloseAsync()
   241  	assert.NoError(t, nbOut.WaitForClose(time.Second))
   242  	assert.Equal(t, []string{
   243  		"foo0", "foo2", "foo4",
   244  	}, w.written)
   245  }
   246  
   247  func TestNotBatchedBreakOutMessagesErrorsAsync(t *testing.T) {
   248  	msg := func(c ...string) types.Message {
   249  		msg := message.New(nil)
   250  		for _, str := range c {
   251  			p := message.NewPart([]byte(str))
   252  			msg.Append(p)
   253  		}
   254  		return msg
   255  	}
   256  
   257  	w := &mockNBWriter{t: t, errorOn: []string{"foo1", "foo3"}}
   258  	out, err := NewAsyncWriter("foo", 5, w, log.Noop(), metrics.Noop())
   259  	require.NoError(t, err)
   260  
   261  	nbOut := OnlySinglePayloads(out)
   262  
   263  	resChan := make(chan types.Response)
   264  	tChan := make(chan types.Transaction)
   265  	require.NoError(t, nbOut.Consume(tChan))
   266  
   267  	select {
   268  	case tChan <- types.NewTransaction(msg(
   269  		"foo0", "foo1", "foo2", "foo3", "foo4",
   270  	), resChan):
   271  	case <-time.After(time.Second):
   272  		t.Fatal("timed out")
   273  	}
   274  	select {
   275  	case res := <-resChan:
   276  		err := res.Error()
   277  		require.Error(t, err)
   278  
   279  		walkable, ok := err.(batch.WalkableError)
   280  		require.True(t, ok)
   281  
   282  		errs := map[int]string{}
   283  		walkable.WalkParts(func(i int, _ types.Part, err error) bool {
   284  			if err != nil {
   285  				errs[i] = err.Error()
   286  			}
   287  			return true
   288  		})
   289  		assert.Equal(t, map[int]string{
   290  			1: "test err",
   291  			3: "test err",
   292  		}, errs)
   293  	case <-time.After(time.Second):
   294  		t.Fatal("timed out")
   295  	}
   296  
   297  	nbOut.CloseAsync()
   298  	assert.NoError(t, nbOut.WaitForClose(time.Second))
   299  	sort.Strings(w.written)
   300  	assert.Equal(t, []string{
   301  		"foo0", "foo2", "foo4",
   302  	}, w.written)
   303  }