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 //------------------------------------------------------------------------------