github.com/Jeffail/benthos/v3@v3.65.0/lib/processor/switch_test.go (about) 1 package processor 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 8 imessage "github.com/Jeffail/benthos/v3/internal/message" 9 "github.com/Jeffail/benthos/v3/lib/condition" 10 "github.com/Jeffail/benthos/v3/lib/log" 11 "github.com/Jeffail/benthos/v3/lib/message" 12 "github.com/Jeffail/benthos/v3/lib/metrics" 13 "github.com/Jeffail/benthos/v3/lib/types" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 func defaultCaseCond() condition.Config { 19 cond := condition.NewConfig() 20 cond.Type = condition.TypeStatic 21 cond.Static = true 22 return cond 23 } 24 25 func TestSwitchCases(t *testing.T) { 26 conf := NewConfig() 27 conf.Type = TypeSwitch 28 29 procConf := NewConfig() 30 procConf.Type = TypeBloblang 31 procConf.Bloblang = `root = "Hit case 0: " + content().string()` 32 33 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 34 Condition: defaultCaseCond(), 35 Check: `content().contains("A")`, 36 Processors: []Config{procConf}, 37 Fallthrough: false, 38 }) 39 40 procConf = NewConfig() 41 procConf.Type = TypeBloblang 42 procConf.Bloblang = `root = "Hit case 1: " + content().string()` 43 44 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 45 Condition: defaultCaseCond(), 46 Check: `content().contains("B")`, 47 Processors: []Config{procConf}, 48 Fallthrough: true, 49 }) 50 51 procConf = NewConfig() 52 procConf.Type = TypeBloblang 53 procConf.Bloblang = `root = "Hit case 2: " + content().string()` 54 55 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 56 Condition: defaultCaseCond(), 57 Check: `content().contains("C")`, 58 Processors: []Config{procConf}, 59 Fallthrough: false, 60 }) 61 62 c, err := New(conf, nil, log.Noop(), metrics.Noop()) 63 require.NoError(t, err) 64 65 defer func() { 66 c.CloseAsync() 67 assert.NoError(t, c.WaitForClose(time.Second)) 68 }() 69 70 type testCase struct { 71 name string 72 input []string 73 expected []string 74 } 75 tests := []testCase{ 76 { 77 name: "switch test 1", 78 input: []string{"A", "AB"}, 79 expected: []string{ 80 "Hit case 0: A", 81 "Hit case 0: AB", 82 }, 83 }, 84 { 85 name: "switch test 2", 86 input: []string{"B", "BC"}, 87 expected: []string{ 88 "Hit case 2: Hit case 1: B", 89 "Hit case 2: Hit case 1: BC", 90 }, 91 }, 92 { 93 name: "switch test 3", 94 input: []string{"C", "CD"}, 95 expected: []string{ 96 "Hit case 2: C", 97 "Hit case 2: CD", 98 }, 99 }, 100 { 101 name: "switch test 4", 102 input: []string{"A", "B", "C"}, 103 expected: []string{ 104 "Hit case 0: A", 105 "Hit case 2: Hit case 1: B", 106 "Hit case 2: C", 107 }, 108 }, 109 { 110 name: "switch test 5", 111 input: []string{"D"}, 112 expected: []string{"D"}, 113 }, 114 { 115 name: "switch test 6", 116 input: []string{"B", "C", "A"}, 117 expected: []string{ 118 "Hit case 2: Hit case 1: B", 119 "Hit case 2: C", 120 "Hit case 0: A", 121 }, 122 }, 123 } 124 125 for _, test := range tests { 126 test := test 127 t.Run(test.name, func(t *testing.T) { 128 msg := message.New(nil) 129 for _, s := range test.input { 130 msg.Append(message.NewPart([]byte(s))) 131 } 132 msgs, res := c.ProcessMessage(msg) 133 require.Nil(t, res) 134 135 resStrs := []string{} 136 for _, b := range message.GetAllBytes(msgs[0]) { 137 resStrs = append(resStrs, string(b)) 138 } 139 assert.Equal(t, test.expected, resStrs) 140 }) 141 } 142 } 143 144 func TestSwitchError(t *testing.T) { 145 conf := NewConfig() 146 conf.Type = TypeSwitch 147 148 procConf := NewConfig() 149 procConf.Type = TypeBloblang 150 procConf.Bloblang = `root = "Hit case 0: " + content().string()` 151 152 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 153 Condition: defaultCaseCond(), 154 Check: `this.id.not_empty().contains("foo")`, 155 Processors: []Config{procConf}, 156 Fallthrough: false, 157 }) 158 159 procConf = NewConfig() 160 procConf.Type = TypeBloblang 161 procConf.Bloblang = `root = "Hit case 1: " + content().string()` 162 163 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 164 Condition: defaultCaseCond(), 165 Check: `this.content.contains("bar")`, 166 Processors: []Config{procConf}, 167 Fallthrough: false, 168 }) 169 170 c, err := New(conf, nil, log.Noop(), metrics.Noop()) 171 require.NoError(t, err) 172 173 defer func() { 174 c.CloseAsync() 175 assert.NoError(t, c.WaitForClose(time.Second)) 176 }() 177 178 msg := message.New(nil) 179 msg.Append(message.NewPart([]byte(`{"id":"foo","content":"just a foo"}`))) 180 msg.Append(message.NewPart([]byte(`{"content":"bar but doesnt have an id!"}`))) 181 msg.Append(message.NewPart([]byte(`{"id":"buz","content":"a real foobar"}`))) 182 183 msgs, res := c.ProcessMessage(msg) 184 require.Nil(t, res) 185 186 assert.Len(t, msgs, 1) 187 assert.Equal(t, 3, msgs[0].Len()) 188 189 resStrs := []string{} 190 for _, b := range message.GetAllBytes(msgs[0]) { 191 resStrs = append(resStrs, string(b)) 192 } 193 194 assert.Equal(t, "", GetFail(msgs[0].Get(0))) 195 assert.Equal(t, "failed assignment (line 1): expected string, array or object value, got null from field `this.id`", GetFail(msgs[0].Get(1))) 196 assert.Equal(t, "", GetFail(msgs[0].Get(2))) 197 198 assert.Equal(t, []string{ 199 `Hit case 0: {"id":"foo","content":"just a foo"}`, 200 `{"content":"bar but doesnt have an id!"}`, 201 `Hit case 1: {"id":"buz","content":"a real foobar"}`, 202 }, resStrs) 203 } 204 205 func BenchmarkSwitch10(b *testing.B) { 206 conf := NewConfig() 207 conf.Type = TypeSwitch 208 209 procConf := NewConfig() 210 procConf.Type = TypeBloblang 211 procConf.Bloblang = `root = "Hit case 0: " + content().string()` 212 213 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 214 Condition: defaultCaseCond(), 215 Check: `content().contains("A")`, 216 Processors: []Config{procConf}, 217 Fallthrough: false, 218 }) 219 220 procConf = NewConfig() 221 procConf.Type = TypeBloblang 222 procConf.Bloblang = `root = "Hit case 1: " + content().string()` 223 224 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 225 Condition: defaultCaseCond(), 226 Check: `content().contains("B")`, 227 Processors: []Config{procConf}, 228 Fallthrough: true, 229 }) 230 231 procConf = NewConfig() 232 procConf.Type = TypeBloblang 233 procConf.Bloblang = `root = "Hit case 2: " + content().string()` 234 235 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 236 Condition: defaultCaseCond(), 237 Check: `content().contains("C")`, 238 Processors: []Config{procConf}, 239 Fallthrough: false, 240 }) 241 242 c, err := New(conf, nil, log.Noop(), metrics.Noop()) 243 require.NoError(b, err) 244 defer func() { 245 c.CloseAsync() 246 assert.NoError(b, c.WaitForClose(time.Second)) 247 }() 248 249 msg := message.New([][]byte{ 250 []byte("A"), 251 []byte("B"), 252 []byte("C"), 253 []byte("D"), 254 []byte("AB"), 255 []byte("AC"), 256 []byte("AD"), 257 []byte("BC"), 258 []byte("BD"), 259 []byte("CD"), 260 }) 261 262 exp := [][]byte{ 263 []byte("Hit case 0: A"), 264 []byte("Hit case 2: Hit case 1: B"), 265 []byte("Hit case 2: C"), 266 []byte("D"), 267 []byte("Hit case 0: AB"), 268 []byte("Hit case 0: AC"), 269 []byte("Hit case 0: AD"), 270 []byte("Hit case 2: Hit case 1: BC"), 271 []byte("Hit case 2: Hit case 1: BD"), 272 []byte("Hit case 2: CD"), 273 } 274 275 b.ResetTimer() 276 277 for i := 0; i < b.N; i++ { 278 msgs, res := c.ProcessMessage(msg) 279 require.Nil(b, res) 280 assert.Equal(b, exp, message.GetAllBytes(msgs[0])) 281 } 282 } 283 284 func BenchmarkSwitch1(b *testing.B) { 285 conf := NewConfig() 286 conf.Type = TypeSwitch 287 288 procConf := NewConfig() 289 procConf.Type = TypeBloblang 290 procConf.Bloblang = `root = "Hit case 0: " + content().string()` 291 292 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 293 Condition: defaultCaseCond(), 294 Check: `content().contains("A")`, 295 Processors: []Config{procConf}, 296 Fallthrough: false, 297 }) 298 299 procConf = NewConfig() 300 procConf.Type = TypeBloblang 301 procConf.Bloblang = `root = "Hit case 1: " + content().string()` 302 303 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 304 Condition: defaultCaseCond(), 305 Check: `content().contains("B")`, 306 Processors: []Config{procConf}, 307 Fallthrough: true, 308 }) 309 310 procConf = NewConfig() 311 procConf.Type = TypeBloblang 312 procConf.Bloblang = `root = "Hit case 2: " + content().string()` 313 314 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 315 Condition: defaultCaseCond(), 316 Check: `content().contains("C")`, 317 Processors: []Config{procConf}, 318 Fallthrough: false, 319 }) 320 321 c, err := New(conf, nil, log.Noop(), metrics.Noop()) 322 require.NoError(b, err) 323 defer func() { 324 c.CloseAsync() 325 assert.NoError(b, c.WaitForClose(time.Second)) 326 }() 327 328 msgs := []types.Message{ 329 message.New([][]byte{[]byte("A")}), 330 message.New([][]byte{[]byte("B")}), 331 message.New([][]byte{[]byte("C")}), 332 message.New([][]byte{[]byte("D")}), 333 message.New([][]byte{[]byte("AB")}), 334 message.New([][]byte{[]byte("AC")}), 335 message.New([][]byte{[]byte("AD")}), 336 message.New([][]byte{[]byte("BC")}), 337 message.New([][]byte{[]byte("BD")}), 338 message.New([][]byte{[]byte("CD")}), 339 } 340 341 exp := [][]byte{ 342 []byte("Hit case 0: A"), 343 []byte("Hit case 2: Hit case 1: B"), 344 []byte("Hit case 2: C"), 345 []byte("D"), 346 []byte("Hit case 0: AB"), 347 []byte("Hit case 0: AC"), 348 []byte("Hit case 0: AD"), 349 []byte("Hit case 2: Hit case 1: BC"), 350 []byte("Hit case 2: Hit case 1: BD"), 351 []byte("Hit case 2: CD"), 352 } 353 354 b.ResetTimer() 355 356 for i := 0; i < b.N; i++ { 357 resMsgs, res := c.ProcessMessage(msgs[i%len(msgs)]) 358 require.Nil(b, res) 359 assert.Equal(b, [][]byte{exp[i%len(exp)]}, message.GetAllBytes(resMsgs[0])) 360 } 361 } 362 363 func BenchmarkSwitchDeprecated1(b *testing.B) { 364 conf := NewConfig() 365 conf.Type = TypeSwitch 366 367 condConf := condition.NewConfig() 368 condConf.Type = condition.TypeBloblang 369 condConf.Bloblang = `content().contains("A")` 370 371 procConf := NewConfig() 372 procConf.Type = TypeBloblang 373 procConf.Bloblang = `root = "Hit case 0: " + content().string()` 374 375 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 376 Condition: condConf, 377 Processors: []Config{procConf}, 378 Fallthrough: false, 379 }) 380 381 condConf = condition.NewConfig() 382 condConf.Type = condition.TypeBloblang 383 condConf.Bloblang = `content().contains("B")` 384 385 procConf = NewConfig() 386 procConf.Type = TypeBloblang 387 procConf.Bloblang = `root = "Hit case 1: " + content().string()` 388 389 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 390 Condition: condConf, 391 Processors: []Config{procConf}, 392 Fallthrough: true, 393 }) 394 395 condConf = condition.NewConfig() 396 condConf.Type = condition.TypeBloblang 397 condConf.Bloblang = `content().contains("C")` 398 399 procConf = NewConfig() 400 procConf.Type = TypeBloblang 401 procConf.Bloblang = `root = "Hit case 2: " + content().string()` 402 403 conf.Switch = append(conf.Switch, SwitchCaseConfig{ 404 Condition: condConf, 405 Processors: []Config{procConf}, 406 Fallthrough: false, 407 }) 408 409 c, err := New(conf, nil, log.Noop(), metrics.Noop()) 410 require.NoError(b, err) 411 defer func() { 412 c.CloseAsync() 413 assert.NoError(b, c.WaitForClose(time.Second)) 414 }() 415 416 msgs := []types.Message{ 417 message.New([][]byte{[]byte("A")}), 418 message.New([][]byte{[]byte("B")}), 419 message.New([][]byte{[]byte("C")}), 420 message.New([][]byte{[]byte("D")}), 421 message.New([][]byte{[]byte("AB")}), 422 message.New([][]byte{[]byte("AC")}), 423 message.New([][]byte{[]byte("AD")}), 424 message.New([][]byte{[]byte("BC")}), 425 message.New([][]byte{[]byte("BD")}), 426 message.New([][]byte{[]byte("CD")}), 427 } 428 429 exp := [][]byte{ 430 []byte("Hit case 0: A"), 431 []byte("Hit case 2: Hit case 1: B"), 432 []byte("Hit case 2: C"), 433 []byte("D"), 434 []byte("Hit case 0: AB"), 435 []byte("Hit case 0: AC"), 436 []byte("Hit case 0: AD"), 437 []byte("Hit case 2: Hit case 1: BC"), 438 []byte("Hit case 2: Hit case 1: BD"), 439 []byte("Hit case 2: CD"), 440 } 441 442 b.ResetTimer() 443 444 for i := 0; i < b.N; i++ { 445 resMsgs, res := c.ProcessMessage(msgs[i%len(msgs)]) 446 require.Nil(b, res) 447 assert.Equal(b, [][]byte{exp[i%len(exp)]}, message.GetAllBytes(resMsgs[0])) 448 } 449 } 450 451 func BenchmarkSortCorrect(b *testing.B) { 452 sortedParts := make([]types.Part, b.N) 453 for i := range sortedParts { 454 sortedParts[i] = message.NewPart([]byte(fmt.Sprintf("hello world %040d", i))) 455 } 456 457 group, parts := imessage.NewSortGroupParts(sortedParts) 458 459 b.ReportAllocs() 460 b.ResetTimer() 461 462 reorderFromGroup(group, parts) 463 } 464 465 func BenchmarkSortReverse(b *testing.B) { 466 sortedParts := make([]types.Part, b.N) 467 for i := range sortedParts { 468 sortedParts[i] = message.NewPart([]byte(fmt.Sprintf("hello world %040d", i))) 469 } 470 471 group, parts := imessage.NewSortGroupParts(sortedParts) 472 unsortedParts := make([]types.Part, b.N) 473 for i := range parts { 474 unsortedParts[i] = parts[len(parts)-i-1] 475 } 476 477 b.ReportAllocs() 478 b.ResetTimer() 479 480 reorderFromGroup(group, unsortedParts) 481 }