github.com/Jeffail/benthos/v3@v3.65.0/lib/output/broker_test.go (about) 1 package output 2 3 import ( 4 "os" 5 "path/filepath" 6 "testing" 7 "time" 8 9 "github.com/Jeffail/benthos/v3/lib/log" 10 "github.com/Jeffail/benthos/v3/lib/message" 11 "github.com/Jeffail/benthos/v3/lib/metrics" 12 "github.com/Jeffail/benthos/v3/lib/processor" 13 "github.com/Jeffail/benthos/v3/lib/types" 14 ) 15 16 func TestFanOutBroker(t *testing.T) { 17 dir := t.TempDir() 18 19 outOne, outTwo := NewConfig(), NewConfig() 20 outOne.Type, outTwo.Type = TypeFiles, TypeFiles 21 outOne.Files.Path = filepath.Join(dir, "one", `foo-${!count("1s")}.txt`) 22 outTwo.Files.Path = filepath.Join(dir, "two", `bar-${!count("2s")}.txt`) 23 24 procOne, procTwo := processor.NewConfig(), processor.NewConfig() 25 procOne.Type, procTwo.Type = processor.TypeText, processor.TypeText 26 procOne.Text.Operator = "prepend" 27 procOne.Text.Value = "one-" 28 procTwo.Text.Operator = "prepend" 29 procTwo.Text.Value = "two-" 30 31 outOne.Processors = append(outOne.Processors, procOne) 32 outTwo.Processors = append(outTwo.Processors, procTwo) 33 34 conf := NewConfig() 35 conf.Type = TypeBroker 36 conf.Broker.Pattern = "fan_out" 37 conf.Broker.Outputs = append(conf.Broker.Outputs, outOne, outTwo) 38 39 s, err := New(conf, nil, log.Noop(), metrics.Noop()) 40 if err != nil { 41 t.Fatal(err) 42 } 43 44 sendChan := make(chan types.Transaction) 45 resChan := make(chan types.Response) 46 if err = s.Consume(sendChan); err != nil { 47 t.Fatal(err) 48 } 49 50 defer func() { 51 s.CloseAsync() 52 if err := s.WaitForClose(time.Second); err != nil { 53 t.Error(err) 54 } 55 }() 56 57 inputs := []string{ 58 "first", "second", "third", 59 } 60 expFiles := map[string]string{ 61 "./one/foo-1.txt": "one-first", 62 "./one/foo-2.txt": "one-second", 63 "./one/foo-3.txt": "one-third", 64 "./two/bar-1.txt": "two-first", 65 "./two/bar-2.txt": "two-second", 66 "./two/bar-3.txt": "two-third", 67 } 68 69 for _, input := range inputs { 70 testMsg := message.New([][]byte{[]byte(input)}) 71 select { 72 case sendChan <- types.NewTransaction(testMsg, resChan): 73 case <-time.After(time.Second): 74 t.Fatal("Action timed out") 75 } 76 77 select { 78 case res := <-resChan: 79 if res.Error() != nil { 80 t.Fatal(res.Error()) 81 } 82 case <-time.After(time.Second): 83 t.Fatal("Action timed out") 84 } 85 } 86 87 for k, exp := range expFiles { 88 k = filepath.Join(dir, k) 89 fileBytes, err := os.ReadFile(k) 90 if err != nil { 91 t.Errorf("Expected file '%v' could not be read: %v", k, err) 92 continue 93 } 94 if act := string(fileBytes); exp != act { 95 t.Errorf("Wrong contents for file '%v': %v != %v", k, act, exp) 96 } 97 } 98 } 99 100 func TestRoundRobinBroker(t *testing.T) { 101 dir := t.TempDir() 102 103 outOne, outTwo := NewConfig(), NewConfig() 104 outOne.Type, outTwo.Type = TypeFiles, TypeFiles 105 outOne.Files.Path = filepath.Join(dir, "one", `foo-${!count("rrfoo")}.txt`) 106 outTwo.Files.Path = filepath.Join(dir, "two", `bar-${!count("rrbar")}.txt`) 107 108 procOne, procTwo := processor.NewConfig(), processor.NewConfig() 109 procOne.Type, procTwo.Type = processor.TypeText, processor.TypeText 110 procOne.Text.Operator = "prepend" 111 procOne.Text.Value = "one-" 112 procTwo.Text.Operator = "prepend" 113 procTwo.Text.Value = "two-" 114 115 outOne.Processors = append(outOne.Processors, procOne) 116 outTwo.Processors = append(outTwo.Processors, procTwo) 117 118 conf := NewConfig() 119 conf.Type = TypeBroker 120 conf.Broker.Pattern = "round_robin" 121 conf.Broker.Outputs = append(conf.Broker.Outputs, outOne, outTwo) 122 123 s, err := New(conf, nil, log.Noop(), metrics.Noop()) 124 if err != nil { 125 t.Fatal(err) 126 } 127 128 sendChan := make(chan types.Transaction) 129 resChan := make(chan types.Response) 130 if err = s.Consume(sendChan); err != nil { 131 t.Fatal(err) 132 } 133 134 t.Cleanup(func() { 135 s.CloseAsync() 136 if err := s.WaitForClose(time.Second); err != nil { 137 t.Error(err) 138 } 139 }) 140 141 inputs := []string{ 142 "first", "second", "third", "fourth", 143 } 144 expFiles := map[string]string{ 145 "./one/foo-1.txt": "one-first", 146 "./one/foo-2.txt": "one-third", 147 "./two/bar-1.txt": "two-second", 148 "./two/bar-2.txt": "two-fourth", 149 } 150 151 for _, input := range inputs { 152 testMsg := message.New([][]byte{[]byte(input)}) 153 select { 154 case sendChan <- types.NewTransaction(testMsg, resChan): 155 case <-time.After(time.Second): 156 t.Fatal("Action timed out") 157 } 158 159 select { 160 case res := <-resChan: 161 if res.Error() != nil { 162 t.Fatal(res.Error()) 163 } 164 case <-time.After(time.Second): 165 t.Fatal("Action timed out") 166 } 167 } 168 169 for k, exp := range expFiles { 170 k = filepath.Join(dir, k) 171 fileBytes, err := os.ReadFile(k) 172 if err != nil { 173 t.Errorf("Expected file '%v' could not be read: %v", k, err) 174 continue 175 } 176 if act := string(fileBytes); exp != act { 177 t.Errorf("Wrong contents for file '%v': %v != %v", k, act, exp) 178 } 179 } 180 } 181 182 func TestGreedyBroker(t *testing.T) { 183 dir := t.TempDir() 184 185 outOne, outTwo := NewConfig(), NewConfig() 186 outOne.Type, outTwo.Type = TypeFiles, TypeFiles 187 outOne.Files.Path = filepath.Join(dir, "one", `foo-${!count("gfoo")}.txt`) 188 outTwo.Files.Path = filepath.Join(dir, "two", `bar-${!count("gbar")}.txt`) 189 190 procOne, procTwo := processor.NewConfig(), processor.NewConfig() 191 procOne.Type, procTwo.Type = processor.TypeText, processor.TypeText 192 procOne.Text.Operator = "prepend" 193 procOne.Text.Value = "one-" 194 procTwo.Text.Operator = "prepend" 195 procTwo.Text.Value = "two-" 196 197 outOne.Processors = append(outOne.Processors, procOne) 198 outTwo.Processors = append(outTwo.Processors, procTwo) 199 200 procOne, procTwo = processor.NewConfig(), processor.NewConfig() 201 procOne.Type, procTwo.Type = processor.TypeSleep, processor.TypeSleep 202 procOne.Sleep.Duration = "50ms" 203 procTwo.Sleep.Duration = "50ms" 204 205 outOne.Processors = append(outOne.Processors, procOne) 206 outTwo.Processors = append(outTwo.Processors, procTwo) 207 208 conf := NewConfig() 209 conf.Type = TypeBroker 210 conf.Broker.Pattern = "greedy" 211 conf.Broker.Outputs = append(conf.Broker.Outputs, outOne, outTwo) 212 213 s, err := New(conf, nil, log.Noop(), metrics.Noop()) 214 if err != nil { 215 t.Fatal(err) 216 } 217 218 sendChan := make(chan types.Transaction) 219 resChan := make(chan types.Response) 220 if err = s.Consume(sendChan); err != nil { 221 t.Fatal(err) 222 } 223 224 defer func() { 225 s.CloseAsync() 226 if err := s.WaitForClose(time.Second); err != nil { 227 t.Error(err) 228 } 229 }() 230 231 inputs := []string{ 232 "first", "second", "third", "fourth", 233 } 234 expFiles := map[string][2]string{ 235 "./one/foo-1.txt": {"one-first", "one-second"}, 236 "./one/foo-2.txt": {"one-third", "one-fourth"}, 237 "./two/bar-1.txt": {"two-first", "two-second"}, 238 "./two/bar-2.txt": {"two-third", "two-fourth"}, 239 } 240 241 for _, input := range inputs { 242 testMsg := message.New([][]byte{[]byte(input)}) 243 select { 244 case sendChan <- types.NewTransaction(testMsg, resChan): 245 case <-time.After(time.Second): 246 t.Fatal("Action timed out") 247 } 248 249 select { 250 case res := <-resChan: 251 if res.Error() != nil { 252 t.Fatal(res.Error()) 253 } 254 case <-time.After(time.Second): 255 t.Fatal("Action timed out") 256 } 257 } 258 259 for k, exp := range expFiles { 260 k = filepath.Join(dir, k) 261 fileBytes, err := os.ReadFile(k) 262 if err != nil { 263 t.Errorf("Expected file '%v' could not be read: %v", k, err) 264 continue 265 } 266 if act := string(fileBytes); exp[0] != act && exp[1] != act { 267 t.Errorf("Wrong contents for file '%v': %v != (%v || %v)", k, act, exp[0], exp[1]) 268 } 269 } 270 } 271 272 func TestTryBroker(t *testing.T) { 273 dir := t.TempDir() 274 275 outOne, outTwo, outThree := NewConfig(), NewConfig(), NewConfig() 276 outOne.Type, outTwo.Type, outThree.Type = TypeHTTPClient, TypeFiles, TypeFile 277 outOne.HTTPClient.URL = "http://localhost:11111111/badurl" 278 outOne.HTTPClient.NumRetries = 1 279 outOne.HTTPClient.Retry = "1ms" 280 outTwo.Files.Path = filepath.Join(dir, "two", `bar-${!count("tfoo")}-${!count("tbar")}.txt`) 281 outThree.File.Path = "/dev/null" 282 283 procOne, procTwo, procThree := processor.NewConfig(), processor.NewConfig(), processor.NewConfig() 284 procOne.Type, procTwo.Type, procThree.Type = processor.TypeText, processor.TypeText, processor.TypeText 285 procOne.Text.Operator = "prepend" 286 procOne.Text.Value = "this-should-never-appear ${!count(\"tfoo\")}" 287 procTwo.Text.Operator = "prepend" 288 procTwo.Text.Value = "two-" 289 procThree.Text.Operator = "prepend" 290 procThree.Text.Value = "this-should-never-appear ${!count(\"tbar\")}" 291 292 outOne.Processors = append(outOne.Processors, procOne) 293 outTwo.Processors = append(outTwo.Processors, procTwo) 294 outThree.Processors = append(outThree.Processors, procThree) 295 296 conf := NewConfig() 297 conf.Type = TypeBroker 298 conf.Broker.Pattern = "try" 299 conf.Broker.Outputs = append(conf.Broker.Outputs, outOne, outTwo, outThree) 300 301 s, err := New(conf, nil, log.Noop(), metrics.Noop()) 302 if err != nil { 303 t.Fatal(err) 304 } 305 306 sendChan := make(chan types.Transaction) 307 resChan := make(chan types.Response) 308 if err = s.Consume(sendChan); err != nil { 309 t.Fatal(err) 310 } 311 312 defer func() { 313 s.CloseAsync() 314 if err := s.WaitForClose(time.Second); err != nil { 315 t.Error(err) 316 } 317 }() 318 319 inputs := []string{ 320 "first", "second", "third", "fourth", 321 } 322 expFiles := map[string]string{ 323 "./two/bar-2-1.txt": "two-first", 324 "./two/bar-4-2.txt": "two-second", 325 "./two/bar-6-3.txt": "two-third", 326 "./two/bar-8-4.txt": "two-fourth", 327 } 328 329 for _, input := range inputs { 330 testMsg := message.New([][]byte{[]byte(input)}) 331 select { 332 case sendChan <- types.NewTransaction(testMsg, resChan): 333 case <-time.After(time.Second * 2): 334 t.Fatal("Action timed out") 335 } 336 337 select { 338 case res := <-resChan: 339 if res.Error() != nil { 340 t.Fatal(res.Error()) 341 } 342 case <-time.After(time.Second * 2): 343 t.Fatal("Action timed out") 344 } 345 } 346 347 for k, exp := range expFiles { 348 k = filepath.Join(dir, k) 349 fileBytes, err := os.ReadFile(k) 350 if err != nil { 351 t.Errorf("Expected file '%v' could not be read: %v", k, err) 352 continue 353 } 354 if act := string(fileBytes); exp != act { 355 t.Errorf("Wrong contents for file '%v': %v != %v", k, act, exp) 356 } 357 } 358 }