github.com/Jeffail/benthos/v3@v3.65.0/lib/input/batcher_test.go (about) 1 package input 2 3 import ( 4 "errors" 5 "fmt" 6 "testing" 7 "time" 8 9 ibatch "github.com/Jeffail/benthos/v3/internal/batch" 10 "github.com/Jeffail/benthos/v3/lib/log" 11 "github.com/Jeffail/benthos/v3/lib/message" 12 "github.com/Jeffail/benthos/v3/lib/message/batch" 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 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func TestBatcherStandard(t *testing.T) { 21 mock := &mockInput{ 22 ts: make(chan types.Transaction), 23 } 24 25 batchConf := batch.NewPolicyConfig() 26 batchConf.Count = 3 27 28 batchPol, err := batch.NewPolicy(batchConf, types.NoopMgr(), log.Noop(), metrics.Noop()) 29 if err != nil { 30 t.Fatal(err) 31 } 32 33 batcher := NewBatcher(batchPol, mock, log.Noop(), metrics.Noop()) 34 35 testMsgs := []string{} 36 testResChans := []chan types.Response{} 37 for i := 0; i < 8; i++ { 38 testMsgs = append(testMsgs, fmt.Sprintf("test%v", i)) 39 testResChans = append(testResChans, make(chan types.Response)) 40 } 41 42 resErrs := []error{} 43 doneWritesChan := make(chan struct{}) 44 doneReadsChan := make(chan struct{}) 45 go func() { 46 for i, m := range testMsgs { 47 mock.ts <- types.NewTransaction(message.New([][]byte{[]byte(m)}), testResChans[i]) 48 } 49 close(doneWritesChan) 50 for _, rChan := range testResChans { 51 resErrs = append(resErrs, (<-rChan).Error()) 52 } 53 close(doneReadsChan) 54 }() 55 56 resChans := []chan<- types.Response{} 57 58 var tran types.Transaction 59 select { 60 case tran = <-batcher.TransactionChan(): 61 case <-time.After(time.Second): 62 t.Fatal("timed out") 63 } 64 resChans = append(resChans, tran.ResponseChan) 65 66 if exp, act := 3, tran.Payload.Len(); exp != act { 67 t.Errorf("Wrong batch size: %v != %v", act, exp) 68 } 69 tran.Payload.Iter(func(i int, part types.Part) error { 70 if exp, act := fmt.Sprintf("test%v", i), string(part.Get()); exp != act { 71 t.Errorf("Unexpected message part: %v != %v", act, exp) 72 } 73 return nil 74 }) 75 76 select { 77 case tran = <-batcher.TransactionChan(): 78 case <-time.After(time.Second): 79 t.Fatal("timed out") 80 } 81 resChans = append(resChans, tran.ResponseChan) 82 83 if exp, act := 3, tran.Payload.Len(); exp != act { 84 t.Errorf("Wrong batch size: %v != %v", act, exp) 85 } 86 tran.Payload.Iter(func(i int, part types.Part) error { 87 if exp, act := fmt.Sprintf("test%v", i+3), string(part.Get()); exp != act { 88 t.Errorf("Unexpected message part: %v != %v", act, exp) 89 } 90 return nil 91 }) 92 93 select { 94 case <-batcher.TransactionChan(): 95 t.Error("Unexpected batch received") 96 default: 97 } 98 99 select { 100 case <-doneWritesChan: 101 case <-time.After(time.Second): 102 t.Error("timed out") 103 } 104 batcher.CloseAsync() 105 106 select { 107 case tran = <-batcher.TransactionChan(): 108 case <-time.After(time.Second): 109 t.Fatal("timed out") 110 } 111 resChans = append(resChans, tran.ResponseChan) 112 113 if exp, act := 2, tran.Payload.Len(); exp != act { 114 t.Errorf("Wrong batch size: %v != %v", act, exp) 115 } 116 tran.Payload.Iter(func(i int, part types.Part) error { 117 if exp, act := fmt.Sprintf("test%v", i+6), string(part.Get()); exp != act { 118 t.Errorf("Unexpected message part: %v != %v", act, exp) 119 } 120 return nil 121 }) 122 123 for i, rChan := range resChans { 124 select { 125 case rChan <- response.NewError(fmt.Errorf("testerr%v", i)): 126 case <-time.After(time.Second): 127 t.Fatal("timed out") 128 } 129 } 130 131 select { 132 case <-doneReadsChan: 133 case <-time.After(time.Second): 134 t.Error("timed out") 135 } 136 137 for i, err := range resErrs { 138 exp := "testerr0" 139 if i >= 3 { 140 exp = "testerr1" 141 } 142 if i >= 6 { 143 exp = "testerr2" 144 } 145 if act := err.Error(); exp != act { 146 t.Errorf("Unexpected error returned: %v != %v", act, exp) 147 } 148 } 149 150 if err := batcher.WaitForClose(time.Second); err != nil { 151 t.Error(err) 152 } 153 } 154 155 func TestBatcherErrorTracking(t *testing.T) { 156 mock := &mockInput{ 157 ts: make(chan types.Transaction), 158 } 159 160 batchConf := batch.NewPolicyConfig() 161 batchConf.Count = 3 162 163 batchPol, err := batch.NewPolicy(batchConf, types.NoopMgr(), log.Noop(), metrics.Noop()) 164 require.NoError(t, err) 165 166 batcher := NewBatcher(batchPol, mock, log.Noop(), metrics.Noop()) 167 168 testMsgs := []string{} 169 testResChans := []chan types.Response{} 170 for i := 0; i < 3; i++ { 171 testMsgs = append(testMsgs, fmt.Sprintf("test%v", i)) 172 testResChans = append(testResChans, make(chan types.Response)) 173 } 174 175 resErrs := []error{} 176 doneReadsChan := make(chan struct{}) 177 go func() { 178 for i, m := range testMsgs { 179 mock.ts <- types.NewTransaction(message.New([][]byte{[]byte(m)}), testResChans[i]) 180 } 181 for _, rChan := range testResChans { 182 resErrs = append(resErrs, (<-rChan).Error()) 183 } 184 close(doneReadsChan) 185 }() 186 187 var tran types.Transaction 188 select { 189 case tran = <-batcher.TransactionChan(): 190 case <-time.After(time.Second): 191 t.Fatal("timed out") 192 } 193 194 assert.Equal(t, 3, tran.Payload.Len()) 195 tran.Payload.Iter(func(i int, part types.Part) error { 196 assert.Equal(t, fmt.Sprintf("test%v", i), string(part.Get())) 197 return nil 198 }) 199 200 batchErr := ibatch.NewError(tran.Payload, errors.New("ignore this")) 201 batchErr.Failed(1, errors.New("message specific error")) 202 select { 203 case tran.ResponseChan <- response.NewError(batchErr): 204 case <-time.After(time.Second * 5): 205 t.Fatal("timed out") 206 } 207 208 select { 209 case <-doneReadsChan: 210 case <-time.After(time.Second * 5): 211 t.Fatal("timed out") 212 } 213 214 require.Len(t, resErrs, 3) 215 assert.Nil(t, resErrs[0]) 216 assert.EqualError(t, resErrs[1], "message specific error") 217 assert.Nil(t, resErrs[2]) 218 219 mock.CloseAsync() 220 require.NoError(t, batcher.WaitForClose(time.Second)) 221 } 222 223 func TestBatcherTiming(t *testing.T) { 224 mock := &mockInput{ 225 ts: make(chan types.Transaction), 226 } 227 228 batchConf := batch.NewPolicyConfig() 229 batchConf.Count = 0 230 batchConf.Period = "1ms" 231 232 batchPol, err := batch.NewPolicy(batchConf, types.NoopMgr(), log.Noop(), metrics.Noop()) 233 if err != nil { 234 t.Fatal(err) 235 } 236 237 batcher := NewBatcher(batchPol, mock, log.Noop(), metrics.Noop()) 238 239 resChan := make(chan types.Response) 240 select { 241 case mock.ts <- types.NewTransaction(message.New([][]byte{[]byte("foo1")}), resChan): 242 case <-time.After(time.Second): 243 t.Fatal("timed out") 244 } 245 246 var tran types.Transaction 247 select { 248 case tran = <-batcher.TransactionChan(): 249 case <-time.After(time.Second): 250 t.Fatal("timed out") 251 } 252 253 if exp, act := 1, tran.Payload.Len(); exp != act { 254 t.Errorf("Wrong batch size: %v != %v", act, exp) 255 } 256 if exp, act := "foo1", string(tran.Payload.Get(0).Get()); exp != act { 257 t.Errorf("Unexpected message part: %v != %v", act, exp) 258 } 259 260 errSend := errors.New("this is a test error") 261 select { 262 case tran.ResponseChan <- response.NewError(errSend): 263 case <-time.After(time.Second): 264 t.Fatal("timed out") 265 } 266 select { 267 case err := <-resChan: 268 if err.Error() != errSend { 269 t.Errorf("Unexpected error: %v != %v", err.Error(), errSend) 270 } 271 case <-time.After(time.Second): 272 t.Fatal("timed out") 273 } 274 275 select { 276 case mock.ts <- types.NewTransaction(message.New([][]byte{[]byte("foo2")}), resChan): 277 case <-time.After(time.Second): 278 t.Fatal("timed out") 279 } 280 281 select { 282 case tran = <-batcher.TransactionChan(): 283 case <-time.After(time.Second): 284 t.Fatal("timed out") 285 } 286 287 if exp, act := 1, tran.Payload.Len(); exp != act { 288 t.Errorf("Wrong batch size: %v != %v", act, exp) 289 } 290 if exp, act := "foo2", string(tran.Payload.Get(0).Get()); exp != act { 291 t.Errorf("Unexpected message part: %v != %v", act, exp) 292 } 293 294 batcher.CloseAsync() 295 296 select { 297 case tran.ResponseChan <- response.NewError(errSend): 298 case <-time.After(time.Second): 299 t.Fatal("timed out") 300 } 301 select { 302 case err := <-resChan: 303 if err.Error() != errSend { 304 t.Errorf("Unexpected error: %v != %v", err.Error(), errSend) 305 } 306 case <-time.After(time.Second): 307 t.Fatal("timed out") 308 } 309 310 if err := batcher.WaitForClose(time.Second); err != nil { 311 t.Error(err) 312 } 313 } 314 315 func TestBatcherFinalFlush(t *testing.T) { 316 mock := &mockInput{ 317 ts: make(chan types.Transaction), 318 } 319 320 batchConf := batch.NewPolicyConfig() 321 batchConf.Count = 10 322 323 batchPol, err := batch.NewPolicy(batchConf, types.NoopMgr(), log.Noop(), metrics.Noop()) 324 require.NoError(t, err) 325 326 batcher := NewBatcher(batchPol, mock, log.Noop(), metrics.Noop()) 327 328 resChan := make(chan types.Response) 329 select { 330 case mock.ts <- types.NewTransaction(message.New([][]byte{[]byte("foo1")}), resChan): 331 case <-time.After(time.Second): 332 t.Fatal("timed out") 333 } 334 335 mock.CloseAsync() 336 337 var tran types.Transaction 338 select { 339 case tran = <-batcher.TransactionChan(): 340 case <-time.After(time.Second): 341 t.Fatal("timed out") 342 } 343 344 if exp, act := 1, tran.Payload.Len(); exp != act { 345 t.Errorf("Wrong batch size: %v != %v", act, exp) 346 } 347 if exp, act := "foo1", string(tran.Payload.Get(0).Get()); exp != act { 348 t.Errorf("Unexpected message part: %v != %v", act, exp) 349 } 350 351 batcher.CloseAsync() 352 353 select { 354 case tran.ResponseChan <- response.NewAck(): 355 case <-time.After(time.Second): 356 t.Fatal("timed out") 357 } 358 359 if err := batcher.WaitForClose(time.Second); err != nil { 360 t.Error(err) 361 } 362 }