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 }