Ocean-240.2-Emulator/gval/gval_parameterized_test.go

720 lines
17 KiB
Go

package gval
import (
"context"
"fmt"
"regexp"
"strings"
"testing"
"time"
"github.com/shopspring/decimal"
)
func TestParameterized(t *testing.T) {
testEvaluate(
[]evaluationTest{
{
name: "Single parameter modified by constant",
expression: "foo + 2",
parameter: map[string]interface{}{
"foo": 2.0,
},
want: 4.0,
},
{
name: "Single parameter modified by variable",
expression: "foo * bar",
parameter: map[string]interface{}{
"foo": 5.0,
"bar": 2.0,
},
want: 10.0,
},
{
name: "Single parameter modified by variable",
expression: `foo["hey"] * bar[1]`,
parameter: map[string]interface{}{
"foo": map[string]interface{}{"hey": 5.0},
"bar": []interface{}{7., 2.0},
},
want: 10.0,
},
{
name: "Multiple multiplications of the same parameter",
expression: "foo * foo * foo",
parameter: map[string]interface{}{
"foo": 10.0,
},
want: 1000.0,
},
{
name: "Multiple additions of the same parameter",
expression: "foo + foo + foo",
parameter: map[string]interface{}{
"foo": 10.0,
},
want: 30.0,
},
{
name: "NoSpaceOperator",
expression: "true&&name",
parameter: map[string]interface{}{
"name": true,
},
want: true,
},
{
name: "Parameter name sensitivity",
expression: "foo + FoO + FOO",
parameter: map[string]interface{}{
"foo": 8.0,
"FoO": 4.0,
"FOO": 2.0,
},
want: 14.0,
},
{
name: "Sign prefix comparison against prefixed variable",
expression: "-1 < -foo",
parameter: map[string]interface{}{"foo": -8.0},
want: true,
},
{
name: "Fixed-point parameter",
expression: "foo > 1",
parameter: map[string]interface{}{"foo": 2},
want: true,
},
{
name: "Modifier after closing clause",
expression: "(2 + 2) + 2 == 6",
want: true,
},
{
name: "Comparator after closing clause",
expression: "(2 + 2) >= 4",
want: true,
},
{
name: "Two-boolean logical operation (for issue #8)",
expression: "(foo == true) || (bar == true)",
parameter: map[string]interface{}{
"foo": true,
"bar": false,
},
want: true,
},
{
name: "Two-variable integer logical operation (for issue #8)",
expression: "foo > 10 && bar > 10",
parameter: map[string]interface{}{
"foo": 1,
"bar": 11,
},
want: false,
},
{
name: "Regex against right-hand parameter",
expression: `"foobar" =~ foo`,
parameter: map[string]interface{}{
"foo": "obar",
},
want: true,
},
{
name: "Not-regex against right-hand parameter",
expression: `"foobar" !~ foo`,
parameter: map[string]interface{}{
"foo": "baz",
},
want: true,
},
{
name: "Regex against two parameter",
expression: `foo =~ bar`,
parameter: map[string]interface{}{
"foo": "foobar",
"bar": "oba",
},
want: true,
},
{
name: "Not-regex against two parameter",
expression: "foo !~ bar",
parameter: map[string]interface{}{
"foo": "foobar",
"bar": "baz",
},
want: true,
},
{
name: "Pre-compiled regex",
expression: "foo =~ bar",
parameter: map[string]interface{}{
"foo": "foobar",
"bar": regexp.MustCompile("[fF][oO]+"),
},
want: true,
},
{
name: "Pre-compiled not-regex",
expression: "foo !~ bar",
parameter: map[string]interface{}{
"foo": "foobar",
"bar": regexp.MustCompile("[fF][oO]+"),
},
want: false,
},
{
name: "Single boolean parameter",
expression: "commission ? 10",
parameter: map[string]interface{}{
"commission": true},
want: 10.0,
},
{
name: "True comparator with a parameter",
expression: `partner == "amazon" ? 10`,
parameter: map[string]interface{}{
"partner": "amazon"},
want: 10.0,
},
{
name: "False comparator with a parameter",
expression: `partner == "amazon" ? 10`,
parameter: map[string]interface{}{
"partner": "ebay"},
want: nil,
},
{
name: "True comparator with multiple parameters",
expression: "theft && period == 24 ? 60",
parameter: map[string]interface{}{
"theft": true,
"period": 24,
},
want: 60.0,
},
{
name: "False comparator with multiple parameters",
expression: "theft && period == 24 ? 60",
parameter: map[string]interface{}{
"theft": false,
"period": 24,
},
want: nil,
},
{
name: "String concat with single string parameter",
expression: `foo + "bar"`,
parameter: map[string]interface{}{
"foo": "baz"},
want: "bazbar",
},
{
name: "String concat with multiple string parameter",
expression: "foo + bar",
parameter: map[string]interface{}{
"foo": "baz",
"bar": "quux",
},
want: "bazquux",
},
{
name: "String concat with float parameter",
expression: "foo + bar",
parameter: map[string]interface{}{
"foo": "baz",
"bar": 123.0,
},
want: "baz123",
},
{
name: "Mixed multiple string concat",
expression: `foo + 123 + "bar" + true`,
parameter: map[string]interface{}{"foo": "baz"},
want: "baz123bartrue",
},
{
name: "Integer width spectrum",
expression: "uint8 + uint16 + uint32 + uint64 + int8 + int16 + int32 + int64",
parameter: map[string]interface{}{
"uint8": uint8(0),
"uint16": uint16(0),
"uint32": uint32(0),
"uint64": uint64(0),
"int8": int8(0),
"int16": int16(0),
"int32": int32(0),
"int64": int64(0),
},
want: 0.0,
},
{
name: "Null coalesce right",
expression: "foo ?? 1.0",
parameter: map[string]interface{}{"foo": nil},
want: 1.0,
},
{
name: "Multiple comparator/logical operators (#30)",
expression: "(foo >= 2887057408 && foo <= 2887122943) || (foo >= 168100864 && foo <= 168118271)",
parameter: map[string]interface{}{"foo": 2887057409},
want: true,
},
{
name: "Multiple comparator/logical operators, opposite order (#30)",
expression: "(foo >= 168100864 && foo <= 168118271) || (foo >= 2887057408 && foo <= 2887122943)",
parameter: map[string]interface{}{"foo": 2887057409},
want: true,
},
{
name: "Multiple comparator/logical operators, small value (#30)",
expression: "(foo >= 2887057408 && foo <= 2887122943) || (foo >= 168100864 && foo <= 168118271)",
parameter: map[string]interface{}{"foo": 168100865},
want: true,
},
{
name: "Multiple comparator/logical operators, small value, opposite order (#30)",
expression: "(foo >= 168100864 && foo <= 168118271) || (foo >= 2887057408 && foo <= 2887122943)",
parameter: map[string]interface{}{"foo": 168100865},
want: true,
},
{
name: "Incomparable array equality comparison",
expression: "arr == arr",
parameter: map[string]interface{}{"arr": []int{0, 0, 0}},
want: true,
},
{
name: "Incomparable array not-equality comparison",
expression: "arr != arr",
parameter: map[string]interface{}{"arr": []int{0, 0, 0}},
want: false,
},
{
name: "Mixed function and parameters",
expression: "sum(1.2, amount) + name",
extension: Function("sum", func(arguments ...interface{}) (interface{}, error) {
sum := 0.0
for _, v := range arguments {
sum += v.(float64)
}
return sum, nil
},
),
parameter: map[string]interface{}{"amount": .8,
"name": "awesome",
},
want: "2awesome",
},
{
name: "Short-circuit OR",
expression: "true || fail()",
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
return nil, fmt.Errorf("Did not short-circuit")
}),
want: true,
},
{
name: "Short-circuit AND",
expression: "false && fail()",
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
return nil, fmt.Errorf("Did not short-circuit")
}),
want: false,
},
{
name: "Short-circuit ternary",
expression: "true ? 1 : fail()",
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
return nil, fmt.Errorf("Did not short-circuit")
}),
want: 1.0,
},
{
name: "Short-circuit coalesce",
expression: `"foo" ?? fail()`,
extension: Function("fail", func(arguments ...interface{}) (interface{}, error) {
return nil, fmt.Errorf("Did not short-circuit")
}),
want: "foo",
},
{
name: "Simple parameter call",
expression: "foo.String",
parameter: map[string]interface{}{"foo": foo},
want: foo.String,
},
{
name: "Simple parameter function call",
expression: "foo.Func()",
parameter: map[string]interface{}{"foo": foo},
want: "funk",
},
{
name: "Simple parameter call from pointer",
expression: "fooptr.String",
parameter: map[string]interface{}{"fooptr": &foo},
want: foo.String,
},
{
name: "Simple parameter function call from pointer",
expression: "fooptr.Func()",
parameter: map[string]interface{}{"fooptr": &foo},
want: "funk",
},
{
name: "Simple parameter call",
expression: `foo.String == "hi"`,
parameter: map[string]interface{}{"foo": foo},
want: false,
},
{
name: "Simple parameter call with modifier",
expression: `foo.String + "hi"`,
parameter: map[string]interface{}{"foo": foo},
want: foo.String + "hi",
},
{
name: "Simple parameter function call, two-arg return",
expression: `foo.Func2()`,
parameter: map[string]interface{}{"foo": foo},
want: "frink",
},
{
name: "Simple parameter function call, one arg",
expression: `foo.FuncArgStr("boop")`,
parameter: map[string]interface{}{"foo": foo},
want: "boop",
},
{
name: "Simple parameter function call, one arg",
expression: `foo.FuncArgStr("boop") + "hi"`,
parameter: map[string]interface{}{"foo": foo},
want: "boophi",
},
{
name: "Nested parameter function call",
expression: `foo.Nested.Dunk("boop")`,
parameter: map[string]interface{}{"foo": foo},
want: "boopdunk",
},
{
name: "Nested parameter call",
expression: "foo.Nested.Funk",
parameter: map[string]interface{}{"foo": foo},
want: "funkalicious",
},
{
name: "Nested map call",
expression: `foo.Nested.Map["a"]`,
parameter: map[string]interface{}{"foo": foo},
want: 1,
},
{
name: "Nested slice call",
expression: `foo.Nested.Slice[1]`,
parameter: map[string]interface{}{"foo": foo},
want: 2,
},
{
name: "Parameter call with + modifier",
expression: "1 + foo.Int",
parameter: map[string]interface{}{"foo": foo},
want: 102.0,
},
{
name: "Parameter string call with + modifier",
expression: `"woop" + (foo.String)`,
parameter: map[string]interface{}{"foo": foo},
want: "woopstring!",
},
{
name: "Parameter call with && operator",
expression: "true && foo.BoolFalse",
parameter: map[string]interface{}{"foo": foo},
want: false,
},
{
name: "Null coalesce nested parameter",
expression: "foo.Nil ?? false",
parameter: map[string]interface{}{"foo": foo},
want: false,
},
{
name: "input functions",
expression: "func1() + func2()",
parameter: map[string]interface{}{
"func1": func() float64 { return 2000 },
"func2": func() float64 { return 2001 },
},
want: 4001.0,
},
{
name: "input functions",
expression: "func1(date1) + func2(date2)",
parameter: map[string]interface{}{
"date1": func() interface{} {
y2k, _ := time.Parse("2006", "2000")
return y2k
}(),
"date2": func() interface{} {
y2k1, _ := time.Parse("2006", "2001")
return y2k1
}(),
},
extension: NewLanguage(
Function("func1", func(arguments ...interface{}) (interface{}, error) {
return float64(arguments[0].(time.Time).Year()), nil
}),
Function("func2", func(arguments ...interface{}) (interface{}, error) {
return float64(arguments[0].(time.Time).Year()), nil
}),
),
want: 4001.0,
},
{
name: "complex64 number as parameter",
expression: "complex64",
parameter: map[string]interface{}{
"complex64": complex64(0),
"complex128": complex128(0),
},
want: complex64(0),
},
{
name: "complex128 number as parameter",
expression: "complex128",
parameter: map[string]interface{}{
"complex64": complex64(0),
"complex128": complex128(0),
},
want: complex128(0),
},
{
name: "coalesce with undefined",
expression: "fooz ?? foo",
parameter: map[string]interface{}{
"foo": "bar",
},
want: "bar",
},
{
name: "map[interface{}]interface{}",
expression: "foo",
parameter: map[interface{}]interface{}{
"foo": "bar",
},
want: "bar",
},
{
name: "method on pointer type",
expression: "foo.PointerFunc()",
parameter: map[string]interface{}{
"foo": &dummyParameter{},
},
want: "point",
},
{
name: "custom selector",
expression: "hello.world",
parameter: "!",
extension: NewLanguage(Base(), VariableSelector(func(path Evaluables) Evaluable {
return func(c context.Context, v interface{}) (interface{}, error) {
keys, err := path.EvalStrings(c, v)
if err != nil {
return nil, err
}
return fmt.Sprintf("%s%s", strings.Join(keys, " "), v), nil
}
})),
want: "hello world!",
},
{
name: "map[int]int",
expression: `a[0] + a[2]`,
parameter: map[string]interface{}{
"a": map[int]int{0: 1, 2: 1},
},
want: 2.,
},
{
name: "map[int]string",
expression: `a[0] * a[2]`,
parameter: map[string]interface{}{
"a": map[int]string{0: "1", 2: "1"},
},
want: 1.,
},
{
name: "coalesce typed nil 0",
expression: `ProjectID ?? 0`,
parameter: struct {
ProjectID *uint
}{},
want: 0.,
},
{
name: "coalesce typed nil 99",
expression: `ProjectID ?? 99`,
parameter: struct {
ProjectID *uint
}{},
want: 99.,
},
{
name: "operator with typed nil 99",
expression: `ProjectID + 99`,
parameter: struct {
ProjectID *uint
}{},
want: "<nil>99",
},
{
name: "operator with typed nil if",
expression: `Flag ? 1 : 2`,
parameter: struct {
Flag *uint
}{},
want: 2.,
},
{
name: "Decimal math doesn't experience rounding error",
expression: "(x * 12.146) - y",
extension: decimalArithmetic,
parameter: map[string]interface{}{
"x": 12.5,
"y": -5,
},
want: decimal.NewFromFloat(156.825),
equalityFunc: decimalEqualityFunc,
},
{
name: "Decimal logical operators fractional difference",
expression: "((x * 12.146) - y) > 156.824999999",
extension: decimalArithmetic,
parameter: map[string]interface{}{
"x": 12.5,
"y": -5,
},
want: true,
},
{
name: "Decimal logical operators whole number difference",
expression: "((x * 12.146) - y) > 156",
extension: decimalArithmetic,
parameter: map[string]interface{}{
"x": 12.5,
"y": -5,
},
want: true,
},
{
name: "Decimal logical operators exact decimal match against GT",
expression: "((x * 12.146) - y) > 156.825",
extension: decimalArithmetic,
parameter: map[string]interface{}{
"x": 12.5,
"y": -5,
},
want: false,
},
{
name: "Decimal logical operators exact equality",
expression: "((x * 12.146) - y) == 156.825",
extension: decimalArithmetic,
parameter: map[string]interface{}{
"x": 12.5,
"y": -5,
},
want: true,
},
{
name: "Decimal mixes with string logic with force fail",
expression: `(((x * 12.146) - y) == 156.825) && a == "test" && !b && b`,
extension: decimalArithmetic,
parameter: map[string]interface{}{
"x": 12.5,
"y": -5,
"a": "test",
"b": false,
},
want: false,
},
{
name: "Typed map with function call",
expression: `foo.MapWithFunc.Sum("a")`,
parameter: map[string]interface{}{
"foo": foo,
},
want: 3,
},
{
name: "Types slice with function call",
expression: `foo.SliceWithFunc.Sum("a")`,
parameter: map[string]interface{}{
"foo": foo,
},
want: 2,
},
},
t,
)
}