...

Source file src/go.einride.tech/aip/filtering/parser_test.go

Documentation: go.einride.tech/aip/filtering

     1  package filtering
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
     8  	"google.golang.org/protobuf/testing/protocmp"
     9  	"gotest.tools/v3/assert"
    10  )
    11  
    12  func TestParser(t *testing.T) {
    13  	t.Parallel()
    14  	for _, tt := range []struct {
    15  		filter        string
    16  		expected      *expr.Expr
    17  		errorContains string
    18  	}{
    19  		{
    20  			filter:   "New York Giants",
    21  			expected: Sequence(Text("New"), Text("York"), Text("Giants")),
    22  		},
    23  
    24  		{
    25  			filter:   "New York Giants OR Yankees",
    26  			expected: Sequence(Text("New"), Text("York"), Or(Text("Giants"), Text("Yankees"))),
    27  		},
    28  
    29  		{
    30  			filter:   "New York (Giants OR Yankees)",
    31  			expected: Sequence(Text("New"), Text("York"), Or(Text("Giants"), Text("Yankees"))),
    32  		},
    33  
    34  		{
    35  			filter: "a b AND c AND d",
    36  			expected: And(
    37  				Sequence(Text("a"), Text("b")),
    38  				Text("c"),
    39  				Text("d"),
    40  			),
    41  		},
    42  
    43  		{
    44  			filter: "(a b) AND c AND d",
    45  			expected: And(
    46  				Sequence(Text("a"), Text("b")),
    47  				Text("c"),
    48  				Text("d"),
    49  			),
    50  		},
    51  
    52  		{
    53  			filter: "a < 10 OR a >= 100",
    54  			expected: Or(
    55  				LessThan(Text("a"), Int(10)),
    56  				GreaterEquals(Text("a"), Int(100)),
    57  			),
    58  		},
    59  		{
    60  			filter: "a OR b OR c",
    61  			expected: Or(
    62  				Text("a"),
    63  				Text("b"),
    64  				Text("c"),
    65  			),
    66  		},
    67  
    68  		{
    69  			filter:   "NOT (a OR b)",
    70  			expected: Not(Or(Text("a"), Text("b"))),
    71  		},
    72  
    73  		{
    74  			filter:   `-file:".java"`,
    75  			expected: Not(Has(Text("file"), String(".java"))),
    76  		},
    77  
    78  		{
    79  			filter:   "-30",
    80  			expected: Int(-30),
    81  		},
    82  
    83  		{
    84  			filter:   "package=com.google",
    85  			expected: Equals(Text("package"), Member(Text("com"), "google")),
    86  		},
    87  
    88  		{
    89  			filter:   `msg != 'hello'`,
    90  			expected: NotEquals(Text("msg"), String("hello")),
    91  		},
    92  
    93  		{
    94  			filter:   `1 > 0`,
    95  			expected: GreaterThan(Int(1), Int(0)),
    96  		},
    97  
    98  		{
    99  			filter:   `2.5 >= 2.4`,
   100  			expected: GreaterEquals(Float(2.5), Float(2.4)),
   101  		},
   102  
   103  		{
   104  			filter:   `foo >= -2.4`,
   105  			expected: GreaterEquals(Text("foo"), Float(-2.4)),
   106  		},
   107  
   108  		{
   109  			filter:   `foo >= (-2.4)`,
   110  			expected: GreaterEquals(Text("foo"), Float(-2.4)),
   111  		},
   112  
   113  		{
   114  			filter:   `-2.5 >= -2.4`,
   115  			expected: Not(GreaterEquals(Float(2.5), Float(-2.4))),
   116  		},
   117  
   118  		{
   119  			filter:   `yesterday < request.time`,
   120  			expected: LessThan(Text("yesterday"), Member(Text("request"), "time")),
   121  		},
   122  
   123  		{
   124  			filter: `experiment.rollout <= cohort(request.user)`,
   125  			expected: LessEquals(
   126  				Member(Text("experiment"), "rollout"),
   127  				Function("cohort", Member(Text("request"), "user")),
   128  			),
   129  		},
   130  
   131  		{
   132  			filter:   `prod`,
   133  			expected: Text("prod"),
   134  		},
   135  
   136  		{
   137  			filter:   `expr.type_map.1.type`,
   138  			expected: Member(Member(Member(Text("expr"), "type_map"), "1"), "type"),
   139  		},
   140  
   141  		{
   142  			filter:   `regex(m.key, '^.*prod.*$')`,
   143  			expected: Function("regex", Member(Text("m"), "key"), String("^.*prod.*$")),
   144  		},
   145  
   146  		{
   147  			filter:   `math.mem('30mb')`,
   148  			expected: Function("math.mem", String("30mb")),
   149  		},
   150  
   151  		{
   152  			filter: `(msg.endsWith('world') AND retries < 10)`,
   153  			expected: And(
   154  				Function("msg.endsWith", String("world")),
   155  				LessThan(Text("retries"), Int(10)),
   156  			),
   157  		},
   158  
   159  		{
   160  			filter: `(endsWith(msg, 'world') AND retries < 10)`,
   161  			expected: And(
   162  				Function("endsWith", Text("msg"), String("world")),
   163  				LessThan(Text("retries"), Int(10)),
   164  			),
   165  		},
   166  
   167  		{
   168  			filter:   "time.now()",
   169  			expected: Function("time.now"),
   170  		},
   171  
   172  		{
   173  			filter: `timestamp("2012-04-21T11:30:00-04:00")`,
   174  			expected: Timestamp(
   175  				time.Date(2012, time.April, 21, 11, 30, 0, 0, time.FixedZone("EST", -4*int(time.Hour.Seconds()))),
   176  			),
   177  		},
   178  
   179  		{
   180  			filter:   `duration("32s")`,
   181  			expected: Duration(32 * time.Second),
   182  		},
   183  
   184  		{
   185  			filter:   `duration("4h0m0s")`,
   186  			expected: Duration(4 * time.Hour),
   187  		},
   188  
   189  		{
   190  			filter: `
   191  				start_time > timestamp("2006-01-02T15:04:05+07:00") AND 
   192  				(driver = "driver1" OR start_driver = "driver1" OR end_driver = "driver1")
   193  			`,
   194  			expected: And(
   195  				GreaterThan(
   196  					Text("start_time"),
   197  					Timestamp(
   198  						time.Date(
   199  							2006, time.January, 2, 15, 4, 5, 0,
   200  							time.FixedZone("EST", 7*int(time.Hour.Seconds())),
   201  						),
   202  					),
   203  				),
   204  				Or(
   205  					Equals(Text("driver"), String("driver1")),
   206  					Equals(Text("start_driver"), String("driver1")),
   207  					Equals(Text("end_driver"), String("driver1")),
   208  				),
   209  			),
   210  		},
   211  
   212  		{
   213  			filter:   `annotations:schedule`,
   214  			expected: Has(Text("annotations"), String("schedule")),
   215  		},
   216  
   217  		{
   218  			filter:   `annotations.schedule = "test"`,
   219  			expected: Equals(Member(Text("annotations"), "schedule"), String("test")),
   220  		},
   221  
   222  		{
   223  			filter:        "<",
   224  			errorContains: "unexpected token <",
   225  		},
   226  
   227  		{
   228  			filter:        `(-2.5) >= -2.4`,
   229  			errorContains: "unexpected token >=",
   230  		},
   231  
   232  		{
   233  			filter:        `a = "foo`,
   234  			errorContains: "unterminated string",
   235  		},
   236  
   237  		{
   238  			filter:        "invalid = foo\xa0\x01bar",
   239  			errorContains: "invalid UTF-8",
   240  		},
   241  	} {
   242  		tt := tt
   243  		t.Run(tt.filter, func(t *testing.T) {
   244  			t.Parallel()
   245  			var parser Parser
   246  			parser.Init(tt.filter)
   247  			actual, err := parser.Parse()
   248  			if tt.errorContains != "" {
   249  				if actual != nil {
   250  					t.Log(actual.GetExpr().String())
   251  				}
   252  				assert.ErrorContains(t, err, tt.errorContains)
   253  			} else {
   254  				assert.NilError(t, err)
   255  				assert.DeepEqual(
   256  					t,
   257  					tt.expected,
   258  					actual.GetExpr(),
   259  					protocmp.Transform(),
   260  					protocmp.IgnoreFields(&expr.Expr{}, "id"),
   261  				)
   262  				assertUniqueExprIDs(t, actual.GetExpr())
   263  			}
   264  		})
   265  	}
   266  }
   267  
   268  func assertUniqueExprIDs(t *testing.T, exp *expr.Expr) {
   269  	t.Helper()
   270  	seenIDs := make(map[int64]struct{})
   271  	Walk(func(currExpr, _ *expr.Expr) bool {
   272  		if _, ok := seenIDs[currExpr.GetId()]; ok {
   273  			t.Fatalf("duplicate expression ID '%d' for expr %v", currExpr.GetId(), currExpr)
   274  		}
   275  		seenIDs[currExpr.GetId()] = struct{}{}
   276  		return true
   277  	}, exp)
   278  }
   279  

View as plain text