Kaynağa Gözat

actually fix expr-debugger to work with the new version (#2124)

blotus 2 yıl önce
ebeveyn
işleme
c77fe16943
2 değiştirilmiş dosya ile 165 ekleme ve 33 silme
  1. 65 33
      pkg/exprhelpers/visitor.go
  2. 100 0
      pkg/exprhelpers/visitor_test.go

+ 65 - 33
pkg/exprhelpers/visitor.go

@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/antonmedv/expr/parser"
+	"github.com/google/uuid"
 	"github.com/sirupsen/logrus"
 	log "github.com/sirupsen/logrus"
 
@@ -19,27 +20,61 @@ Visitor is used to reconstruct variables with its property called in an expr fil
 Thus, we can debug expr filter by displaying all variables contents present in the filter
 */
 type visitor struct {
-	newVar     bool
-	currentID  string
-	properties []string
-	vars       []string
+	newVar    bool
+	currentId string
+	vars      map[string][]string
+	logger    *log.Entry
 }
 
 func (v *visitor) Visit(node *ast.Node) {
-	if n, ok := (*node).(*ast.IdentifierNode); ok {
-		if !v.newVar {
-			v.newVar = true
-			v.currentID = n.Value
-		} else {
-			fullVar := fmt.Sprintf("%s.%s", v.currentID, strings.Join(v.properties, "."))
-			v.vars = append(v.vars, fullVar)
-			v.properties = []string{}
-			v.currentID = n.Value
-		}
-	} else if n2, ok := (*node).(*ast.MemberNode); ok {
-		if ns, ok := (n2.Property).(*ast.StringNode); ok {
-			v.properties = append(v.properties, ns.Value)
+	switch n := (*node).(type) {
+	case *ast.IdentifierNode:
+		v.newVar = true
+		uid, _ := uuid.NewUUID()
+		v.currentId = uid.String()
+		v.vars[v.currentId] = []string{n.Value}
+	case *ast.MemberNode:
+		if n2, ok := n.Property.(*ast.StringNode); ok {
+			v.vars[v.currentId] = append(v.vars[v.currentId], n2.Value)
 		}
+	case *ast.StringNode: //Don't reset here, as any attribute of a member node is a string node (in evt.X, evt is member node, X is string node)
+	default:
+		v.newVar = false
+		v.currentId = ""
+		/*case *ast.IntegerNode:
+			v.logger.Infof("integer node found: %+v", n)
+		case *ast.FloatNode:
+			v.logger.Infof("float node found: %+v", n)
+		case *ast.BoolNode:
+			v.logger.Infof("boolean node found: %+v", n)
+		case *ast.ArrayNode:
+			v.logger.Infof("array node found: %+v", n)
+		case *ast.ConstantNode:
+			v.logger.Infof("constant node found: %+v", n)
+		case *ast.UnaryNode:
+			v.logger.Infof("unary node found: %+v", n)
+		case *ast.BinaryNode:
+			v.logger.Infof("binary node found: %+v", n)
+		case *ast.CallNode:
+			v.logger.Infof("call node found: %+v", n)
+		case *ast.BuiltinNode:
+			v.logger.Infof("builtin node found: %+v", n)
+		case *ast.ConditionalNode:
+			v.logger.Infof("conditional node found: %+v", n)
+		case *ast.ChainNode:
+			v.logger.Infof("chain node found: %+v", n)
+		case *ast.PairNode:
+			v.logger.Infof("pair node found: %+v", n)
+		case *ast.MapNode:
+			v.logger.Infof("map node found: %+v", n)
+		case *ast.SliceNode:
+			v.logger.Infof("slice node found: %+v", n)
+		case *ast.ClosureNode:
+			v.logger.Infof("closure node found: %+v", n)
+		case *ast.PointerNode:
+			v.logger.Infof("pointer node found: %+v", n)
+		default:
+			v.logger.Infof("unknown node found: %+v | type: %T", n, n)*/
 	}
 }
 
@@ -52,34 +87,30 @@ func (v *visitor) Build(filter string, exprEnv expr.Option) (*ExprDebugger, erro
 		filter: filter,
 	}
 	if filter == "" {
-		log.Debugf("unable to create expr debugger with empty filter")
+		v.logger.Debugf("unable to create expr debugger with empty filter")
 		return &ExprDebugger{}, nil
 	}
 	v.newVar = false
+	v.vars = make(map[string][]string)
 	tree, err := parser.Parse(filter)
 	if err != nil {
 		return nil, err
 	}
 	ast.Walk(&tree.Node, v)
-	if v.currentID != "" && len(v.properties) > 0 { // if its a variable with property (eg. evt.Line.Labels)
-		fullVar := fmt.Sprintf("%s.%s", v.currentID, strings.Join(v.properties, "."))
-		v.vars = append(v.vars, fullVar)
-	} else if v.currentID != "" && len(v.properties) == 0 { // if it's a variable without property
-		fullVar := v.currentID
-		v.vars = append(v.vars, fullVar)
-	} else {
-		log.Debugf("no variable in filter : '%s'", filter)
-	}
-	v.properties = []string{}
-	v.currentID = ""
+	log.Debugf("vars: %+v", v.vars)
 
 	for _, variable := range v.vars {
-		debugFilter, err := expr.Compile(variable, exprEnv)
+		if variable[0] != "evt" {
+			continue
+		}
+		toBuild := strings.Join(variable, ".")
+		v.logger.Debugf("compiling expression '%s'", toBuild)
+		debugFilter, err := expr.Compile(toBuild, exprEnv)
 		if err != nil {
-			return ret, fmt.Errorf("compilation of variable '%s' failed: %v", variable, err)
+			return ret, fmt.Errorf("compilation of variable '%s' failed: %v", toBuild, err)
 		}
 		tmpExpression := &expression{
-			variable,
+			toBuild,
 			debugFilter,
 		}
 		expressions = append(expressions, tmpExpression)
@@ -123,7 +154,8 @@ func (e *ExprDebugger) Run(logger *logrus.Entry, filterResult bool, exprEnv map[
 
 // NewDebugger is the exported function that build the debuggers expressions
 func NewDebugger(filter string, exprEnv expr.Option) (*ExprDebugger, error) {
-	visitor := &visitor{}
+	logger := log.WithField("component", "expr-debugger")
+	visitor := &visitor{logger: logger}
 	exprDebugger, err := visitor.Build(filter, exprEnv)
 	return exprDebugger, err
 }

+ 100 - 0
pkg/exprhelpers/visitor_test.go

@@ -0,0 +1,100 @@
+package exprhelpers
+
+import (
+	"sort"
+	"testing"
+
+	"github.com/antonmedv/expr"
+	log "github.com/sirupsen/logrus"
+)
+
+func TestVisitorBuild(t *testing.T) {
+	tests := []struct {
+		name string
+		expr string
+		want []string
+		env  map[string]interface{}
+	}{
+		{
+			name: "simple",
+			expr: "evt.X",
+			want: []string{"evt.X"},
+			env: map[string]interface{}{
+				"evt": map[string]interface{}{
+					"X": 1,
+				},
+			},
+		},
+		{
+			name: "two vars",
+			expr: "evt.X && evt.Y",
+			want: []string{"evt.X", "evt.Y"},
+			env: map[string]interface{}{
+				"evt": map[string]interface{}{
+					"X": 1,
+					"Y": 2,
+				},
+			},
+		},
+		{
+			name: "in",
+			expr: "evt.X in [1,2,3]",
+			want: []string{"evt.X"},
+			env: map[string]interface{}{
+				"evt": map[string]interface{}{
+					"X": 1,
+				},
+			},
+		},
+		{
+			name: "in complex",
+			expr: "evt.X in [1,2,3] && evt.Y in [1,2,3] || evt.Z in [1,2,3]",
+			want: []string{"evt.X", "evt.Y", "evt.Z"},
+			env: map[string]interface{}{
+				"evt": map[string]interface{}{
+					"X": 1,
+					"Y": 2,
+					"Z": 3,
+				},
+			},
+		},
+		{
+			name: "function call",
+			expr: "Foo(evt.X, 'ads')",
+			want: []string{"evt.X"},
+			env: map[string]interface{}{
+				"evt": map[string]interface{}{
+					"X": 1,
+				},
+				"Foo": func(x int, y string) int {
+					return x
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			v := &visitor{logger: log.NewEntry(log.New())}
+			ret, err := v.Build(tt.expr, expr.Env(tt.env))
+			if err != nil {
+				t.Errorf("visitor.Build() error = %v", err)
+				return
+			}
+			if len(ret.expression) != len(tt.want) {
+				t.Errorf("visitor.Build() = %v, want %v", ret.expression, tt.want)
+			}
+			//Sort both slices as the order is not guaranteed ??
+			sort.Slice(tt.want, func(i, j int) bool {
+				return tt.want[i] < tt.want[j]
+			})
+			sort.Slice(ret.expression, func(i, j int) bool {
+				return ret.expression[i].Str < ret.expression[j].Str
+			})
+			for idx, v := range ret.expression {
+				if v.Str != tt.want[idx] {
+					t.Errorf("visitor.Build() = %v, want %v", v.Str, tt.want[idx])
+				}
+			}
+		})
+	}
+}