ソースを参照

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

blotus 2 年 前
コミット
c77fe16943
2 ファイル変更165 行追加33 行削除
  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"
 	"strings"
 
 
 	"github.com/antonmedv/expr/parser"
 	"github.com/antonmedv/expr/parser"
+	"github.com/google/uuid"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	log "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
 Thus, we can debug expr filter by displaying all variables contents present in the filter
 */
 */
 type visitor struct {
 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) {
 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,
 		filter: filter,
 	}
 	}
 	if 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
 		return &ExprDebugger{}, nil
 	}
 	}
 	v.newVar = false
 	v.newVar = false
+	v.vars = make(map[string][]string)
 	tree, err := parser.Parse(filter)
 	tree, err := parser.Parse(filter)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	ast.Walk(&tree.Node, v)
 	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 {
 	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 {
 		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{
 		tmpExpression := &expression{
-			variable,
+			toBuild,
 			debugFilter,
 			debugFilter,
 		}
 		}
 		expressions = append(expressions, tmpExpression)
 		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
 // NewDebugger is the exported function that build the debuggers expressions
 func NewDebugger(filter string, exprEnv expr.Option) (*ExprDebugger, error) {
 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)
 	exprDebugger, err := visitor.Build(filter, exprEnv)
 	return exprDebugger, err
 	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])
+				}
+			}
+		})
+	}
+}