func TestEvaluate_Vars_ErrorMissingValue(t *testing.T) { script := ` var x duration ` scope := stateful.NewScope() if _, err := tick.Evaluate(script, scope, nil, false); err == nil { t.Fatal("expected error for missing var type") } if _, err := tick.Evaluate(script, scope, nil, true); err != nil { t.Fatal("uexpected error missing var should be ignored") } }
func TestEvaluate_ListVars(t *testing.T) { script := ` var strList = [ 'host', 'dc', 'service' ] f(strList) ` called := false f := func(a, b, c string) interface{} { called = true got := []string{a, b, c} exp := []string{"host", "dc", "service"} if !reflect.DeepEqual(got, exp) { t.Errorf("unexpected func args got %v exp %v", got, exp) } return nil } scope := stateful.NewScope() scope.Set("f", f) _, err := tick.Evaluate(script, scope, nil, false) if err != nil { t.Fatal(err) } if !called { t.Fatal("expected function to be called") } }
// Create a pipeline from a given script. // tick:ignore func CreatePipeline(script string, sourceEdge EdgeType, scope *tick.Scope, deadman DeadmanService) (*Pipeline, error) { p := &Pipeline{ deadman: deadman, } var src Node switch sourceEdge { case StreamEdge: src = newSourceStreamNode() scope.Set("stream", src) case BatchEdge: src = newSourceBatchNode() scope.Set("batch", src) default: return nil, fmt.Errorf("source edge type must be either Stream or Batch not %s", sourceEdge) } p.addSource(src) err := tick.Evaluate(script, scope) if err != nil { return nil, err } if sourceEdge == StreamEdge && deadman.Global() { src.(*SourceStreamNode).Deadman(deadman.Threshold(), deadman.Interval()) } return p, nil }
func ExampleEvaluate() { //Run a test that evaluates the DSL against the Process struct. script := ` //Name the parent parent.name('parent') // Spawn a first child var child1 = parent.spawn() // Name the first child child1.name('child1') //Spawn a grandchild and name it child1.spawn().name('grandchild') //Spawn a second child and name it parent.spawn().name('child2') ` scope := tick.NewScope() parent := &Process{} scope.Set("parent", parent) err := tick.Evaluate(script, scope) if err != nil { fmt.Println(err) } fmt.Println(parent) // Output: {"parent" [{"child1" [{"grandchild" []}]} {"child2" []}]} }
func TestEvaluate(t *testing.T) { //Run a test that evaluates the DSL against the above structures. script := ` var s2 = a|structB() .field1('f1') .field2(42) s2.field3(15m) s2|structC() .options('c', 21.5, 7h) .aggFunc(influxql.agg.sum) ` scope := stateful.NewScope() a := &structA{} scope.Set("a", a) i := &influxql{ Agg: &agg{ Sum: aggSum, }, } scope.Set("influxql", i) _, err := tick.Evaluate(script, scope, nil, false) if err != nil { t.Fatal(err) } s2I, err := scope.Get("s2") if err != nil { t.Fatal(err) } s2 := s2I.(*structB) exp := structB{ Field1: "f1", Field2: 42, Field3: time.Minute * 15, } s3 := *s2.c s2.c = nil if !reflect.DeepEqual(*s2, exp) { t.Errorf("unexpected s2 exp:%v got%v", exp, *s2) } c := structC{ field1: "c", field2: 21.5, field3: time.Hour * 7, } aggFunc := s3.AggFunc s3.AggFunc = nil if !reflect.DeepEqual(s3, c) { t.Errorf("unexpected s3 exp:%v got%v", c, s3) } if exp, got := []float64{10.0}, aggFunc([]float64{5, 5}); !reflect.DeepEqual(exp, got) { t.Errorf("unexpected s3.AggFunc exp:%v got%v", exp, got) } }
func TestEvaluate(t *testing.T) { assert := assert.New(t) //Run a test that evaluates the DSL against the above structures. script := ` var s2 = a.structB() .field1('f1') .field2(42) s2.field3(15m) s2.structC() .options('c', 21.5, 7h) .aggFunc(influxql.agg.sum) ` scope := tick.NewScope() a := &structA{} scope.Set("a", a) i := &influxql{ Agg: &agg{ Sum: aggSum, }, } scope.Set("influxql", i) err := tick.Evaluate(script, scope) if err != nil { t.Fatal(err) } s2I, err := scope.Get("s2") if err != nil { t.Fatal(err) } s2 := s2I.(*structB) assert.NotNil(s2) assert.Equal("f1", s2.Field1) assert.Equal(int64(42), s2.Field2) assert.Equal(time.Minute*15, s2.Field3) s3 := s2.c if assert.NotNil(s3) { assert.Equal("c", s3.field1) assert.Equal(21.5, s3.field2) assert.Equal(time.Hour*7, s3.field3) if assert.NotNil(s3.AggFunc) { assert.Equal([]float64{10.0}, s3.AggFunc([]float64{5, 5})) } } }
func TestEvaluate_DynamicMethod(t *testing.T) { script := `var x = a.dynamicMethod(1,'str', 10s).sad(FALSE)` scope := tick.NewScope() a := &structA{} scope.Set("a", a) dm := func(self interface{}, args ...interface{}) (interface{}, error) { a, ok := self.(*structA) if !ok { return nil, fmt.Errorf("cannot call dynamicMethod on %T", self) } o := &orphan{ parent: a, Sad: true, args: args, } return o, nil } scope.SetDynamicMethod("dynamicMethod", dm) err := tick.Evaluate(script, scope) if err != nil { t.Fatal(err) } xI, err := scope.Get("x") if err != nil { t.Fatal(err) } x, ok := xI.(*orphan) if !ok { t.Fatalf("expected x to be an *orphan, got %T", xI) } if x.Sad { t.Errorf("expected x to not be sad") } if got, exp := len(x.args), 3; exp != got { t.Fatalf("unexpected number of args: got %d exp %d", got, exp) } if got, exp := x.args[0], int64(1); exp != got { t.Errorf("unexpected x.args[0]: got %v exp %d", got, exp) } if got, exp := x.args[1], "str"; exp != got { t.Errorf("unexpected x.args[1]: got %v exp %s", got, exp) } if got, exp := x.args[2], time.Second*10; exp != got { t.Errorf("unexpected x.args[1]: got %v exp %v", got, exp) } }
// Test that using the wrong chain operator fails func TestStrictEvaluate(t *testing.T) { script := ` var s2 = a.structB() .field1('f1') .field2(42) ` scope := stateful.NewScope() a := &structA{} scope.Set("a", a) _, err := tick.Evaluate(script, scope, nil, false) if err == nil { t.Fatal("expected error from Evaluate") } }
func TestEvaluate_Vars_TypeConversion(t *testing.T) { script := ` var d = 5m var messageDuration = '{{ .ID }} has crossed threshold: ' + string(d) var x = 5 var messageInt = '{{ .ID }} has crossed threshold: ' + string(x) var y = 1.0 / 3.0 var messageFloat = '{{ .ID }} has crossed threshold: ' + string(y) var z = FALSE var messageBool = '{{ .ID }} is: ' + string(z) ` scope := stateful.NewScope() vars, err := tick.Evaluate(script, scope, nil, true) if err != nil { t.Fatal(err) } exp := map[string]interface{}{ "d": 5 * time.Minute, "messageDuration": "{{ .ID }} has crossed threshold: 5m", "x": int64(5), "messageInt": "{{ .ID }} has crossed threshold: 5", "y": 1.0 / 3.0, "messageFloat": "{{ .ID }} has crossed threshold: 0.3333333333333333", "z": false, "messageBool": "{{ .ID }} is: false", } for name, value := range exp { if got, err := scope.Get(name); err != nil { t.Errorf("unexpected error for %s: %s", name, err) } else if !reflect.DeepEqual(got, value) { t.Errorf("unexpected %s value: got %v exp %v", name, got, value) } if got, exp := vars[name].Value, value; !reflect.DeepEqual(got, exp) { t.Errorf("unexpected %s vars value: got %v exp %v", name, got, value) } if got, exp := vars[name].Type, ast.TypeOf(value); got != exp { t.Errorf("unexpected %s vars type: got %v exp %v", name, got, exp) } } }
func TestEvaluate_Vars_ErrorWrongType(t *testing.T) { script := ` var x = 3m ` definedVars := map[string]tick.Var{ "x": { Value: "5m", Type: ast.TString, }, } scope := stateful.NewScope() _, err := tick.Evaluate(script, scope, definedVars, false) if err == nil { t.Fatal("expected error for invalid var type") } }
func createPipelineAndVars( script string, sourceEdge EdgeType, scope *stateful.Scope, deadman DeadmanService, predefinedVars map[string]tick.Var, ignoreMissingVars bool, ) (*Pipeline, map[string]tick.Var, error) { p := &Pipeline{ deadman: deadman, } var src Node switch sourceEdge { case StreamEdge: src = newStreamNode() scope.Set("stream", src) case BatchEdge: src = newBatchNode() scope.Set("batch", src) default: return nil, nil, fmt.Errorf("source edge type must be either Stream or Batch not %s", sourceEdge) } p.addSource(src) vars, err := tick.Evaluate(script, scope, predefinedVars, ignoreMissingVars) if err != nil { return nil, nil, err } if deadman.Global() { switch s := src.(type) { case *StreamNode: s.Deadman(deadman.Threshold(), deadman.Interval()) case *BatchNode: s.Deadman(deadman.Threshold(), deadman.Interval()) default: return nil, nil, fmt.Errorf("source edge type must be either Stream or Batch not %s", sourceEdge) } } if err = p.Walk( func(n Node) error { return n.validate() }); err != nil { return nil, nil, err } return p, vars, nil }
func TestEvaluate_StringQuotesError(t *testing.T) { script := ` f("asdf") ` f := func(got string) (interface{}, error) { return nil, errors.New("function should not be called") } scope := stateful.NewScope() scope.Set("f", f) _, err := tick.Evaluate(script, scope, nil, false) if err == nil { t.Fatal("expected error from invalid string call") } else if got, exp := err.Error(), "line 2 char 1: cannot assign *ast.ReferenceNode to type string, did you use double quotes instead of single quotes?"; got != exp { t.Errorf("unexpected error string: \ngot\n%s\nexp\n%s\n", got, exp) } }
func TestEvaluate_Func_Expression_Parameter(t *testing.T) { script := ` f('asdf' + string(10) + 'qwerty') ` f := func(got string) (interface{}, error) { if exp := "asdf10qwerty"; got != exp { t.Errorf("unexpected arg to function: got %s exp %s", got, exp) } return nil, nil } scope := stateful.NewScope() scope.Set("f", f) if _, err := tick.Evaluate(script, scope, nil, false); err != nil { t.Fatal(err) } }
// Create a pipeline from a given script. // tick:ignore func CreatePipeline(script string, sourceEdge EdgeType, scope *tick.Scope) (*Pipeline, error) { var src Node switch sourceEdge { case StreamEdge: src = newStreamNode() scope.Set("stream", src) case BatchEdge: src = newSourceBatchNode() scope.Set("batch", src) default: return nil, fmt.Errorf("source edge type must be either Stream or Batch not %s", sourceEdge) } err := tick.Evaluate(script, scope) if err != nil { return nil, err } p := &Pipeline{Source: src} p.Walk(p.setID) return p, nil }
func TestEvaluate_Vars_Immutable(t *testing.T) { script := ` var x = 3m var x = 2m ` scope := stateful.NewScope() _, err := tick.Evaluate(script, scope, nil, false) if exp, got := "attempted to redefine x, vars are immutable", err.Error(); exp != got { t.Errorf("unexpected error message: got %s exp %s", got, exp) } x, err := scope.Get("x") if err != nil { t.Fatal(err) } if value, ok := x.(time.Duration); ok { if exp, got := time.Minute*3, value; exp != got { t.Errorf("unexpected x value: exp %v got %v", exp, got) } } else { t.Errorf("unexpected x value type: exp time.Duration got %T", x) } }
func TestEvaluate_Vars(t *testing.T) { script := ` var x = 3m var y = -x var n = TRUE var m = !n ` scope := tick.NewScope() err := tick.Evaluate(script, scope) if err != nil { t.Fatal(err) } x, err := scope.Get("x") if err != nil { t.Fatal(err) } if value, ok := x.(time.Duration); ok { if exp, got := time.Minute*3, value; exp != got { t.Errorf("unexpected x value: exp %v got %v", exp, got) } } else { t.Errorf("unexpected x value type: exp time.Duration got %T", x) } y, err := scope.Get("y") if err != nil { t.Fatal(err) } if value, ok := y.(time.Duration); ok { if exp, got := time.Minute*-3, value; exp != got { t.Errorf("unexpected y value: exp %v got %v", exp, got) } } else { t.Errorf("unexpected y value type: exp time.Duration got %T", x) } n, err := scope.Get("n") if err != nil { t.Fatal(err) } if value, ok := n.(bool); ok { if exp, got := true, value; exp != got { t.Errorf("unexpected n value: exp %v got %v", exp, got) } } else { t.Errorf("unexpected m value type: exp bool got %T", x) } m, err := scope.Get("m") if err != nil { t.Fatal(err) } if value, ok := m.(bool); ok { if exp, got := false, value; exp != got { t.Errorf("unexpected m value: exp %v got %v", exp, got) } } else { t.Errorf("unexpected m value type: exp bool got %T", x) } }
func TestValidateTemplate_Vars(t *testing.T) { script := ` var x = 3m var y = -x var n = TRUE var m = !n var z = x + y var a string ` scope := stateful.NewScope() vars, err := tick.Evaluate(script, scope, nil, true) if err != nil { t.Fatal(err) } x, err := scope.Get("x") if err != nil { t.Fatal(err) } if value, ok := x.(time.Duration); ok { if exp, got := time.Minute*3, value; exp != got { t.Errorf("unexpected x value: exp %v got %v", exp, got) } } else { t.Errorf("unexpected x value type: exp time.Duration got %T", x) } y, err := scope.Get("y") if err != nil { t.Fatal(err) } if value, ok := y.(time.Duration); ok { if exp, got := time.Minute*-3, value; exp != got { t.Errorf("unexpected y value: exp %v got %v", exp, got) } } else { t.Errorf("unexpected y value type: exp time.Duration got %T", y) } n, err := scope.Get("n") if err != nil { t.Fatal(err) } if value, ok := n.(bool); ok { if exp, got := true, value; exp != got { t.Errorf("unexpected n value: exp %v got %v", exp, got) } } else { t.Errorf("unexpected m value type: exp bool got %T", x) } m, err := scope.Get("m") if err != nil { t.Fatal(err) } if value, ok := m.(bool); ok { if exp, got := false, value; exp != got { t.Errorf("unexpected m value: exp %v got %v", exp, got) } } else { t.Errorf("unexpected m value type: exp bool got %T", x) } z, err := scope.Get("z") if err != nil { t.Fatal(err) } if value, ok := z.(time.Duration); ok { if exp, got := time.Duration(0), value; exp != got { t.Errorf("unexpected z value: exp %v got %v", exp, got) } } else { t.Errorf("unexpected z value type: exp time.Duration got %T", z) } expVars := map[string]tick.Var{ "a": { Value: nil, Type: ast.TString, }, "x": { Value: 3 * time.Minute, Type: ast.TDuration, }, "y": { Value: -3 * time.Minute, Type: ast.TDuration, }, "z": { Value: time.Duration(0), Type: ast.TDuration, }, "n": { Value: true, Type: ast.TBool, }, "m": { Value: false, Type: ast.TBool, }, } if !reflect.DeepEqual(expVars, vars) { t.Errorf("unexpected vars: got %v exp %v", vars, expVars) } }
func TestEvaluate_Vars_PreDefined(t *testing.T) { script := ` var str = 'this is a string' var strZero string var strList = [ 'string list', str, *] var strListZero list var star = * var starZero star var int = 8 var intZero int var float = 3.14 var floatZero float var duration = 5m var durationZero duration var regex = /.*/ var regexZero regex var boolean = TRUE var booleanZero bool var lambda = lambda: "value" > 0 var lambdaZero lambda ` lambda := &ast.LambdaNode{ Expression: &ast.BinaryNode{ Operator: ast.TokenGreater, Left: &ast.ReferenceNode{ Reference: "value", }, Right: &ast.NumberNode{ IsInt: true, Int64: 0, }, }, } definedVars := map[string]tick.Var{ "str": { Value: "asdf", Type: ast.TString, }, "strZero": { Value: "qwerty", Type: ast.TString, }, "strList": { Value: []tick.Var{ { Value: "a", Type: ast.TString, }, { Value: "b", Type: ast.TString, }, { Value: "c", Type: ast.TString, }, }, Type: ast.TList, }, "strListZero": { Value: []tick.Var{ { Value: &ast.StarNode{}, Type: ast.TStar, }, }, Type: ast.TList, }, "star": { Value: &ast.StarNode{}, Type: ast.TStar, }, "starZero": { Value: &ast.StarNode{}, Type: ast.TStar, }, "int": { Value: int64(42), Type: ast.TInt, }, "intZero": { Value: int64(6 * 9), Type: ast.TInt, }, "float": { Value: float64(42), Type: ast.TFloat, }, "floatZero": { Value: float64(6 * 9), Type: ast.TFloat, }, "duration": { Value: 5 * time.Hour, Type: ast.TDuration, }, "durationZero": { Value: time.Minute, Type: ast.TDuration, }, "regex": { Value: regexp.MustCompile(`^asdf.*qwerty$`), Type: ast.TRegex, }, "regexZero": { Value: regexp.MustCompile(`^.*$`), Type: ast.TRegex, }, "boolean": { Value: false, Type: ast.TBool, }, "booleanZero": { Value: true, Type: ast.TBool, }, "lambda": { Value: lambda, Type: ast.TLambda, }, "lambdaZero": { Value: lambda, Type: ast.TLambda, }, } scope := stateful.NewScope() _, err := tick.Evaluate(script, scope, definedVars, false) if err != nil { t.Fatal(err) } expScope := map[string]interface{}{ "str": "asdf", "strZero": "qwerty", "strList": []interface{}{"a", "b", "c"}, "strListZero": []interface{}{&ast.StarNode{}}, "star": &ast.StarNode{}, "starZero": &ast.StarNode{}, "int": int64(42), "intZero": int64(6 * 9), "float": float64(42), "floatZero": float64(6 * 9), "duration": 5 * time.Hour, "durationZero": time.Minute, "regex": regexp.MustCompile(`^asdf.*qwerty$`), "regexZero": regexp.MustCompile(`^.*$`), "boolean": false, "booleanZero": true, "lambda": lambda, "lambdaZero": lambda, } for name, value := range expScope { if got, err := scope.Get(name); err != nil { t.Errorf("unexpected error for %s: %s", name, err) } else if !equal(got, value) { t.Errorf("unexpected %s value: \ngot\n%v\ngot type: %T\nexp\n%v\nexp type: %T", name, got, got, value, value) } } }
func TestEvaluate_Vars_AllTypes(t *testing.T) { script := ` var str = 'this is a string' var strA = str + ' concat' var strZero string var strCopy = str var strList = [ 'string list', str, strA, strCopy, *] var strListZero list var strListCopy = strList var star = * var starZero star var starCopy = star var integer = 42 var intergerMath = (integer * 2) - ( 6 * 9 ) + (36 / 3) var intZero int var integerCopy= integer var float = 3.14 var tastyPie = float * 42.0 var floatZero float var floatCopy = float var intFloat = int(sqrt(float)) * 3 var duration = 5m var later = duration + 1m var durationZero duration var durationCopy = duration var regex = /.*/ var regexZero regex var regexCopy = regex var boolean = TRUE var f = boolean AND FALSE var booleanZero bool var booleanCopy = boolean var lambda = lambda: "value" > 0 var l = lambda: lambda OR "value" < -100 var lambdaZero lambda var lambdaCopy = lambda ` scope := stateful.NewScope() vars, err := tick.Evaluate(script, scope, nil, true) if err != nil { t.Fatal(err) } expLambda := &ast.LambdaNode{ Expression: &ast.BinaryNode{ Operator: ast.TokenGreater, Left: &ast.ReferenceNode{ Reference: "value", }, Right: &ast.NumberNode{ IsInt: true, Int64: 0, }, }, } expNestedLambda := &ast.LambdaNode{ Expression: &ast.BinaryNode{ Operator: ast.TokenOr, Left: expLambda, Right: &ast.BinaryNode{ Operator: ast.TokenLess, Left: &ast.ReferenceNode{ Reference: "value", }, Right: &ast.UnaryNode{ Operator: ast.TokenMinus, Node: &ast.NumberNode{ IsInt: true, Int64: 100, }, }, }, }, } expStrList := []interface{}{ "string list", "this is a string", "this is a string concat", "this is a string", &ast.StarNode{}, } expStrVarList := tick.Var{ Value: []tick.Var{ {Value: "string list", Type: ast.TString}, {Value: "this is a string", Type: ast.TString}, {Value: "this is a string concat", Type: ast.TString}, {Value: "this is a string", Type: ast.TString}, {Value: &ast.StarNode{}, Type: ast.TStar}, }, Type: ast.TList, } expScope := map[string]interface{}{ "str": "this is a string", "strCopy": "this is a string", "strA": "this is a string concat", "strZero": "", "strList": expStrList, "strListCopy": expStrList, "strListZero": []interface{}(nil), "star": &ast.StarNode{}, "starZero": (*ast.StarNode)(nil), "starCopy": &ast.StarNode{}, "integer": int64(42), "integerCopy": int64(42), "intergerMath": int64(42), "intZero": int64(0), "float": 3.14, "floatCopy": 3.14, "tastyPie": 3.14 * 42.0, "intFloat": int64(3), "floatZero": float64(0), "duration": 5 * time.Minute, "durationCopy": 5 * time.Minute, "later": 6 * time.Minute, "durationZero": time.Duration(0), "regex": regexp.MustCompile(".*"), "regexCopy": regexp.MustCompile(".*"), "regexZero": (*regexp.Regexp)(nil), "boolean": true, "booleanCopy": true, "f": false, "booleanZero": false, "lambda": expLambda, "lambdaCopy": expLambda, "l": expNestedLambda, "lambdaZero": (*ast.LambdaNode)(nil), } expVars := map[string]tick.Var{ "strList": expStrVarList, "strListCopy": expStrVarList, } for name, value := range expScope { if got, err := scope.Get(name); err != nil { t.Errorf("unexpected error for %s: %s", name, err) } else if !equal(got, value) { t.Errorf("unexpected %s value: \ngot\n%v\ngot type: %T\nexp\n%v\nexp type: %T", name, got, got, value, value) } expVar, ok := expVars[name] if !ok { typ := ast.TypeOf(value) if strings.Contains(name, "Zero") { value = interface{}(nil) } expVar = tick.Var{ Type: typ, Value: value, } } if got, exp := vars[name].Value, expVar.Value; !equal(exp, got) { t.Errorf("unexpected %s var value:\ngot\n%v\nexp\n%v\n", name, got, exp) } if got, exp := vars[name].Type, expVar.Type; got != exp { t.Errorf("unexpected %s var type: got %v exp %v", name, got, exp) } } }