...

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

Documentation: go.einride.tech/aip/filtering

     1  package filtering
     2  
     3  import (
     4  	"testing"
     5  
     6  	syntaxv1 "go.einride.tech/aip/proto/gen/einride/example/syntax/v1"
     7  	"gotest.tools/v3/assert"
     8  )
     9  
    10  func TestChecker(t *testing.T) {
    11  	t.Parallel()
    12  	for _, tt := range []struct {
    13  		filter        string
    14  		declarations  []DeclarationOption
    15  		errorContains string
    16  	}{
    17  		{
    18  			filter: "New York Giants",
    19  			declarations: []DeclarationOption{
    20  				DeclareIdent("New", TypeBool),
    21  				DeclareIdent("York", TypeBool),
    22  				DeclareIdent("Giants", TypeBool),
    23  			},
    24  			errorContains: "undeclared function 'FUZZY'",
    25  		},
    26  
    27  		{
    28  			filter: "New York Giants OR Yankees",
    29  			declarations: []DeclarationOption{
    30  				DeclareStandardFunctions(),
    31  				DeclareIdent("New", TypeBool),
    32  				DeclareIdent("York", TypeBool),
    33  				DeclareIdent("Giants", TypeBool),
    34  				DeclareIdent("Yankees", TypeBool),
    35  			},
    36  			errorContains: "undeclared function 'FUZZY'",
    37  		},
    38  
    39  		{
    40  			filter: "New York (Giants OR Yankees)",
    41  			declarations: []DeclarationOption{
    42  				DeclareStandardFunctions(),
    43  				DeclareIdent("New", TypeBool),
    44  				DeclareIdent("York", TypeBool),
    45  				DeclareIdent("Giants", TypeBool),
    46  				DeclareIdent("Yankees", TypeBool),
    47  			},
    48  			errorContains: "undeclared function 'FUZZY'",
    49  		},
    50  
    51  		{
    52  			filter: "a b AND c AND d",
    53  			declarations: []DeclarationOption{
    54  				DeclareStandardFunctions(),
    55  				DeclareIdent("a", TypeBool),
    56  				DeclareIdent("b", TypeBool),
    57  				DeclareIdent("c", TypeBool),
    58  				DeclareIdent("d", TypeBool),
    59  			},
    60  			errorContains: "undeclared function 'FUZZY'",
    61  		},
    62  
    63  		{
    64  			filter: "a",
    65  			declarations: []DeclarationOption{
    66  				DeclareIdent("a", TypeBool),
    67  			},
    68  		},
    69  
    70  		{
    71  			filter: "(a b) AND c AND d",
    72  			declarations: []DeclarationOption{
    73  				DeclareStandardFunctions(),
    74  				DeclareIdent("a", TypeBool),
    75  				DeclareIdent("b", TypeBool),
    76  				DeclareIdent("c", TypeBool),
    77  				DeclareIdent("d", TypeBool),
    78  			},
    79  			errorContains: "undeclared function 'FUZZY'",
    80  		},
    81  
    82  		{
    83  			filter: `author = "Karin Boye" AND NOT read`,
    84  			declarations: []DeclarationOption{
    85  				DeclareStandardFunctions(),
    86  				DeclareIdent("author", TypeString),
    87  				DeclareIdent("read", TypeBool),
    88  			},
    89  		},
    90  
    91  		{
    92  			filter: "a < 10 OR a >= 100",
    93  			declarations: []DeclarationOption{
    94  				DeclareStandardFunctions(),
    95  				DeclareIdent("a", TypeInt),
    96  			},
    97  		},
    98  
    99  		{
   100  			filter: "NOT (a OR b)",
   101  			declarations: []DeclarationOption{
   102  				DeclareStandardFunctions(),
   103  				DeclareIdent("a", TypeBool),
   104  				DeclareIdent("b", TypeBool),
   105  			},
   106  		},
   107  
   108  		{
   109  			filter: `-file:".java"`,
   110  			declarations: []DeclarationOption{
   111  				DeclareStandardFunctions(),
   112  				DeclareIdent("file", TypeString),
   113  			},
   114  		},
   115  
   116  		{
   117  			filter:        "-30",
   118  			errorContains: "non-bool result type",
   119  		},
   120  
   121  		{
   122  			filter: "package=com.google",
   123  			declarations: []DeclarationOption{
   124  				DeclareStandardFunctions(),
   125  				DeclareIdent("package", TypeString),
   126  				DeclareIdent("com", TypeMap(TypeString, TypeString)),
   127  			},
   128  		},
   129  
   130  		{
   131  			filter: `msg != 'hello'`,
   132  			declarations: []DeclarationOption{
   133  				DeclareStandardFunctions(),
   134  				DeclareIdent("msg", TypeString),
   135  			},
   136  		},
   137  
   138  		{
   139  			filter: `1 > 0`,
   140  			declarations: []DeclarationOption{
   141  				DeclareStandardFunctions(),
   142  			},
   143  		},
   144  
   145  		{
   146  			filter: `2.5 >= 2.4`,
   147  			declarations: []DeclarationOption{
   148  				DeclareStandardFunctions(),
   149  			},
   150  		},
   151  
   152  		{
   153  			filter: `foo >= -2.4`,
   154  			declarations: []DeclarationOption{
   155  				DeclareStandardFunctions(),
   156  				DeclareIdent("foo", TypeFloat),
   157  			},
   158  		},
   159  
   160  		{
   161  			filter: `foo >= (-2.4)`,
   162  			declarations: []DeclarationOption{
   163  				DeclareStandardFunctions(),
   164  				DeclareIdent("foo", TypeFloat),
   165  			},
   166  		},
   167  
   168  		{
   169  			filter: `-2.5 >= -2.4`,
   170  			declarations: []DeclarationOption{
   171  				DeclareStandardFunctions(),
   172  			},
   173  		},
   174  
   175  		{
   176  			filter: `yesterday < request.time`,
   177  			declarations: []DeclarationOption{
   178  				DeclareStandardFunctions(),
   179  				DeclareIdent("yesterday", TypeTimestamp),
   180  			},
   181  			// TODO: Add support for structs.
   182  			errorContains: "undeclared identifier 'request'",
   183  		},
   184  
   185  		{
   186  			filter: `experiment.rollout <= cohort(request.user)`,
   187  			declarations: []DeclarationOption{
   188  				DeclareStandardFunctions(),
   189  				DeclareFunction("cohort", NewFunctionOverload("cohort_string", TypeFloat, TypeString)),
   190  			},
   191  			// TODO: Add support for structs.
   192  			errorContains: "undeclared identifier 'experiment'",
   193  		},
   194  
   195  		{
   196  			filter: `prod`,
   197  			declarations: []DeclarationOption{
   198  				DeclareIdent("prod", TypeBool),
   199  			},
   200  		},
   201  
   202  		{
   203  			filter: `expr.type_map.1.type`,
   204  			// TODO: Add support for structs.
   205  			errorContains: "undeclared identifier 'expr'",
   206  		},
   207  
   208  		{
   209  			filter: `regex(m.key, '^.*prod.*$')`,
   210  			declarations: []DeclarationOption{
   211  				DeclareStandardFunctions(),
   212  				DeclareIdent("m", TypeMap(TypeString, TypeString)),
   213  				DeclareFunction("regex", NewFunctionOverload("regex_string", TypeBool, TypeString, TypeString)),
   214  			},
   215  		},
   216  
   217  		{
   218  			filter: `math.mem('30mb')`,
   219  			declarations: []DeclarationOption{
   220  				DeclareFunction("math.mem", NewFunctionOverload("math.mem_string", TypeBool, TypeString)),
   221  			},
   222  		},
   223  
   224  		{
   225  			filter: `(msg.endsWith('world') AND retries < 10)`,
   226  			declarations: []DeclarationOption{
   227  				DeclareStandardFunctions(),
   228  				DeclareIdent("retries", TypeInt),
   229  			},
   230  			errorContains: "undeclared function 'msg.endsWith'",
   231  		},
   232  
   233  		{
   234  			filter: `(endsWith(msg, 'world') AND retries < 10)`,
   235  			declarations: []DeclarationOption{
   236  				DeclareStandardFunctions(),
   237  				DeclareFunction("endsWith", NewFunctionOverload("endsWith_string", TypeBool, TypeString, TypeString)),
   238  				DeclareIdent("retries", TypeInt),
   239  				DeclareIdent("msg", TypeString),
   240  			},
   241  		},
   242  
   243  		{
   244  			filter: "expire_time > time.now()",
   245  			declarations: []DeclarationOption{
   246  				DeclareStandardFunctions(),
   247  				DeclareFunction("time.now", NewFunctionOverload("time.now", TypeTimestamp)),
   248  				DeclareIdent("expire_time", TypeTimestamp),
   249  			},
   250  		},
   251  
   252  		{
   253  			filter: `time.now() > timestamp("2012-04-21T11:30:00-04:00")`,
   254  			declarations: []DeclarationOption{
   255  				DeclareStandardFunctions(),
   256  				DeclareFunction("time.now", NewFunctionOverload("time.now", TypeTimestamp)),
   257  			},
   258  		},
   259  
   260  		{
   261  			filter: `time.now() > timestamp("INVALID")`,
   262  			declarations: []DeclarationOption{
   263  				DeclareStandardFunctions(),
   264  				DeclareFunction("time.now", NewFunctionOverload("time.now", TypeTimestamp)),
   265  			},
   266  			errorContains: "invalid timestamp",
   267  		},
   268  
   269  		{
   270  			filter: `ttl > duration("30s")`,
   271  			declarations: []DeclarationOption{
   272  				DeclareStandardFunctions(),
   273  				DeclareIdent("ttl", TypeDuration),
   274  			},
   275  		},
   276  
   277  		{
   278  			filter: `ttl > duration("INVALID")`,
   279  			declarations: []DeclarationOption{
   280  				DeclareStandardFunctions(),
   281  				DeclareIdent("ttl", TypeDuration),
   282  			},
   283  			errorContains: "invalid duration",
   284  		},
   285  
   286  		{
   287  			filter: `ttl > duration(input_field)`,
   288  			declarations: []DeclarationOption{
   289  				DeclareStandardFunctions(),
   290  				DeclareIdent("ttl", TypeDuration),
   291  				DeclareIdent("input_field", TypeString),
   292  			},
   293  		},
   294  
   295  		{
   296  			filter: `create_time > timestamp("2006-01-02T15:04:05+07:00")`,
   297  			declarations: []DeclarationOption{
   298  				DeclareStandardFunctions(),
   299  				DeclareIdent("create_time", TypeTimestamp),
   300  			},
   301  		},
   302  
   303  		{
   304  			filter: `create_time > timestamp("2006-01-02T15:04:05+07:00")`,
   305  			declarations: []DeclarationOption{
   306  				DeclareStandardFunctions(),
   307  				DeclareIdent("create_time", TypeTimestamp),
   308  			},
   309  		},
   310  
   311  		{
   312  			filter: `
   313  				start_time > timestamp("2006-01-02T15:04:05+07:00") AND
   314  				(driver = "driver1" OR start_driver = "driver1" OR end_driver = "driver1")
   315  			`,
   316  			declarations: []DeclarationOption{
   317  				DeclareStandardFunctions(),
   318  				DeclareIdent("start_time", TypeTimestamp),
   319  				DeclareIdent("driver", TypeString),
   320  				DeclareIdent("start_driver", TypeString),
   321  				DeclareIdent("end_driver", TypeString),
   322  			},
   323  		},
   324  
   325  		{
   326  			filter: `annotations:schedule`,
   327  			declarations: []DeclarationOption{
   328  				DeclareStandardFunctions(),
   329  				DeclareIdent("annotations", TypeMap(TypeString, TypeString)),
   330  			},
   331  		},
   332  
   333  		{
   334  			filter: `annotations.schedule = "test"`,
   335  			declarations: []DeclarationOption{
   336  				DeclareStandardFunctions(),
   337  				DeclareIdent("annotations", TypeMap(TypeString, TypeString)),
   338  			},
   339  		},
   340  
   341  		{
   342  			filter: `enum = ENUM_ONE`,
   343  			declarations: []DeclarationOption{
   344  				DeclareStandardFunctions(),
   345  				DeclareEnumIdent("enum", syntaxv1.Enum(0).Type()),
   346  			},
   347  		},
   348  
   349  		{
   350  			filter: `enum = ENUM_ONE AND NOT enum2 = ENUM_TWO`,
   351  			declarations: []DeclarationOption{
   352  				DeclareStandardFunctions(),
   353  				DeclareEnumIdent("enum", syntaxv1.Enum(0).Type()),
   354  				DeclareEnumIdent("enum2", syntaxv1.Enum(0).Type()),
   355  			},
   356  		},
   357  
   358  		{
   359  			filter: `create_time = "2022-08-12 22:22:22"`,
   360  			declarations: []DeclarationOption{
   361  				DeclareStandardFunctions(),
   362  				DeclareIdent("create_time", TypeTimestamp),
   363  			},
   364  			errorContains: "invalid timestamp. Should be in RFC3339 format",
   365  		},
   366  
   367  		{
   368  			filter: `create_time = "2022-08-12T22:22:22+01:00"`,
   369  			declarations: []DeclarationOption{
   370  				DeclareStandardFunctions(),
   371  				DeclareIdent("create_time", TypeTimestamp),
   372  			},
   373  		},
   374  
   375  		{
   376  			filter: `create_time != "2022-08-12T22:22:22+01:00"`,
   377  			declarations: []DeclarationOption{
   378  				DeclareStandardFunctions(),
   379  				DeclareIdent("create_time", TypeTimestamp),
   380  			},
   381  		},
   382  
   383  		{
   384  			filter: `create_time < "2022-08-12T22:22:22+01:00"`,
   385  			declarations: []DeclarationOption{
   386  				DeclareStandardFunctions(),
   387  				DeclareIdent("create_time", TypeTimestamp),
   388  			},
   389  		},
   390  
   391  		{
   392  			filter: `create_time > "2022-08-12T22:22:22+01:00"`,
   393  			declarations: []DeclarationOption{
   394  				DeclareStandardFunctions(),
   395  				DeclareIdent("create_time", TypeTimestamp),
   396  			},
   397  		},
   398  
   399  		{
   400  			filter: `create_time <= "2022-08-12T22:22:22+01:00"`,
   401  			declarations: []DeclarationOption{
   402  				DeclareStandardFunctions(),
   403  				DeclareIdent("create_time", TypeTimestamp),
   404  			},
   405  		},
   406  
   407  		{
   408  			filter: `create_time >= "2022-08-12T22:22:22+01:00"`,
   409  			declarations: []DeclarationOption{
   410  				DeclareStandardFunctions(),
   411  				DeclareIdent("create_time", TypeTimestamp),
   412  			},
   413  		},
   414  
   415  		{
   416  			filter:        "<",
   417  			errorContains: "unexpected token <",
   418  		},
   419  
   420  		{
   421  			filter:        `(-2.5) >= -2.4`,
   422  			errorContains: "unexpected token >=",
   423  		},
   424  
   425  		{
   426  			filter:        `a = "foo`,
   427  			errorContains: "unterminated string",
   428  		},
   429  
   430  		{
   431  			filter:        "invalid = foo\xa0\x01bar",
   432  			errorContains: "invalid UTF-8",
   433  		},
   434  	} {
   435  		tt := tt
   436  		t.Run(tt.filter, func(t *testing.T) {
   437  			t.Parallel()
   438  			var parser Parser
   439  			parser.Init(tt.filter)
   440  			parsedExpr, err := parser.Parse()
   441  			if err != nil && tt.errorContains != "" {
   442  				assert.ErrorContains(t, err, tt.errorContains)
   443  				return
   444  			}
   445  			assert.NilError(t, err)
   446  			declarations, err := NewDeclarations(tt.declarations...)
   447  			if err != nil && tt.errorContains != "" {
   448  				assert.ErrorContains(t, err, tt.errorContains)
   449  				return
   450  			}
   451  			assert.NilError(t, err)
   452  			var checker Checker
   453  			checker.Init(parsedExpr.GetExpr(), parsedExpr.GetSourceInfo(), declarations)
   454  			checkedExpr, err := checker.Check()
   455  			if tt.errorContains != "" {
   456  				assert.ErrorContains(t, err, tt.errorContains)
   457  				return
   458  			}
   459  			assert.NilError(t, err)
   460  			assert.Assert(t, checkedExpr != nil)
   461  		})
   462  	}
   463  }
   464  

View as plain text