...

Source file src/github.com/PaesslerAG/gval/gval_parameterized_test.go

Documentation: github.com/PaesslerAG/gval

     1  package gval
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/shopspring/decimal"
    12  )
    13  
    14  func TestParameterized(t *testing.T) {
    15  	testEvaluate(
    16  		[]evaluationTest{
    17  			{
    18  				name:       "Single parameter modified by constant",
    19  				expression: "foo + 2",
    20  				parameter: map[string]interface{}{
    21  					"foo": 2.0,
    22  				},
    23  				want: 4.0,
    24  			},
    25  			{
    26  
    27  				name:       "Single parameter modified by variable",
    28  				expression: "foo * bar",
    29  				parameter: map[string]interface{}{
    30  					"foo": 5.0,
    31  					"bar": 2.0,
    32  				},
    33  				want: 10.0,
    34  			},
    35  			{
    36  
    37  				name:       "Single parameter modified by variable",
    38  				expression: `foo["hey"] * bar[1]`,
    39  				parameter: map[string]interface{}{
    40  					"foo": map[string]interface{}{"hey": 5.0},
    41  					"bar": []interface{}{7., 2.0},
    42  				},
    43  				want: 10.0,
    44  			},
    45  			{
    46  
    47  				name:       "Multiple multiplications of the same parameter",
    48  				expression: "foo * foo * foo",
    49  				parameter: map[string]interface{}{
    50  					"foo": 10.0,
    51  				},
    52  				want: 1000.0,
    53  			},
    54  			{
    55  
    56  				name:       "Multiple additions of the same parameter",
    57  				expression: "foo + foo + foo",
    58  				parameter: map[string]interface{}{
    59  					"foo": 10.0,
    60  				},
    61  				want: 30.0,
    62  			},
    63  			{
    64  				name:       "NoSpaceOperator",
    65  				expression: "true&&name",
    66  				parameter: map[string]interface{}{
    67  					"name": true,
    68  				},
    69  				want: true,
    70  			},
    71  			{
    72  
    73  				name:       "Parameter name sensitivity",
    74  				expression: "foo + FoO + FOO",
    75  				parameter: map[string]interface{}{
    76  					"foo": 8.0,
    77  					"FoO": 4.0,
    78  					"FOO": 2.0,
    79  				},
    80  				want: 14.0,
    81  			},
    82  			{
    83  
    84  				name:       "Sign prefix comparison against prefixed variable",
    85  				expression: "-1 < -foo",
    86  				parameter:  map[string]interface{}{"foo": -8.0},
    87  				want:       true,
    88  			},
    89  			{
    90  
    91  				name:       "Fixed-point parameter",
    92  				expression: "foo > 1",
    93  				parameter:  map[string]interface{}{"foo": 2},
    94  				want:       true,
    95  			},
    96  			{
    97  
    98  				name:       "Modifier after closing clause",
    99  				expression: "(2 + 2) + 2 == 6",
   100  				want:       true,
   101  			},
   102  			{
   103  
   104  				name:       "Comparator after closing clause",
   105  				expression: "(2 + 2) >= 4",
   106  				want:       true,
   107  			},
   108  			{
   109  
   110  				name:       "Two-boolean logical operation (for issue #8)",
   111  				expression: "(foo == true) || (bar == true)",
   112  				parameter: map[string]interface{}{
   113  					"foo": true,
   114  					"bar": false,
   115  				},
   116  				want: true,
   117  			},
   118  			{
   119  
   120  				name:       "Two-variable integer logical operation (for issue #8)",
   121  				expression: "foo > 10 && bar > 10",
   122  				parameter: map[string]interface{}{
   123  					"foo": 1,
   124  					"bar": 11,
   125  				},
   126  				want: false,
   127  			},
   128  			{
   129  
   130  				name:       "Regex against right-hand parameter",
   131  				expression: `"foobar" =~ foo`,
   132  				parameter: map[string]interface{}{
   133  					"foo": "obar",
   134  				},
   135  				want: true,
   136  			},
   137  			{
   138  
   139  				name:       "Not-regex against right-hand parameter",
   140  				expression: `"foobar" !~ foo`,
   141  				parameter: map[string]interface{}{
   142  					"foo": "baz",
   143  				},
   144  				want: true,
   145  			},
   146  			{
   147  
   148  				name:       "Regex against two parameter",
   149  				expression: `foo =~ bar`,
   150  				parameter: map[string]interface{}{
   151  					"foo": "foobar",
   152  					"bar": "oba",
   153  				},
   154  				want: true,
   155  			},
   156  			{
   157  
   158  				name:       "Not-regex against two parameter",
   159  				expression: "foo !~ bar",
   160  				parameter: map[string]interface{}{
   161  					"foo": "foobar",
   162  					"bar": "baz",
   163  				},
   164  				want: true,
   165  			},
   166  			{
   167  
   168  				name:       "Pre-compiled regex",
   169  				expression: "foo =~ bar",
   170  				parameter: map[string]interface{}{
   171  					"foo": "foobar",
   172  					"bar": regexp.MustCompile("[fF][oO]+"),
   173  				},
   174  				want: true,
   175  			},
   176  			{
   177  
   178  				name:       "Pre-compiled not-regex",
   179  				expression: "foo !~ bar",
   180  				parameter: map[string]interface{}{
   181  					"foo": "foobar",
   182  					"bar": regexp.MustCompile("[fF][oO]+"),
   183  				},
   184  				want: false,
   185  			},
   186  			{
   187  
   188  				name:       "Single boolean parameter",
   189  				expression: "commission ? 10",
   190  				parameter: map[string]interface{}{
   191  					"commission": true},
   192  				want: 10.0,
   193  			},
   194  			{
   195  
   196  				name:       "True comparator with a parameter",
   197  				expression: `partner == "amazon" ? 10`,
   198  				parameter: map[string]interface{}{
   199  					"partner": "amazon"},
   200  				want: 10.0,
   201  			},
   202  			{
   203  
   204  				name:       "False comparator with a parameter",
   205  				expression: `partner == "amazon" ? 10`,
   206  				parameter: map[string]interface{}{
   207  					"partner": "ebay"},
   208  				want: nil,
   209  			},
   210  			{
   211  
   212  				name:       "True comparator with multiple parameters",
   213  				expression: "theft && period == 24 ? 60",
   214  				parameter: map[string]interface{}{
   215  					"theft":  true,
   216  					"period": 24,
   217  				},
   218  				want: 60.0,
   219  			},
   220  			{
   221  
   222  				name:       "False comparator with multiple parameters",
   223  				expression: "theft && period == 24 ? 60",
   224  				parameter: map[string]interface{}{
   225  					"theft":  false,
   226  					"period": 24,
   227  				},
   228  				want: nil,
   229  			},
   230  			{
   231  
   232  				name:       "String concat with single string parameter",
   233  				expression: `foo + "bar"`,
   234  				parameter: map[string]interface{}{
   235  					"foo": "baz"},
   236  				want: "bazbar",
   237  			},
   238  			{
   239  
   240  				name:       "String concat with multiple string parameter",
   241  				expression: "foo + bar",
   242  				parameter: map[string]interface{}{
   243  					"foo": "baz",
   244  					"bar": "quux",
   245  				},
   246  				want: "bazquux",
   247  			},
   248  			{
   249  
   250  				name:       "String concat with float parameter",
   251  				expression: "foo + bar",
   252  				parameter: map[string]interface{}{
   253  					"foo": "baz",
   254  					"bar": 123.0,
   255  				},
   256  				want: "baz123",
   257  			},
   258  			{
   259  
   260  				name:       "Mixed multiple string concat",
   261  				expression: `foo + 123 + "bar" + true`,
   262  				parameter:  map[string]interface{}{"foo": "baz"},
   263  				want:       "baz123bartrue",
   264  			},
   265  			{
   266  
   267  				name:       "Integer width spectrum",
   268  				expression: "uint8 + uint16 + uint32 + uint64 + int8 + int16 + int32 + int64",
   269  				parameter: map[string]interface{}{
   270  					"uint8":  uint8(0),
   271  					"uint16": uint16(0),
   272  					"uint32": uint32(0),
   273  					"uint64": uint64(0),
   274  					"int8":   int8(0),
   275  					"int16":  int16(0),
   276  					"int32":  int32(0),
   277  					"int64":  int64(0),
   278  				},
   279  				want: 0.0,
   280  			},
   281  			{
   282  
   283  				name:       "Null coalesce right",
   284  				expression: "foo ?? 1.0",
   285  				parameter:  map[string]interface{}{"foo": nil},
   286  				want:       1.0,
   287  			},
   288  			{
   289  
   290  				name:       "Multiple comparator/logical operators (#30)",
   291  				expression: "(foo >= 2887057408 && foo <= 2887122943) || (foo >= 168100864 && foo <= 168118271)",
   292  				parameter:  map[string]interface{}{"foo": 2887057409},
   293  				want:       true,
   294  			},
   295  			{
   296  
   297  				name:       "Multiple comparator/logical operators, opposite order (#30)",
   298  				expression: "(foo >= 168100864 && foo <= 168118271) || (foo >= 2887057408 && foo <= 2887122943)",
   299  				parameter:  map[string]interface{}{"foo": 2887057409},
   300  				want:       true,
   301  			},
   302  			{
   303  
   304  				name:       "Multiple comparator/logical operators, small value (#30)",
   305  				expression: "(foo >= 2887057408 && foo <= 2887122943) || (foo >= 168100864 && foo <= 168118271)",
   306  				parameter:  map[string]interface{}{"foo": 168100865},
   307  				want:       true,
   308  			},
   309  			{
   310  
   311  				name:       "Multiple comparator/logical operators, small value, opposite order (#30)",
   312  				expression: "(foo >= 168100864 && foo <= 168118271) || (foo >= 2887057408 && foo <= 2887122943)",
   313  				parameter:  map[string]interface{}{"foo": 168100865},
   314  				want:       true,
   315  			},
   316  			{
   317  
   318  				name:       "Incomparable array equality comparison",
   319  				expression: "arr == arr",
   320  				parameter:  map[string]interface{}{"arr": []int{0, 0, 0}},
   321  				want:       true,
   322  			},
   323  			{
   324  
   325  				name:       "Incomparable array not-equality comparison",
   326  				expression: "arr != arr",
   327  				parameter:  map[string]interface{}{"arr": []int{0, 0, 0}},
   328  				want:       false,
   329  			},
   330  			{
   331  
   332  				name:       "Mixed function and parameters",
   333  				expression: "sum(1.2, amount) + name",
   334  				extension: Function("sum", func(arguments ...interface{}) (interface{}, error) {
   335  					sum := 0.0
   336  					for _, v := range arguments {
   337  						sum += v.(float64)
   338  					}
   339  					return sum, nil
   340  				},
   341  				),
   342  				parameter: map[string]interface{}{"amount": .8,
   343  					"name": "awesome",
   344  				},
   345  
   346  				want: "2awesome",
   347  			},
   348  			{
   349  
   350  				name:       "Short-circuit OR",
   351  				expression: "true || fail()",
   352  				extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
   353  					return nil, fmt.Errorf("Did not short-circuit")
   354  				}),
   355  				want: true,
   356  			},
   357  			{
   358  
   359  				name:       "Short-circuit AND",
   360  				expression: "false && fail()",
   361  				extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
   362  					return nil, fmt.Errorf("Did not short-circuit")
   363  				}),
   364  				want: false,
   365  			},
   366  			{
   367  
   368  				name:       "Short-circuit ternary",
   369  				expression: "true ? 1 : fail()",
   370  				extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
   371  					return nil, fmt.Errorf("Did not short-circuit")
   372  				}),
   373  				want: 1.0,
   374  			},
   375  			{
   376  
   377  				name:       "Short-circuit coalesce",
   378  				expression: `"foo" ?? fail()`,
   379  				extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
   380  					return nil, fmt.Errorf("Did not short-circuit")
   381  				}),
   382  				want: "foo",
   383  			},
   384  			{
   385  
   386  				name:       "Simple parameter call",
   387  				expression: "foo.String",
   388  				parameter:  map[string]interface{}{"foo": foo},
   389  				want:       foo.String,
   390  			},
   391  			{
   392  
   393  				name:       "Simple parameter function call",
   394  				expression: "foo.Func()",
   395  				parameter:  map[string]interface{}{"foo": foo},
   396  				want:       "funk",
   397  			},
   398  			{
   399  
   400  				name:       "Simple parameter call from pointer",
   401  				expression: "fooptr.String",
   402  				parameter:  map[string]interface{}{"fooptr": &foo},
   403  				want:       foo.String,
   404  			},
   405  			{
   406  
   407  				name:       "Simple parameter function call from pointer",
   408  				expression: "fooptr.Func()",
   409  				parameter:  map[string]interface{}{"fooptr": &foo},
   410  				want:       "funk",
   411  			},
   412  			{
   413  
   414  				name:       "Simple parameter call",
   415  				expression: `foo.String == "hi"`,
   416  				parameter:  map[string]interface{}{"foo": foo},
   417  				want:       false,
   418  			},
   419  			{
   420  
   421  				name:       "Simple parameter call with modifier",
   422  				expression: `foo.String + "hi"`,
   423  				parameter:  map[string]interface{}{"foo": foo},
   424  				want:       foo.String + "hi",
   425  			},
   426  			{
   427  
   428  				name:       "Simple parameter function call, two-arg return",
   429  				expression: `foo.Func2()`,
   430  				parameter:  map[string]interface{}{"foo": foo},
   431  				want:       "frink",
   432  			},
   433  			{
   434  
   435  				name:       "Simple parameter function call, one arg",
   436  				expression: `foo.FuncArgStr("boop")`,
   437  				parameter:  map[string]interface{}{"foo": foo},
   438  				want:       "boop",
   439  			},
   440  			{
   441  
   442  				name:       "Simple parameter function call, one arg",
   443  				expression: `foo.FuncArgStr("boop") + "hi"`,
   444  				parameter:  map[string]interface{}{"foo": foo},
   445  				want:       "boophi",
   446  			},
   447  			{
   448  
   449  				name:       "Nested parameter function call",
   450  				expression: `foo.Nested.Dunk("boop")`,
   451  				parameter:  map[string]interface{}{"foo": foo},
   452  				want:       "boopdunk",
   453  			},
   454  			{
   455  
   456  				name:       "Nested parameter call",
   457  				expression: "foo.Nested.Funk",
   458  				parameter:  map[string]interface{}{"foo": foo},
   459  				want:       "funkalicious",
   460  			},
   461  			{
   462  				name:       "Nested map call",
   463  				expression: `foo.Nested.Map["a"]`,
   464  				parameter:  map[string]interface{}{"foo": foo},
   465  				want:       1,
   466  			},
   467  			{
   468  				name:       "Nested slice call",
   469  				expression: `foo.Nested.Slice[1]`,
   470  				parameter:  map[string]interface{}{"foo": foo},
   471  				want:       2,
   472  			},
   473  			{
   474  
   475  				name:       "Parameter call with + modifier",
   476  				expression: "1 + foo.Int",
   477  				parameter:  map[string]interface{}{"foo": foo},
   478  				want:       102.0,
   479  			},
   480  			{
   481  
   482  				name:       "Parameter string call with + modifier",
   483  				expression: `"woop" + (foo.String)`,
   484  				parameter:  map[string]interface{}{"foo": foo},
   485  				want:       "woopstring!",
   486  			},
   487  			{
   488  
   489  				name:       "Parameter call with && operator",
   490  				expression: "true && foo.BoolFalse",
   491  				parameter:  map[string]interface{}{"foo": foo},
   492  				want:       false,
   493  			},
   494  			{
   495  				name:       "Null coalesce nested parameter",
   496  				expression: "foo.Nil ?? false",
   497  				parameter:  map[string]interface{}{"foo": foo},
   498  				want:       false,
   499  			},
   500  			{
   501  				name:       "input functions",
   502  				expression: "func1() + func2()",
   503  				parameter: map[string]interface{}{
   504  					"func1": func() float64 { return 2000 },
   505  					"func2": func() float64 { return 2001 },
   506  				},
   507  				want: 4001.0,
   508  			},
   509  			{
   510  				name:       "input functions",
   511  				expression: "func1(date1) + func2(date2)",
   512  				parameter: map[string]interface{}{
   513  					"date1": func() interface{} {
   514  						y2k, _ := time.Parse("2006", "2000")
   515  						return y2k
   516  					}(),
   517  					"date2": func() interface{} {
   518  						y2k1, _ := time.Parse("2006", "2001")
   519  						return y2k1
   520  					}(),
   521  				},
   522  				extension: NewLanguage(
   523  					Function("func1", func(arguments ...interface{}) (interface{}, error) {
   524  						return float64(arguments[0].(time.Time).Year()), nil
   525  					}),
   526  					Function("func2", func(arguments ...interface{}) (interface{}, error) {
   527  						return float64(arguments[0].(time.Time).Year()), nil
   528  					}),
   529  				),
   530  				want: 4001.0,
   531  			},
   532  			{
   533  				name:       "complex64 number as parameter",
   534  				expression: "complex64",
   535  				parameter: map[string]interface{}{
   536  					"complex64":  complex64(0),
   537  					"complex128": complex128(0),
   538  				},
   539  				want: complex64(0),
   540  			},
   541  			{
   542  				name:       "complex128 number as parameter",
   543  				expression: "complex128",
   544  				parameter: map[string]interface{}{
   545  					"complex64":  complex64(0),
   546  					"complex128": complex128(0),
   547  				},
   548  				want: complex128(0),
   549  			},
   550  			{
   551  				name:       "coalesce with undefined",
   552  				expression: "fooz ?? foo",
   553  				parameter: map[string]interface{}{
   554  					"foo": "bar",
   555  				},
   556  				want: "bar",
   557  			},
   558  			{
   559  				name:       "map[interface{}]interface{}",
   560  				expression: "foo",
   561  				parameter: map[interface{}]interface{}{
   562  					"foo": "bar",
   563  				},
   564  				want: "bar",
   565  			},
   566  			{
   567  				name:       "method on pointer type",
   568  				expression: "foo.PointerFunc()",
   569  				parameter: map[string]interface{}{
   570  					"foo": &dummyParameter{},
   571  				},
   572  				want: "point",
   573  			},
   574  			{
   575  				name:       "custom selector",
   576  				expression: "hello.world",
   577  				parameter:  "!",
   578  				extension: NewLanguage(Base(), VariableSelector(func(path Evaluables) Evaluable {
   579  					return func(c context.Context, v interface{}) (interface{}, error) {
   580  						keys, err := path.EvalStrings(c, v)
   581  						if err != nil {
   582  							return nil, err
   583  						}
   584  						return fmt.Sprintf("%s%s", strings.Join(keys, " "), v), nil
   585  					}
   586  				})),
   587  				want: "hello world!",
   588  			},
   589  			{
   590  				name:       "map[int]int",
   591  				expression: `a[0] + a[2]`,
   592  				parameter: map[string]interface{}{
   593  					"a": map[int]int{0: 1, 2: 1},
   594  				},
   595  				want: 2.,
   596  			},
   597  			{
   598  				name:       "map[int]string",
   599  				expression: `a[0] * a[2]`,
   600  				parameter: map[string]interface{}{
   601  					"a": map[int]string{0: "1", 2: "1"},
   602  				},
   603  				want: 1.,
   604  			},
   605  			{
   606  				name:       "coalesce typed nil 0",
   607  				expression: `ProjectID ?? 0`,
   608  				parameter: struct {
   609  					ProjectID *uint
   610  				}{},
   611  				want: 0.,
   612  			},
   613  			{
   614  				name:       "coalesce typed nil 99",
   615  				expression: `ProjectID ?? 99`,
   616  				parameter: struct {
   617  					ProjectID *uint
   618  				}{},
   619  				want: 99.,
   620  			},
   621  			{
   622  				name:       "operator with typed nil 99",
   623  				expression: `ProjectID + 99`,
   624  				parameter: struct {
   625  					ProjectID *uint
   626  				}{},
   627  				want: "<nil>99",
   628  			},
   629  			{
   630  				name:       "operator with typed nil if",
   631  				expression: `Flag ? 1 : 2`,
   632  				parameter: struct {
   633  					Flag *uint
   634  				}{},
   635  				want: 2.,
   636  			},
   637  			{
   638  				name:       "Decimal math doesn't experience rounding error",
   639  				expression: "(x * 12.146) - y",
   640  				extension:  decimalArithmetic,
   641  				parameter: map[string]interface{}{
   642  					"x": 12.5,
   643  					"y": -5,
   644  				},
   645  				want:         decimal.NewFromFloat(156.825),
   646  				equalityFunc: decimalEqualityFunc,
   647  			},
   648  			{
   649  				name:       "Decimal logical operators fractional difference",
   650  				expression: "((x * 12.146) - y) > 156.824999999",
   651  				extension:  decimalArithmetic,
   652  				parameter: map[string]interface{}{
   653  					"x": 12.5,
   654  					"y": -5,
   655  				},
   656  				want: true,
   657  			},
   658  			{
   659  				name:       "Decimal logical operators whole number difference",
   660  				expression: "((x * 12.146) - y) > 156",
   661  				extension:  decimalArithmetic,
   662  				parameter: map[string]interface{}{
   663  					"x": 12.5,
   664  					"y": -5,
   665  				},
   666  				want: true,
   667  			},
   668  			{
   669  				name:       "Decimal logical operators exact decimal match against GT",
   670  				expression: "((x * 12.146) - y) > 156.825",
   671  				extension:  decimalArithmetic,
   672  				parameter: map[string]interface{}{
   673  					"x": 12.5,
   674  					"y": -5,
   675  				},
   676  				want: false,
   677  			},
   678  			{
   679  				name:       "Decimal logical operators exact equality",
   680  				expression: "((x * 12.146) - y) == 156.825",
   681  				extension:  decimalArithmetic,
   682  				parameter: map[string]interface{}{
   683  					"x": 12.5,
   684  					"y": -5,
   685  				},
   686  				want: true,
   687  			},
   688  			{
   689  				name:       "Decimal mixes with string logic with force fail",
   690  				expression: `(((x * 12.146) - y) == 156.825) && a == "test" && !b && b`,
   691  				extension:  decimalArithmetic,
   692  				parameter: map[string]interface{}{
   693  					"x": 12.5,
   694  					"y": -5,
   695  					"a": "test",
   696  					"b": false,
   697  				},
   698  				want: false,
   699  			},
   700  		},
   701  		t,
   702  	)
   703  }
   704  

View as plain text