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  }