github.com/GuanceCloud/cliutils@v1.1.21/pipeline/ptinput/funcs/utils_fn_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 funcs 7 8 import ( 9 "fmt" 10 "testing" 11 "time" 12 13 "github.com/GuanceCloud/cliutils/pipeline/ptinput" 14 "github.com/GuanceCloud/cliutils/point" 15 tu "github.com/GuanceCloud/cliutils/testutil" 16 "github.com/GuanceCloud/platypus/pkg/ast" 17 "github.com/GuanceCloud/platypus/pkg/engine" 18 "github.com/GuanceCloud/platypus/pkg/engine/runtime" 19 "github.com/GuanceCloud/platypus/pkg/errchain" 20 "github.com/GuanceCloud/platypus/pkg/token" 21 "github.com/stretchr/testify/assert" 22 ) 23 24 func TestNewFn(t *testing.T) { 25 t.Run("new_check_p", func(t *testing.T) { 26 err := panicWrap(func() { 27 NewFunc("check_p", nil, nil, [2]*PLDoc{{}, {}}, nil) 28 }) 29 assert.NoError(t, err) 30 }) 31 32 t.Run("new_check_n", func(t *testing.T) { 33 err := panicWrap(func() { 34 NewFunc("check_n", []*Param{ 35 {Name: "name", Type: []ast.DType{ast.String}}, 36 {Name: "age", Type: []ast.DType{ast.Int}, 37 Optional: true, DefaultVal: func() (any, ast.DType) { return int64(0), ast.Int }}, 38 {Name: "tags", Type: []ast.DType{ast.String}, VariableP: true, 39 DefaultVal: VarPDefaultVal}, 40 }, nil, [2]*PLDoc{nil, nil}, nil) 41 }) 42 assert.NoError(t, err) 43 }) 44 45 t.Run("new_check_r", func(t *testing.T) { 46 err := panicWrap(func() { 47 NewFunc("check_r", []*Param{ 48 {Name: "name", Type: []ast.DType{ast.String}}, 49 {Name: "age", Type: []ast.DType{ast.Int}, 50 Optional: true, DefaultVal: func() (any, ast.DType) { return int64(0), ast.Int }}, 51 {Name: "fields", Type: []ast.DType{ast.String, ast.Int, ast.Bool, ast.Float, ast.List, ast.Map, ast.Nil}, VariableP: true, 52 DefaultVal: func() (any, ast.DType) { 53 return []any{ 54 float64(1), int64(1), true, "abc", []any{"a"}, map[string]any{"a": 1}, nil, 55 }, ast.List 56 }}, 57 }, nil, [2]*PLDoc{nil, nil}, nil) 58 }) 59 assert.NoError(t, err) 60 }) 61 62 t.Run("new_check_err_r", func(t *testing.T) { 63 err := panicWrap(func() { 64 NewFunc("check_err_r", []*Param{ 65 {Name: "name", Type: []ast.DType{ast.String}}, 66 {Name: "age", Type: []ast.DType{ast.Int}, 67 Optional: true, DefaultVal: func() (any, ast.DType) { return int64(0), ast.Int }}, 68 {Name: "fields", Type: []ast.DType{ast.String, ast.Int, ast.Bool, ast.Float, ast.List, ast.Map}, VariableP: true, 69 DefaultVal: func() (any, ast.DType) { 70 return []any{ 71 float64(1), int64(1), true, "abc", []any{"a"}, map[string]any{"a": 1}, 72 []byte{}, 73 }, ast.List 74 }}, 75 }, nil, [2]*PLDoc{nil, nil}, nil) 76 }) 77 assert.Error(t, err) 78 if err != nil { 79 assert.Equal(t, "parameter fields: default value data type not match", err.Error()) 80 } 81 }) 82 83 t.Run("new_check_err_0", func(t *testing.T) { 84 err := panicWrap(func() { 85 NewFunc("check_n", []*Param{ 86 {Name: "name", Type: []ast.DType{ast.String}, 87 Optional: true}, 88 {Name: "age", Type: []ast.DType{ast.Int}, 89 Optional: true, DefaultVal: func() (any, ast.DType) { return int64(0), ast.Int }}, 90 {Name: "tags", Type: []ast.DType{ast.String}, VariableP: true, 91 DefaultVal: VarPDefaultVal}, 92 }, nil, [2]*PLDoc{nil, nil}, nil) 93 }) 94 assert.Error(t, err) 95 if err != nil { 96 assert.Equal(t, "parameter name: optional parameter should have default value", err.Error()) 97 } 98 }) 99 100 t.Run("new_check_err_1", func(t *testing.T) { 101 err := panicWrap(func() { 102 NewFunc("check_n", []*Param{ 103 {Name: "name", Type: []ast.DType{ast.String}}, 104 {Name: "age", Type: []ast.DType{ast.Int}, 105 Optional: true, DefaultVal: func() (any, ast.DType) { return int64(0), ast.Int }}, 106 {Name: "tags", Type: []ast.DType{ast.String}, VariableP: true}, 107 {Name: "tags", Type: []ast.DType{ast.String}, VariableP: true}, 108 }, nil, [2]*PLDoc{nil, nil}, nil) 109 }) 110 assert.Error(t, err) 111 if err != nil { 112 assert.Equal(t, "parameter tags: variable parameter should be the last one", err.Error()) 113 } 114 }) 115 116 t.Run("new_check_err_1", func(t *testing.T) { 117 err := panicWrap(func() { 118 NewFunc("check_n", []*Param{ 119 {Name: "name", Type: []ast.DType{ast.String}}, 120 {Name: "age", Type: []ast.DType{ast.Int}, 121 Optional: true, DefaultVal: func() (any, ast.DType) { return int64(0), ast.Int }}, 122 {Name: "age", Type: []ast.DType{ast.String}}, 123 124 {Name: "tags", Type: []ast.DType{ast.String}, VariableP: true}, 125 }, nil, [2]*PLDoc{nil, nil}, nil) 126 }) 127 assert.Error(t, err) 128 if err != nil { 129 assert.Equal(t, "duplicate parameter name: age", err.Error()) 130 } 131 }) 132 133 t.Run("new_check_err_2", func(t *testing.T) { 134 err := panicWrap(func() { 135 NewFunc("check_n", []*Param{ 136 {Name: "name", Type: []ast.DType{ast.String}}, 137 {Name: "age", Type: []ast.DType{ast.Int}, 138 Optional: true, DefaultVal: func() (any, ast.DType) { return int64(0), ast.Int }}, 139 {Name: "opt", Type: []ast.DType{ast.String}}, 140 141 {Name: "tags", Type: []ast.DType{ast.String}, VariableP: true}, 142 }, nil, [2]*PLDoc{nil, nil}, nil) 143 }) 144 assert.Error(t, err) 145 if err != nil { 146 assert.Equal(t, "parameter opt: required parameter should not follow optional parameter", err.Error()) 147 } 148 }) 149 150 t.Run("new_check_err_3", func(t *testing.T) { 151 err := panicWrap(func() { 152 NewFunc("check_n", []*Param{ 153 {Name: "name", Type: []ast.DType{ast.String}}, 154 {Name: "age", Type: []ast.DType{ast.Int}, 155 Optional: true, DefaultVal: func() (any, ast.DType) { return 0, ast.Int }}, 156 {Name: "tags", Type: []ast.DType{ast.String}, VariableP: true}, 157 }, nil, [2]*PLDoc{nil, nil}, nil) 158 }) 159 assert.Error(t, err) 160 if err != nil { 161 assert.Equal(t, "parameter age: value type not match", err.Error()) 162 } 163 }) 164 165 t.Run("new_check_err_4", func(t *testing.T) { 166 err := panicWrap(func() { 167 NewFunc("check_n", []*Param{ 168 {Name: "name", Type: []ast.DType{ast.String}}, 169 {Name: "age", Type: []ast.DType{ast.Int}, 170 Optional: true, DefaultVal: func() (any, ast.DType) { return float64(1.1), ast.Float }}, 171 {Name: "tags", Type: []ast.DType{ast.String}, VariableP: true}, 172 }, nil, [2]*PLDoc{nil, nil}, nil) 173 }) 174 assert.Error(t, err) 175 if err != nil { 176 assert.Equal(t, "parameter age: default value data type not match", err.Error()) 177 } 178 }) 179 } 180 181 func TestRunFunc(t *testing.T) { 182 fnLi1 := []*Function{ 183 NewFunc("trigger", []*Param{ 184 {Name: "msg", Type: []ast.DType{ast.String}}, 185 {Name: "args", Type: []ast.DType{ast.Bool, ast.Int, ast.Float, ast.String, 186 ast.List, ast.Map, ast.Nil}, VariableP: true, DefaultVal: VarPDefaultVal}, 187 }, nil, [2]*PLDoc{nil, nil}, func(ctx *runtime.Task, funcExpr *ast.CallExpr, vals ...any) *errchain.PlError { 188 var msg string 189 switch vals[0].(type) { 190 case string: 191 msg = vals[0].(string) 192 default: 193 var pos token.LnColPos 194 if funcExpr.Param[0] != nil { 195 pos = funcExpr.Param[0].StartPos() 196 } else { 197 pos = funcExpr.NamePos 198 } 199 return runtime.NewRunError(ctx, "unexpected type", pos) 200 } 201 var varP []any 202 switch v := vals[1].(type) { 203 case []any: 204 varP = v 205 case nil: 206 default: 207 return runtime.NewRunError(ctx, "unexpected type", funcExpr.Param[1].StartPos()) 208 } 209 210 s := fmt.Sprintf(msg, varP...) 211 _ = addKey2PtWithVal(ctx.InData(), "msg", s, ast.String, ptinput.KindPtDefault) 212 213 return nil 214 }), 215 } 216 217 fnLi2 := []*Function{ 218 NewFunc("trigger2", []*Param{ 219 {Name: "msg", Type: []ast.DType{ast.String}, Optional: true, DefaultVal: func() (any, ast.DType) { 220 return "test", ast.String 221 }}, 222 {Name: "args", Type: []ast.DType{ast.Bool, ast.Int, ast.Float, ast.String, 223 ast.List, ast.Map, ast.Nil}, VariableP: true, DefaultVal: VarPDefaultVal}, 224 }, nil, [2]*PLDoc{nil, nil}, func(ctx *runtime.Task, funcExpr *ast.CallExpr, vals ...any) *errchain.PlError { 225 var msg string 226 switch vals[0].(type) { 227 case string: 228 msg = vals[0].(string) 229 default: 230 var pos token.LnColPos 231 if funcExpr.Param[0] != nil { 232 pos = funcExpr.Param[0].StartPos() 233 } else { 234 pos = funcExpr.NamePos 235 } 236 return runtime.NewRunError(ctx, "unexpected type", pos) 237 } 238 var varP []any 239 switch v := vals[1].(type) { 240 case []any: 241 varP = v 242 case nil: 243 default: 244 return runtime.NewRunError(ctx, "unexpected type", funcExpr.Param[1].StartPos()) 245 } 246 247 s := fmt.Sprintf(msg, varP...) 248 _ = addKey2PtWithVal(ctx.InData(), "msg", s, ast.String, ptinput.KindPtDefault) 249 250 return nil 251 }), 252 } 253 254 cases := []struct { 255 name string 256 pl, in string 257 outKey string 258 expected string 259 funcs []*Function 260 fail bool 261 }{ 262 { 263 name: "pos_varp", 264 pl: ` 265 a = 1 266 b = "aaa" 267 c = 1.1 268 x = trigger("%d %s %.3f", a, b, c) 269 `, 270 outKey: "msg", 271 funcs: fnLi1, 272 expected: "1 aaa 1.100", 273 fail: false, 274 }, 275 { 276 name: "err", 277 pl: ` 278 a = 1 279 b = "aaa" 280 c = 1.1 281 x = trigger() 282 `, 283 outKey: "msg", 284 funcs: fnLi1, 285 expected: "1 aaa 1.100", 286 fail: true, 287 }, 288 { 289 name: "pos_varp_1", 290 pl: ` 291 x = trigger("%d %v %v %v %.1f", 1, true, [], {}, 1.1) 292 `, 293 outKey: "msg", 294 funcs: fnLi1, 295 expected: "1 true [] map[] 1.1", 296 fail: false, 297 }, 298 { 299 name: "named", 300 pl: ` 301 a = 1 302 b = "aaa" 303 c = 1.1 304 x = trigger("%d %s %.3f", args = [a, b, c]) 305 `, 306 outKey: "msg", 307 funcs: fnLi1, 308 expected: "1 aaa 1.100", 309 fail: false, 310 }, 311 { 312 name: "named1", 313 pl: ` 314 a = 1 315 b = "aaa" 316 c = 1.1 317 x = trigger(args = [a, b, c], msg="%d %s %.3f") 318 `, 319 outKey: "msg", 320 funcs: fnLi1, 321 expected: "1 aaa 1.100", 322 fail: false, 323 }, 324 { 325 name: "named2", 326 pl: ` 327 x = trigger("abc") 328 `, 329 outKey: "msg", 330 funcs: fnLi1, 331 expected: "abc", 332 fail: false, 333 }, 334 335 { 336 name: "p3", 337 pl: ` 338 a = 1 339 b = "aaa" 340 c = 1.1 341 x = trigger("%d %s %.3f %v", args = [a, b, c, nil]) 342 `, 343 outKey: "msg", 344 funcs: fnLi1, 345 expected: "1 aaa 1.100 <nil>", 346 fail: false, 347 }, 348 { 349 name: "fn2", 350 pl: ` 351 x = trigger2() 352 `, 353 outKey: "msg", 354 funcs: fnLi2, 355 expected: "test", 356 fail: false, 357 }, 358 { 359 name: "fn2-1", 360 pl: ` 361 x = trigger2("%d", 1) 362 `, 363 outKey: "msg", 364 funcs: fnLi2, 365 expected: "1", 366 fail: false, 367 }, 368 { 369 name: "fn2-1-1", 370 pl: ` 371 x = trigger2("%d %s", 1, "a") 372 `, 373 outKey: "msg", 374 funcs: fnLi2, 375 expected: "1 a", 376 fail: false, 377 }, 378 { 379 name: "fn2-2", 380 pl: ` 381 x = trigger2(args=[1, "a"], msg="%d %s") 382 `, 383 outKey: "msg", 384 funcs: fnLi2, 385 expected: "1 a", 386 fail: false, 387 }, 388 { 389 name: "fn2-2-1", 390 pl: ` 391 x = trigger2( msg="%d %s", args=[1, "a"]) 392 `, 393 outKey: "msg", 394 funcs: fnLi2, 395 expected: "1 a", 396 fail: false, 397 }, 398 { 399 name: "fn2-2-2", 400 pl: ` 401 x = trigger2( msg="aaa") 402 `, 403 outKey: "msg", 404 funcs: fnLi2, 405 expected: "aaa", 406 fail: false, 407 }, 408 { 409 name: "fn-run-failed", 410 pl: ` 411 x = trigger2( msg="aaa", args = "") 412 `, 413 outKey: "msg", 414 funcs: fnLi2, 415 expected: "aaa", 416 fail: true, 417 }, 418 } 419 420 for idx, tc := range cases { 421 t.Run(tc.name, func(t *testing.T) { 422 script, err := parseScipt(tc.pl, tc.funcs) 423 if err != nil { 424 if tc.fail { 425 t.Logf("[%d]expect error: %s", idx, err) 426 } else { 427 t.Errorf("[%d] failed: %s", idx, err) 428 } 429 return 430 } 431 432 pt := ptinput.NewPlPoint( 433 point.Logging, "test", nil, map[string]any{"message": tc.in}, time.Now()) 434 435 errR := script.Run(pt, nil) 436 if errR != nil { 437 t.Fatal(errR.Error()) 438 } 439 440 if v, _, err := pt.Get(tc.outKey); err != nil { 441 if !tc.fail { 442 t.Errorf("[%d]expect error: %s", idx, err) 443 } 444 } else { 445 tu.Equals(t, tc.expected, v) 446 t.Logf("[%d] PASS", idx) 447 } 448 }) 449 } 450 } 451 452 func panicWrap(f func()) (err error) { 453 defer func() { 454 if e := recover(); e != nil { 455 err = fmt.Errorf("%v", e) 456 } 457 }() 458 f() 459 return 460 } 461 462 func parseScipt(s string, funcs []*Function) (*runtime.Script, error) { 463 fn := map[string]runtime.FuncCall{} 464 fnCheck := map[string]runtime.FuncCheck{} 465 466 for _, f := range funcs { 467 fn[f.Name] = f.Call 468 fnCheck[f.Name] = f.Check 469 } 470 471 ret1, ret2 := engine.ParseScript(map[string]string{ 472 "default.p": s, 473 }, 474 fn, fnCheck, 475 ) 476 477 if len(ret1) > 0 { 478 return ret1["default.p"], nil 479 } 480 481 if len(ret2) > 0 { 482 return nil, ret2["default.p"] 483 } 484 485 return nil, fmt.Errorf("parser func error") 486 }