func Test_TypeOf(t *testing.T) { type expectation struct { value interface{} valueType ast.ValueType } expectations := []expectation{ {value: float64(0), valueType: ast.TFloat}, {value: int64(0), valueType: ast.TInt}, {value: "Kapacitor Rulz", valueType: ast.TString}, {value: true, valueType: ast.TBool}, {value: regexp.MustCompile("\\d"), valueType: ast.TRegex}, {value: time.Duration(5), valueType: ast.TDuration}, {value: time.Time{}, valueType: ast.TTime}, {value: t, valueType: ast.InvalidType}, } for _, expect := range expectations { result := ast.TypeOf(expect.value) if result != expect.valueType { t.Errorf("Got unexpected result for valueTypeOf(%T):\ngot: %s\nexpected: %s", expect.value, result, expect.valueType) } } }
func (n *EvalReferenceNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ast.ValueType, error) { value, err := n.getReferenceValue(scope.(*Scope), executionState) if err != nil { return ast.InvalidType, err } return ast.TypeOf(value), nil }
func (n *EvalFunctionNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ast.ValueType, error) { // PERF: today we are evaluating the function, it will be much faster if will type info the function it self result, err := n.callFunction(scope.(*Scope), executionState) if err != nil { return ast.InvalidType, err } // We can't cache here the result (although it's very tempting ;)) // because can't trust function to return always the same consistent type return ast.TypeOf(result), nil }
func (n *EvalReferenceNode) EvalFloat(scope *Scope, executionState ExecutionState) (float64, error) { refValue, err := n.getReferenceValue(scope, executionState) if err != nil { return float64(0), err } if float64Value, isFloat64 := refValue.(float64); isFloat64 { return float64Value, nil } return float64(0), ErrTypeGuardFailed{RequestedType: ast.TFloat, ActualType: ast.TypeOf(refValue)} }
func (n *EvalReferenceNode) EvalString(scope *Scope, executionState ExecutionState) (string, error) { refValue, err := n.getReferenceValue(scope, executionState) if err != nil { return "", err } if stringValue, isString := refValue.(string); isString { return stringValue, nil } return "", ErrTypeGuardFailed{RequestedType: ast.TString, ActualType: ast.TypeOf(refValue)} }
func (n *EvalReferenceNode) EvalDuration(scope *Scope, executionState ExecutionState) (time.Duration, error) { refValue, err := n.getReferenceValue(scope, executionState) if err != nil { return 0, err } if durValue, isDuration := refValue.(time.Duration); isDuration { return durValue, nil } return 0, ErrTypeGuardFailed{RequestedType: ast.TDuration, ActualType: ast.TypeOf(refValue)} }
func (n *EvalReferenceNode) EvalTime(scope *Scope, executionState ExecutionState) (time.Time, error) { refValue, err := n.getReferenceValue(scope, executionState) if err != nil { return time.Time{}, err } if timeValue, isTime := refValue.(time.Time); isTime { return timeValue, nil } return time.Time{}, ErrTypeGuardFailed{RequestedType: ast.TTime, ActualType: ast.TypeOf(refValue)} }
func (n *EvalReferenceNode) EvalRegex(scope *Scope, executionState ExecutionState) (*regexp.Regexp, error) { refValue, err := n.getReferenceValue(scope, executionState) if err != nil { return nil, err } if regexValue, isRegex := refValue.(*regexp.Regexp); isRegex { return regexValue, nil } return nil, ErrTypeGuardFailed{RequestedType: ast.TRegex, ActualType: ast.TypeOf(refValue)} }
func (n *EvalReferenceNode) EvalBool(scope *Scope, executionState ExecutionState) (bool, error) { refValue, err := n.getReferenceValue(scope, executionState) if err != nil { return false, err } if boolValue, isBool := refValue.(bool); isBool { return boolValue, nil } return false, ErrTypeGuardFailed{RequestedType: ast.TBool, ActualType: ast.TypeOf(refValue)} }
func (n *EvalFunctionNode) EvalInt(scope *Scope, executionState ExecutionState) (int64, error) { refValue, err := n.callFunction(scope, executionState) if err != nil { return int64(0), err } if int64Value, isInt64 := refValue.(int64); isInt64 { return int64Value, nil } return int64(0), ErrTypeGuardFailed{RequestedType: ast.TInt, ActualType: ast.TypeOf(refValue)} }
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 evalDeclaration(node *ast.DeclarationNode, scope *stateful.Scope, stck *stack, predefinedVars, defaultVars map[string]Var) error { name := node.Left.Ident if v, _ := scope.Get(name); v != nil { return fmt.Errorf("attempted to redefine %s, vars are immutable", name) } value := stck.Pop() if i, ok := value.(*ast.IdentifierNode); ok { // Resolve identifier v, err := scope.Get(i.Ident) if err != nil { return err } value = v } actualType := ast.TypeOf(value) // Populate set of default vars if actualType != ast.InvalidType { desc := "" if node.Comment != nil { desc = node.Comment.CommentString() } v, err := convertValueToVar(value, actualType, desc) if err != nil { return err } defaultVars[name] = v } // Populate scope, first check for predefined var if predefinedValue, ok := predefinedVars[name]; ok { if predefinedValue.Type != actualType { return fmt.Errorf("invalid type supplied for %s, got %v exp %v", name, predefinedValue.Type, actualType) } v, err := convertVarToValue(Var{Value: predefinedValue.Value, Type: actualType}) if err != nil { return err } value = v } scope.Set(name, value) return nil }
func convertValueToVar(value interface{}, typ ast.ValueType, desc string) (Var, error) { varValue := value if typ == ast.TList { values, ok := value.([]interface{}) if !ok { return Var{}, fmt.Errorf("var has type list but value is type %T", value) } list := make([]Var, len(values)) for i := range values { typ := ast.TypeOf(values[i]) list[i] = Var{ Type: typ, Value: values[i], } } varValue = list } return Var{ Type: typ, Value: varValue, Description: desc, }, nil }
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) } } }