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 }