github.com/GuanceCloud/cliutils@v1.1.21/filter/eval_test.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package filter
     7  
     8  import (
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  )
    13  
    14  func TestExprConditions(t *testing.T) {
    15  	cases := []struct {
    16  		in     string
    17  		source string
    18  		tags   map[string]string
    19  		fields map[string]interface{}
    20  		pass   bool
    21  	}{
    22  		{
    23  			in:     "{ abc notmatch []}",
    24  			fields: map[string]interface{}{"abc": "abc123"},
    25  			pass:   false,
    26  		},
    27  
    28  		{
    29  			in:     "{ abc match ['g(-z]+ng wrong regex']} # invalid regexp",
    30  			fields: map[string]interface{}{"abc": "abc123"},
    31  			pass:   false,
    32  		},
    33  
    34  		{
    35  			in:     "{ abc match ['a.*']}",
    36  			fields: map[string]interface{}{"abc": "abc123"},
    37  			pass:   true,
    38  		},
    39  
    40  		{
    41  			in:     "{ abc match ['a.*']}",
    42  			fields: map[string]interface{}{"abc": "abc123"},
    43  			pass:   true,
    44  		},
    45  
    46  		{
    47  			in:     "{ source = re(`.*`) and (abc match ['a.*'])}",
    48  			fields: map[string]interface{}{"abc": "abc123"},
    49  			tags:   map[string]string{"source": "12345"},
    50  			pass:   true,
    51  		},
    52  
    53  		{
    54  			in:     "{ abc notmatch ['a.*'] or xyz match ['.*']}",
    55  			fields: map[string]interface{}{"abc": "abc123"},
    56  			tags:   map[string]string{"xyz": "def"},
    57  			pass:   true,
    58  		},
    59  
    60  		{
    61  			in:     "{abc notin [1.1,1.2,1.3] and (a > 1 || c< 0)}",
    62  			fields: map[string]interface{}{"abc": int64(4), "a": int64(-1), "c": int64(-2)},
    63  			pass:   true,
    64  		},
    65  
    66  		{
    67  			in:     "{a notin [1,2,3,4]}",
    68  			fields: map[string]interface{}{"a": int64(4)},
    69  			pass:   false,
    70  		},
    71  
    72  		{
    73  			in:     "{abc notin [1,2,3]}",
    74  			fields: map[string]interface{}{"abc": int64(4)},
    75  			pass:   true,
    76  		},
    77  
    78  		{
    79  			in:     ";;;{a > 1, b > 1 or c > 1, xx != 123 };;;; {xyz > 1};;;",
    80  			fields: map[string]interface{}{"a": int64(2), "c": "xyz"},
    81  			pass:   false,
    82  		},
    83  
    84  		{
    85  			in:     "{a > 1, b > 1 or c > 1}",
    86  			fields: map[string]interface{}{"a": int64(2), "c": "xyz"},
    87  			pass:   false,
    88  		},
    89  
    90  		{
    91  			in:     "{a > 1, b > 1 or c = 'xyz'}",
    92  			fields: map[string]interface{}{"a": int64(2), "c": "xyz", "b": false},
    93  			pass:   true,
    94  		},
    95  
    96  		{
    97  			in:     "{xxx < 111}; {a > 1, b > 1 or c = 'xyz'}",
    98  			fields: map[string]interface{}{"a": int64(2), "c": "xyz", "b": false},
    99  			pass:   true,
   100  		},
   101  
   102  		{
   103  			in:     `{host = re("^nginx_.*$")}`,
   104  			fields: map[string]interface{}{"host": "nginx_abc"},
   105  			pass:   true,
   106  		},
   107  
   108  		{
   109  			in:     "{host = re(`nginx_*`)}",
   110  			fields: map[string]interface{}{"host": "abcdef"},
   111  			pass:   false,
   112  		},
   113  
   114  		// {
   115  		// 	in:     "{host in [re(`mongo_.*`), re(`nginx_.*`), reg(`mysql_.*`)]}",
   116  		// 	fields: map[string]interface{}{"host": "123abc"},
   117  		// 	pass:   false,
   118  		// },
   119  
   120  		{
   121  			in:     "{ abc = NULL && abc = null && abc = NIL && abc = nil }",
   122  			fields: map[string]any{"xyz": int64(123)},
   123  			pass:   true,
   124  		},
   125  
   126  		{
   127  			in:     "{ abc in [ NULL, 123, 'hello'] }",
   128  			fields: map[string]any{"xyz": int64(123)},
   129  			pass:   true,
   130  		},
   131  
   132  		{
   133  			in:     "{ abc notin [ NULL, 123, 'hello'] }",
   134  			fields: map[string]any{"xyz": int64(123)},
   135  			pass:   false,
   136  		},
   137  
   138  		{
   139  			in:     "{ abc not_in [ 123, 'hello'] }",
   140  			fields: map[string]any{"xyz": int64(123)},
   141  			pass:   true,
   142  		},
   143  
   144  		{
   145  			in:     "{ xyz != NULL and abc = nil }",
   146  			fields: map[string]any{"xyz": int64(123)},
   147  			pass:   true,
   148  		},
   149  
   150  		{
   151  			in:     "{ xyz in [ null, 123 ] and abc = nil }",
   152  			fields: map[string]any{"xyz": int64(123)},
   153  			pass:   true,
   154  		},
   155  
   156  		{
   157  			in:   "{ xyz = nil }",
   158  			pass: true,
   159  		},
   160  		{
   161  			in:   "{ xyz != nil }",
   162  			pass: false,
   163  		},
   164  
   165  		{
   166  			in:   "{ nil = nil }", // nil literal
   167  			pass: true,
   168  		},
   169  
   170  		{
   171  			in:   "{ 1 = 1 }", // int literal
   172  			pass: true,
   173  		},
   174  
   175  		{
   176  			in:   " {a = b}", // a,b both nil, but b is not literal or regex
   177  			pass: false,
   178  		},
   179  
   180  		{
   181  			in:   "{ true = true }", // boolean literal
   182  			pass: true,
   183  		},
   184  
   185  		{
   186  			in:   "{ 'hello' = 'hello'}", // string literal
   187  			pass: true,
   188  		},
   189  
   190  		{
   191  			in:   "{ 1.0 = 1.0 }", // float literal
   192  			pass: true,
   193  		},
   194  		{
   195  			in:   "{ 'abc' = 'ABC' }",
   196  			pass: false,
   197  		},
   198  
   199  		{
   200  			in:   "{ re('ABC') = nil }", // regexp can not be lhs
   201  			pass: false,
   202  		},
   203  
   204  		{
   205  			in:   "{ nil = re('ABC') }",
   206  			pass: false,
   207  		},
   208  
   209  		{
   210  			in:     "{ abc = re(`nginx_*`)}", // abc is nil
   211  			fields: map[string]interface{}{"host": "abcdef"},
   212  			pass:   false,
   213  		},
   214  
   215  		{
   216  			in:     "{ false = re(`nginx_*`)}",
   217  			fields: map[string]interface{}{"host": "abcdef"},
   218  			pass:   false,
   219  		},
   220  
   221  		{
   222  			in:     "{ 123 = re(`nginx_*`)}",
   223  			fields: map[string]interface{}{"host": "abcdef"},
   224  			pass:   false,
   225  		},
   226  
   227  		{
   228  			in:     "{ 3.14 = re(`nginx_*`)}",
   229  			fields: map[string]interface{}{"host": "abcdef"},
   230  			pass:   false,
   231  		},
   232  
   233  		// bool in list
   234  		{
   235  			in:     "{ xyz in [ false, true, 123,'abc' ] }",
   236  			fields: map[string]any{"xyz": false},
   237  			pass:   true,
   238  		},
   239  
   240  		{
   241  			in:     "{ abc in [ false ] }",
   242  			fields: map[string]any{"xyz": false},
   243  			pass:   false,
   244  		},
   245  	}
   246  
   247  	for _, tc := range cases {
   248  		t.Run(tc.in, func(t *testing.T) {
   249  			conditions, _ := GetConds(tc.in)
   250  
   251  			assert.Equalf(t, tc.pass, conditions.Eval(newtf(tc.tags, tc.fields)) >= 0, "conditions: %s", conditions)
   252  
   253  			t.Logf("[ok] %s => %v, source: %s, tags: %+#v, fields: %+#v", tc.in, tc.pass, tc.source, tc.tags, tc.fields)
   254  		})
   255  	}
   256  }
   257  
   258  func TestConditions(t *testing.T) {
   259  	cases := []struct {
   260  		in     WhereConditions
   261  		tags   map[string]string
   262  		fields map[string]interface{}
   263  		pass   bool
   264  	}{
   265  		{ // multi conditions
   266  			fields: map[string]interface{}{"a": int64(2), "c": "xyz"},
   267  			in: WhereConditions{
   268  				&WhereCondition{
   269  					conditions: []Node{
   270  						&BinaryExpr{
   271  							Op:  GT,
   272  							LHS: &Identifier{Name: "b"},
   273  							RHS: &NumberLiteral{IsInt: true, Int: int64(1)},
   274  						},
   275  					},
   276  				},
   277  
   278  				&WhereCondition{
   279  					conditions: []Node{
   280  						&BinaryExpr{
   281  							Op:  GT,
   282  							LHS: &Identifier{Name: "d"},
   283  							RHS: &NumberLiteral{IsInt: true, Int: int64(1)},
   284  						},
   285  					},
   286  				},
   287  			},
   288  			pass: false,
   289  		},
   290  
   291  		{
   292  			fields: map[string]interface{}{"a": int64(2)},
   293  			in: WhereConditions{
   294  				&WhereCondition{
   295  					conditions: []Node{
   296  						&BinaryExpr{
   297  							Op:  GT,
   298  							LHS: &Identifier{Name: "a"},
   299  							RHS: &NumberLiteral{IsInt: true, Int: int64(1)},
   300  						},
   301  					},
   302  				},
   303  			},
   304  			pass: true,
   305  		},
   306  
   307  		{
   308  			pass:   true,
   309  			fields: map[string]interface{}{"a": "abc"},
   310  			in: WhereConditions{
   311  				&WhereCondition{
   312  					conditions: []Node{
   313  						&BinaryExpr{
   314  							Op:  IN,
   315  							LHS: &Identifier{Name: "a"},
   316  							RHS: NodeList{
   317  								&StringLiteral{Val: "123"},
   318  								&StringLiteral{Val: "abc"},
   319  								&NumberLiteral{Float: 123.0},
   320  							},
   321  						},
   322  					},
   323  				},
   324  			},
   325  		},
   326  
   327  		{
   328  			pass: true,
   329  			tags: map[string]string{"source": "abc"},
   330  			in: WhereConditions{
   331  				&WhereCondition{
   332  					conditions: []Node{
   333  						&BinaryExpr{
   334  							Op:  IN,
   335  							LHS: &Identifier{Name: "source"},
   336  							RHS: NodeList{
   337  								&StringLiteral{Val: "xyz"},
   338  								&StringLiteral{Val: "abc"},
   339  								&NumberLiteral{Float: 123.0},
   340  							},
   341  						},
   342  					},
   343  				},
   344  			},
   345  		},
   346  
   347  		{
   348  			pass: true,
   349  			tags: map[string]string{"source": "abc"},
   350  			in: WhereConditions{
   351  				&WhereCondition{
   352  					conditions: []Node{
   353  						&BinaryExpr{
   354  							Op:  NEQ,
   355  							LHS: &Identifier{Name: "source"},
   356  							RHS: &StringLiteral{Val: "xyz"},
   357  						},
   358  					},
   359  				},
   360  			},
   361  		},
   362  
   363  		{
   364  			pass: false,
   365  			tags: map[string]string{"source": "abc"},
   366  			in: WhereConditions{
   367  				&WhereCondition{
   368  					conditions: []Node{
   369  						&BinaryExpr{
   370  							Op:  EQ,
   371  							LHS: &Identifier{Name: "source"},
   372  							RHS: &StringLiteral{Val: "xyz"},
   373  						},
   374  					},
   375  				},
   376  			},
   377  		},
   378  	}
   379  
   380  	for _, tc := range cases {
   381  		t.Run(tc.in.String(), func(t *testing.T) {
   382  			assert.Equal(t, tc.pass, tc.in.Eval(newtf(tc.tags, tc.fields)) >= 0)
   383  			t.Logf("[ok] %s => %v,  tags: %+#v, fields: %+#v", tc.in, tc.pass, tc.tags, tc.fields)
   384  		})
   385  	}
   386  }
   387  
   388  func TestBinEval(t *testing.T) {
   389  	cases := []struct {
   390  		op   ItemType
   391  		lhs  interface{}
   392  		rhs  interface{}
   393  		pass bool
   394  	}{
   395  		{
   396  			op:   GT,
   397  			lhs:  int64(4),
   398  			rhs:  int64(3),
   399  			pass: true,
   400  		},
   401  
   402  		{
   403  			op:   GTE,
   404  			lhs:  int64(4),
   405  			rhs:  int64(3),
   406  			pass: true,
   407  		},
   408  
   409  		{
   410  			op:   GTE,
   411  			lhs:  int64(4),
   412  			rhs:  int64(4),
   413  			pass: true,
   414  		},
   415  
   416  		{
   417  			op:   EQ,
   418  			lhs:  int64(3),
   419  			rhs:  int64(3),
   420  			pass: true,
   421  		},
   422  
   423  		{
   424  			op:   EQ,
   425  			lhs:  "abc",
   426  			rhs:  "def",
   427  			pass: false,
   428  		},
   429  
   430  		{
   431  			op:   LT,
   432  			lhs:  "abc",
   433  			rhs:  "def",
   434  			pass: true,
   435  		},
   436  
   437  		{
   438  			op:   LT,
   439  			lhs:  "abc",
   440  			rhs:  123.4,
   441  			pass: false,
   442  		},
   443  
   444  		{
   445  			op:   LT,
   446  			lhs:  123.4,
   447  			rhs:  "abc",
   448  			pass: false,
   449  		},
   450  
   451  		{
   452  			op:   EQ,
   453  			lhs:  123.4,
   454  			rhs:  "abc",
   455  			pass: false,
   456  		},
   457  
   458  		{
   459  			op:   NEQ,
   460  			lhs:  123.4,
   461  			rhs:  "abc",
   462  			pass: false,
   463  		},
   464  
   465  		{
   466  			op:   IN,
   467  			lhs:  123.4,
   468  			rhs:  "abc",
   469  			pass: false,
   470  		},
   471  	}
   472  
   473  	for _, tc := range cases {
   474  		assert.Equal(t, tc.pass, binEval(tc.op, tc.lhs, tc.rhs))
   475  		t.Logf("[ok] %v %s %v => %v", tc.lhs, tc.op, tc.rhs, tc.pass)
   476  	}
   477  }
   478  
   479  func TestEval(t *testing.T) {
   480  	cases := []struct {
   481  		cond   *BinaryExpr
   482  		tags   map[string]string
   483  		fields map[string]interface{}
   484  		pass   bool
   485  	}{
   486  		{
   487  			fields: map[string]interface{}{"a": int64(3)},
   488  			cond: &BinaryExpr{
   489  				Op:  GTE,
   490  				LHS: &Identifier{Name: "a"},
   491  				RHS: &NumberLiteral{IsInt: true, Int: int64(3)},
   492  			},
   493  			pass: true,
   494  		},
   495  
   496  		{
   497  			fields: map[string]interface{}{"a": float64(3.14)},
   498  			cond: &BinaryExpr{
   499  				Op:  GT,
   500  				LHS: &Identifier{Name: "a"},
   501  				RHS: &NumberLiteral{Float: float64(1.1)},
   502  			},
   503  			pass: true,
   504  		},
   505  
   506  		{
   507  			tags: map[string]string{"source": "abc"},
   508  			cond: &BinaryExpr{
   509  				Op:  EQ,
   510  				LHS: &Identifier{Name: "source"},
   511  				RHS: &StringLiteral{Val: "abc"},
   512  			},
   513  			pass: true,
   514  		},
   515  
   516  		{
   517  			tags: map[string]string{"source": "abc"},
   518  			cond: &BinaryExpr{
   519  				Op:  IN,
   520  				LHS: &Identifier{Name: "source"},
   521  				RHS: NodeList{
   522  					&StringLiteral{Val: "abc123"},
   523  					&NumberLiteral{Float: 3.14},
   524  					&StringLiteral{Val: "xyz"},
   525  				},
   526  			},
   527  			pass: false,
   528  		},
   529  
   530  		{
   531  			tags: map[string]string{"a": "xyz"},
   532  			cond: &BinaryExpr{
   533  				Op:  IN,
   534  				LHS: &Identifier{Name: "a"},
   535  				RHS: NodeList{
   536  					&StringLiteral{Val: "abc123"},
   537  					&NumberLiteral{Float: 3.14},
   538  					&StringLiteral{Val: "xyz"},
   539  				},
   540  			},
   541  			pass: true,
   542  		},
   543  	}
   544  
   545  	for _, tc := range cases {
   546  		t.Run(tc.cond.String(), func(t *testing.T) {
   547  			t.Logf("[ok] %s => %v", tc.cond, tc.pass)
   548  			assert.Equal(t, tc.pass, tc.cond.Eval(newtf(tc.tags, tc.fields)))
   549  		})
   550  	}
   551  }
   552  
   553  func BenchmarkRegexp(b *testing.B) {
   554  	// cliutils.CreateRandomString()
   555  }