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  }