diff --git a/pkg/engine/jmespath/arithmetic.go b/pkg/engine/jmespath/arithmetic.go index cc377f7d4a..67d88c221e 100644 --- a/pkg/engine/jmespath/arithmetic.go +++ b/pkg/engine/jmespath/arithmetic.go @@ -1,7 +1,6 @@ package jmespath import ( - "errors" "fmt" "math" "reflect" @@ -31,42 +30,24 @@ type Scalar struct { float64 } -var errTypeMismatch = errors.New("types mismatch") - func ParseArithemticOperands(arguments []interface{}, operator string) (Operand, Operand, error) { op := [2]Operand{nil, nil} - t := [2]int{0, 0} - for i := 0; i < 2; i++ { - tmp, err := validateArg(divide, arguments, i, reflect.Float64) - if err == nil { + if tmp, err := validateArg(divide, arguments, i, reflect.Float64); err == nil { var sc Scalar sc.float64 = tmp.Float() op[i] = sc - } - - tmp, err = validateArg(divide, arguments, i, reflect.String) - if err == nil { - var q Quantity - q.Quantity, err = resource.ParseQuantity(tmp.String()) - if err == nil { - op[i] = q - t[i] = 1 - } else { - var d Duration - d.Duration, err = time.ParseDuration(tmp.String()) - if err == nil { - op[i] = d - t[i] = 2 - } + } else if tmp, err = validateArg(divide, arguments, i, reflect.String); err == nil { + if q, err := resource.ParseQuantity(tmp.String()); err == nil { + op[i] = Quantity{Quantity: q} + } else if d, err := time.ParseDuration(tmp.String()); err == nil { + op[i] = Duration{Duration: d} } } } - - if op[0] == nil || op[1] == nil || t[0]|t[1] == 3 { + if op[0] == nil || op[1] == nil { return nil, nil, formatError(genericError, operator, "invalid operands") } - return op[0], op[1], nil } @@ -83,7 +64,7 @@ func (op1 Quantity) Add(op2 interface{}) (interface{}, error) { op1.Quantity.Add(v.Quantity) return op1.String(), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, add) } } @@ -92,7 +73,7 @@ func (op1 Duration) Add(op2 interface{}) (interface{}, error) { case Duration: return (op1.Duration + v.Duration).String(), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, add) } } @@ -101,7 +82,7 @@ func (op1 Scalar) Add(op2 interface{}) (interface{}, error) { case Scalar: return op1.float64 + v.float64, nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, add) } } @@ -111,7 +92,7 @@ func (op1 Quantity) Subtract(op2 interface{}) (interface{}, error) { op1.Quantity.Sub(v.Quantity) return op1.String(), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, subtract) } } @@ -120,7 +101,7 @@ func (op1 Duration) Subtract(op2 interface{}) (interface{}, error) { case Duration: return (op1.Duration - v.Duration).String(), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, subtract) } } @@ -129,7 +110,7 @@ func (op1 Scalar) Subtract(op2 interface{}) (interface{}, error) { case Scalar: return op1.float64 - v.float64, nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, subtract) } } @@ -154,7 +135,7 @@ func (op1 Quantity) Multiply(op2 interface{}) (interface{}, error) { prod.Mul(op1.Quantity.AsDec(), q.AsDec()) return resource.NewDecimalQuantity(prod, op1.Quantity.Format).String(), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, multiply) } } @@ -164,7 +145,7 @@ func (op1 Duration) Multiply(op2 interface{}) (interface{}, error) { seconds := op1.Seconds() * v.float64 return time.Duration(seconds * float64(time.Second)).String(), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, multiply) } } @@ -176,9 +157,9 @@ func (op1 Scalar) Multiply(op2 interface{}) (interface{}, error) { return v.Multiply(op1) case Duration: return v.Multiply(op1) + default: + return nil, formatError(typeMismatchError, multiply) } - - return nil, nil } // Quantity / Duration -> error @@ -215,7 +196,7 @@ func (op1 Quantity) Divide(op2 interface{}) (interface{}, error) { quo.QuoRound(op1.AsDec(), q.AsDec(), scale, inf.RoundDown) return resource.NewDecimalQuantity(quo, op1.Quantity.Format).String(), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, divide) } } @@ -233,7 +214,7 @@ func (op1 Duration) Divide(op2 interface{}) (interface{}, error) { seconds := op1.Seconds() / v.float64 return time.Duration(seconds * float64(time.Second)).String(), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, divide) } } @@ -245,7 +226,7 @@ func (op1 Scalar) Divide(op2 interface{}) (interface{}, error) { } return op1.float64 / v.float64, nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, divide) } } @@ -276,7 +257,7 @@ func (op1 Quantity) Modulo(op2 interface{}) (interface{}, error) { } return resource.NewQuantity(i1%i2, op1.Quantity.Format).String(), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, modulo) } } @@ -288,7 +269,7 @@ func (op1 Duration) Modulo(op2 interface{}) (interface{}, error) { } return (op1.Duration % v.Duration).String(), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, modulo) } } @@ -308,6 +289,6 @@ func (op1 Scalar) Modulo(op2 interface{}) (interface{}, error) { } return float64(val1 % val2), nil default: - return nil, errTypeMismatch + return nil, formatError(typeMismatchError, modulo) } } diff --git a/pkg/engine/jmespath/arithmetic_test.go b/pkg/engine/jmespath/arithmetic_test.go new file mode 100644 index 0000000000..b9b4f93ff5 --- /dev/null +++ b/pkg/engine/jmespath/arithmetic_test.go @@ -0,0 +1,707 @@ +package jmespath + +import ( + "reflect" + "testing" + + "gotest.tools/assert" +) + +func Test_Add(t *testing.T) { + testCases := []struct { + name string + test string + expectedResult interface{} + err bool + retFloat bool + }{ + // Scalar + { + name: "Scalar + Scalar -> Scalar", + test: "add(`12`, `13`)", + expectedResult: 25.0, + retFloat: true, + }, + { + name: "Scalar + Duration -> error", + test: "add('12', '13s')", + err: true, + }, + { + name: "Scalar + Quantity -> error", + test: "add(`12`, '13Ki')", + err: true, + }, + { + name: "Scalar + Quantity -> error", + test: "add(`12`, '13')", + err: true, + }, + // Quantity + { + name: "Quantity + Quantity -> Quantity", + test: "add('12Ki', '13Ki')", + expectedResult: `25Ki`, + }, + { + name: "Quantity + Quantity -> Quantity", + test: "add('12Ki', '13')", + expectedResult: `12301`, + }, + { + name: "Quantity + Duration -> error", + test: "add('12Ki', '13s')", + err: true, + }, + { + name: "Quantity + Scalar -> error", + test: "add('12Ki', `13`)", + err: true, + }, + // Duration + { + name: "Duration + Duration -> Duration", + test: "add('12s', '13s')", + expectedResult: `25s`, + }, + { + name: "Duration + Scalar -> error", + test: "add('12s', `13`)", + err: true, + }, + { + name: "Duration + Quantity -> error", + test: "add('12s', '13Ki')", + err: true, + }, + { + name: "Duration + Quantity -> error", + test: "add('12s', '13')", + err: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + jp, err := New(tc.test) + assert.NilError(t, err) + + result, err := jp.Search("") + if !tc.err { + assert.NilError(t, err) + } else { + assert.Assert(t, err != nil) + return + } + + if tc.retFloat { + equal, ok := result.(float64) + assert.Assert(t, ok) + assert.Equal(t, equal, tc.expectedResult.(float64)) + } else { + equal, ok := result.(string) + assert.Assert(t, ok) + assert.Equal(t, equal, tc.expectedResult.(string)) + } + }) + } +} + +func Test_Subtract(t *testing.T) { + testCases := []struct { + name string + test string + expectedResult interface{} + err bool + retFloat bool + }{ + // Scalar + { + name: "Scalar - Scalar -> Scalar", + test: "subtract(`12`, `13`)", + expectedResult: -1.0, + retFloat: true, + }, + { + name: "Scalar - Duration -> error", + test: "subtract('12', '13s')", + err: true, + }, + { + name: "Scalar - Quantity -> error", + test: "subtract(`12`, '13Ki')", + err: true, + }, + { + name: "Scalar - Quantity -> error", + test: "subtract(`12`, '13')", + err: true, + }, + // Quantity + { + name: "Quantity - Quantity -> Quantity", + test: "subtract('12Ki', '13Ki')", + expectedResult: `-1Ki`, + }, + { + name: "Quantity - Quantity -> Quantity", + test: "subtract('12Ki', '13')", + expectedResult: `12275`, + }, + { + name: "Quantity - Duration -> error", + test: "subtract('12Ki', '13s')", + err: true, + }, + { + name: "Quantity - Scalar -> error", + test: "subtract('12Ki', `13`)", + err: true, + }, + // Duration + { + name: "Duration - Duration -> Duration", + test: "subtract('12s', '13s')", + expectedResult: `-1s`, + }, + { + name: "Duration - Scalar -> error", + test: "subtract('12s', `13`)", + err: true, + }, + { + name: "Duration - Quantity -> error", + test: "subtract('12s', '13Ki')", + err: true, + }, + { + name: "Duration - Quantity -> error", + test: "subtract('12s', '13')", + err: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + jp, err := New(tc.test) + assert.NilError(t, err) + + result, err := jp.Search("") + if !tc.err { + assert.NilError(t, err) + } else { + assert.Assert(t, err != nil) + return + } + + if tc.retFloat { + equal, ok := result.(float64) + assert.Assert(t, ok) + assert.Equal(t, equal, tc.expectedResult.(float64)) + } else { + equal, ok := result.(string) + assert.Assert(t, ok) + assert.Equal(t, equal, tc.expectedResult.(string)) + } + }) + } +} + +func Test_Multiply(t *testing.T) { + testCases := []struct { + name string + test string + expectedResult interface{} + err bool + retFloat bool + }{ + // Quantity + { + name: "Quantity * Scalar -> Quantity", + test: "multiply('12Ki', `2`)", + expectedResult: `24Ki`, + }, + { + name: "Quantity * Quantity -> error", + test: "multiply('12Ki', '12Ki')", + err: true, + }, + { + name: "Quantity * Quantity -> error", + test: "multiply('12Ki', '12')", + err: true, + }, + { + name: "Quantity * Duration -> error", + test: "multiply('12Ki', '12s')", + err: true, + }, + // Duration + { + name: "Duration * Scalar -> Duration", + test: "multiply('12s', `2`)", + expectedResult: `24s`, + }, + { + name: "Duration * Quantity -> error", + test: "multiply('12s', '12Ki')", + err: true, + }, + { + name: "Duration * Quantity -> error", + test: "multiply('12s', '12')", + err: true, + }, + { + name: "Duration * Duration -> error", + test: "multiply('12s', '12s')", + err: true, + }, + // Scalar + { + name: "Scalar * Scalar -> Scalar", + test: "multiply(`2.5`, `2.5`)", + expectedResult: 2.5 * 2.5, + retFloat: true, + }, + { + name: "Scalar * Quantity -> Quantity", + test: "multiply(`2.5`, '12Ki')", + expectedResult: "30Ki", + }, + { + name: "Scalar * Quantity -> Quantity", + test: "multiply(`2.5`, '12')", + expectedResult: "30", + }, + { + name: "Scalar * Duration -> Duration", + test: "multiply(`2.5`, '40s')", + expectedResult: "1m40s", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + jp, err := New(tc.test) + assert.NilError(t, err) + + result, err := jp.Search("") + if !tc.err { + assert.NilError(t, err) + } else { + assert.Assert(t, err != nil) + return + } + + if tc.retFloat { + equal, ok := result.(float64) + assert.Assert(t, ok) + assert.Equal(t, equal, tc.expectedResult.(float64)) + } else { + equal, ok := result.(string) + assert.Assert(t, ok) + assert.Equal(t, equal, tc.expectedResult.(string)) + } + }) + } +} + +func Test_Divide(t *testing.T) { + testCases := []struct { + name string + test string + expectedResult interface{} + err bool + retFloat bool + }{ + // Quantity + { + name: "Quantity / Quantity -> Scalar", + test: "divide('256M', '256M')", + expectedResult: 1.0, + retFloat: true, + }, + { + name: "Quantity / Quantity -> Scalar", + test: "divide('512M', '256M')", + expectedResult: 2.0, + retFloat: true, + }, + { + name: "Quantity / Quantity -> Scalar", + test: "divide('8', '3')", + expectedResult: 8.0 / 3.0, + retFloat: true, + }, + { + name: "Quantity / Quantity -> Scalar", + test: "divide('128M', '256M')", + expectedResult: 0.5, + retFloat: true, + }, + { + name: "Quantity / Scalar -> Quantity", + test: "divide('12Ki', `3`)", + expectedResult: "4Ki", + }, + { + name: "Quantity / Quantity -> Scalar", + test: "divide('12Ki', '2Ki')", + expectedResult: 6.0, + retFloat: true, + }, + { + name: "Quantity / Quantity -> Scalar", + test: "divide('12Ki', '200')", + expectedResult: 61.44, + retFloat: true, + }, + { + name: "Quantity / Duration -> error", + test: "divide('12Ki', '2s')", + err: true, + }, + // Duration + { + name: "Duration / Scalar -> Duration", + test: "divide('12s', `3`)", + expectedResult: "4s", + }, + { + name: "Duration / Duration -> Scalar", + test: "divide('12s', '5s')", + expectedResult: 2.4, + retFloat: true, + }, + { + name: "Duration / Quantity -> error", + test: "divide('12s', '4Ki')", + err: true, + }, + { + name: "Duration / Quantity -> error", + test: "divide('12s', '4')", + err: true, + }, + // Scalar + { + name: "Scalar / Scalar -> Scalar", + test: "divide(`14`, `3`)", + expectedResult: 4.666666666666667, + retFloat: true, + }, + { + name: "Scalar / Duration -> error", + test: "divide(`14`, '5s')", + err: true, + }, + { + name: "Scalar / Quantity -> error", + test: "divide(`14`, '5Ki')", + err: true, + }, + { + name: "Scalar / Quantity -> error", + test: "divide(`14`, '5')", + err: true, + }, + // Divide by 0 + { + name: "Scalar / Zero -> error", + test: "divide(`14`, `0`)", + err: true, + }, + { + name: "Quantity / Zero -> error", + test: "divide('4Ki', `0`)", + err: true, + }, + { + name: "Quantity / Zero -> error", + test: "divide('4Ki', '0Ki')", + err: true, + }, + { + name: "Quantity / Zero -> error", + test: "divide('4', `0`)", + err: true, + }, + { + name: "Quantity / Zero -> error", + test: "divide('4', '0')", + err: true, + }, + { + name: "Duration / Zero -> error", + test: "divide('4s', `0`)", + err: true, + }, + { + name: "Duration / Zero -> error", + test: "divide('4s', '0s')", + err: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + jp, err := New(tc.test) + assert.NilError(t, err) + + result, err := jp.Search("") + if !tc.err { + assert.NilError(t, err) + } else { + assert.Assert(t, err != nil) + return + } + + if tc.retFloat { + equal, ok := result.(float64) + assert.Assert(t, ok) + assert.Equal(t, equal, tc.expectedResult.(float64)) + } else { + equal, ok := result.(string) + assert.Assert(t, ok) + assert.Equal(t, equal, tc.expectedResult.(string)) + } + }) + } +} + +func Test_Modulo(t *testing.T) { + testCases := []struct { + name string + test string + expectedResult interface{} + err bool + retFloat bool + }{ + // Quantity + { + name: "Quantity % Duration -> error", + test: "modulo('12', '13s')", + err: true, + }, + { + name: "Quantity % Duration -> error", + test: "modulo('12Ki', '13s')", + err: true, + }, + { + name: "Quantity % Scalar -> error", + test: "modulo('12Ki', `13`)", + err: true, + }, + { + name: "Quantity % Quantity -> Quantity", + test: "modulo('12Ki', '5Ki')", + expectedResult: `2Ki`, + }, + // Duration + { + name: "Duration % Quantity -> error", + test: "modulo('13s', '12')", + err: true, + }, + { + name: "Duration % Quantity -> error", + test: "modulo('13s', '12Ki')", + err: true, + }, + { + name: "Duration % Duration -> Duration", + test: "modulo('13s', '2s')", + expectedResult: `1s`, + }, + { + name: "Duration % Scalar -> error", + test: "modulo('13s', `2`)", + err: true, + }, + // Scalar + { + name: "Scalar % Quantity -> error", + test: "modulo(`13`, '12')", + err: true, + }, + { + name: "Scalar % Quantity -> error", + test: "modulo(`13`, '12Ki')", + err: true, + }, + { + name: "Scalar % Duration -> error", + test: "modulo(`13`, '5s')", + err: true, + }, + { + name: "Scalar % Scalar -> Scalar", + test: "modulo(`13`, `5`)", + expectedResult: 3.0, + retFloat: true, + }, + // Modulo by 0 + { + name: "Scalar % Zero -> error", + test: "modulo(`14`, `0`)", + err: true, + }, + { + name: "Quantity % Zero -> error", + test: "modulo('4Ki', `0`)", + err: true, + }, + { + name: "Quantity % Zero -> error", + test: "modulo('4Ki', '0Ki')", + err: true, + }, + { + name: "Quantity % Zero -> error", + test: "modulo('4', `0`)", + err: true, + }, + { + name: "Quantity % Zero -> error", + test: "modulo('4', '0')", + err: true, + }, + { + name: "Duration % Zero -> error", + test: "modulo('4s', `0`)", + err: true, + }, + { + name: "Duration % Zero -> error", + test: "modulo('4s', '0s')", + err: true, + }, + // Modulo with non int values + { + name: "Quantity % Non int -> error", + test: "modulo('4', '1.5')", + err: true, + }, + { + name: "Non int % Quantity -> error", + test: "modulo('4.5', '1')", + err: true, + }, + { + name: "Scalar % Non int -> error", + test: "modulo(`14`, `1.5`)", + err: true, + }, + { + name: "Non int % Scalar -> error", + test: "modulo(`14.5`, `2`)", + err: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + jp, err := New(tc.test) + assert.NilError(t, err) + + result, err := jp.Search("") + if !tc.err { + assert.NilError(t, err) + } else { + assert.Assert(t, err != nil) + return + } + + if tc.retFloat { + equal, ok := result.(float64) + assert.Assert(t, ok) + assert.Equal(t, equal, tc.expectedResult.(float64)) + } else { + equal, ok := result.(string) + assert.Assert(t, ok) + assert.Equal(t, equal, tc.expectedResult.(string)) + } + }) + } +} + +func TestScalar_Multiply(t *testing.T) { + type fields struct { + float64 float64 + } + type args struct { + op2 interface{} + } + tests := []struct { + name string + fields fields + args args + want interface{} + wantErr bool + }{{ + fields: fields{ + float64: 123, + }, + args: args{ + op2: true, + }, + wantErr: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op1 := Scalar{ + float64: tt.fields.float64, + } + got, err := op1.Multiply(tt.args.op2) + if (err != nil) != tt.wantErr { + t.Errorf("Scalar.Multiply() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Scalar.Multiply() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParseArithemticOperands(t *testing.T) { + type args struct { + arguments []interface{} + operator string + } + tests := []struct { + name string + args args + want Operand + want1 Operand + wantErr bool + }{{ + args: args{ + arguments: []interface{}{ + true, + 1.0, + }, + }, + wantErr: true, + }, { + args: args{ + arguments: []interface{}{ + 1.0, + true, + }, + }, + wantErr: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := ParseArithemticOperands(tt.args.arguments, tt.args.operator) + if (err != nil) != tt.wantErr { + t.Errorf("ParseArithemticOperands() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseArithemticOperands() got = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got1, tt.want1) { + t.Errorf("ParseArithemticOperands() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/pkg/engine/jmespath/error.go b/pkg/engine/jmespath/error.go index 3153ad75c3..be7596a896 100644 --- a/pkg/engine/jmespath/error.go +++ b/pkg/engine/jmespath/error.go @@ -11,6 +11,7 @@ const ( argOutOfBoundsError = errorPrefix + "%d argument is out of bounds (%d)" zeroDivisionError = errorPrefix + "Zero divisor passed" nonIntModuloError = errorPrefix + "Non-integer argument(s) passed for modulo" + typeMismatchError = errorPrefix + "Types mismatch" ) func formatError(format string, function string, values ...interface{}) error { diff --git a/pkg/engine/jmespath/functions_test.go b/pkg/engine/jmespath/functions_test.go index fc6328139f..6189f2e1ac 100644 --- a/pkg/engine/jmespath/functions_test.go +++ b/pkg/engine/jmespath/functions_test.go @@ -538,600 +538,6 @@ func Test_LabelMatch(t *testing.T) { } } -func Test_Add(t *testing.T) { - testCases := []struct { - name string - test string - expectedResult interface{} - err bool - retFloat bool - }{ - // Scalar - { - name: "Scalar + Scalar -> Scalar", - test: "add(`12`, `13`)", - expectedResult: 25.0, - retFloat: true, - }, - { - name: "Scalar + Duration -> error", - test: "add('12', '13s')", - err: true, - }, - { - name: "Scalar + Quantity -> error", - test: "add(`12`, '13Ki')", - err: true, - }, - { - name: "Scalar + Quantity -> error", - test: "add(`12`, '13')", - err: true, - }, - // Quantity - { - name: "Quantity + Quantity -> Quantity", - test: "add('12Ki', '13Ki')", - expectedResult: `25Ki`, - }, - { - name: "Quantity + Quantity -> Quantity", - test: "add('12Ki', '13')", - expectedResult: `12301`, - }, - { - name: "Quantity + Duration -> error", - test: "add('12Ki', '13s')", - err: true, - }, - { - name: "Quantity + Scalar -> error", - test: "add('12Ki', `13`)", - err: true, - }, - // Duration - { - name: "Duration + Duration -> Duration", - test: "add('12s', '13s')", - expectedResult: `25s`, - }, - { - name: "Duration + Scalar -> error", - test: "add('12s', '13')", - err: true, - }, - { - name: "Duration + Quantity -> error", - test: "add('12s', '13Ki')", - err: true, - }, - { - name: "Duration + Quantity -> error", - test: "add('12s', '13')", - err: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - jp, err := New(tc.test) - assert.NilError(t, err) - - result, err := jp.Search("") - if !tc.err { - assert.NilError(t, err) - } else { - assert.Assert(t, err != nil) - return - } - - if tc.retFloat { - equal, ok := result.(float64) - assert.Assert(t, ok) - assert.Equal(t, equal, tc.expectedResult.(float64)) - } else { - equal, ok := result.(string) - assert.Assert(t, ok) - assert.Equal(t, equal, tc.expectedResult.(string)) - } - }) - } -} - -func Test_Subtract(t *testing.T) { - testCases := []struct { - name string - test string - expectedResult interface{} - err bool - retFloat bool - }{ - // Scalar - { - name: "Scalar - Scalar -> Scalar", - test: "subtract(`12`, `13`)", - expectedResult: -1.0, - retFloat: true, - }, - { - name: "Scalar - Duration -> error", - test: "subtract('12', '13s')", - err: true, - }, - { - name: "Scalar - Quantity -> error", - test: "subtract(`12`, '13Ki')", - err: true, - }, - { - name: "Scalar - Quantity -> error", - test: "subtract(`12`, '13')", - err: true, - }, - // Quantity - { - name: "Quantity - Quantity -> Quantity", - test: "subtract('12Ki', '13Ki')", - expectedResult: `-1Ki`, - }, - { - name: "Quantity - Quantity -> Quantity", - test: "subtract('12Ki', '13')", - expectedResult: `12275`, - }, - { - name: "Quantity - Duration -> error", - test: "subtract('12Ki', '13s')", - err: true, - }, - { - name: "Quantity - Scalar -> error", - test: "subtract('12Ki', `13`)", - err: true, - }, - // Duration - { - name: "Duration - Duration -> Duration", - test: "subtract('12s', '13s')", - expectedResult: `-1s`, - }, - { - name: "Duration - Scalar -> error", - test: "subtract('12s', '13')", - err: true, - }, - { - name: "Duration - Quantity -> error", - test: "subtract('12s', '13Ki')", - err: true, - }, - { - name: "Duration - Quantity -> error", - test: "subtract('12s', '13')", - err: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - jp, err := New(tc.test) - assert.NilError(t, err) - - result, err := jp.Search("") - if !tc.err { - assert.NilError(t, err) - } else { - assert.Assert(t, err != nil) - return - } - - if tc.retFloat { - equal, ok := result.(float64) - assert.Assert(t, ok) - assert.Equal(t, equal, tc.expectedResult.(float64)) - } else { - equal, ok := result.(string) - assert.Assert(t, ok) - assert.Equal(t, equal, tc.expectedResult.(string)) - } - }) - } -} - -func Test_Multiply(t *testing.T) { - testCases := []struct { - name string - test string - expectedResult interface{} - err bool - retFloat bool - }{ - // Quantity - { - name: "Quantity * Scalar -> Quantity", - test: "multiply('12Ki', `2`)", - expectedResult: `24Ki`, - }, - { - name: "Quantity * Quantity -> error", - test: "multiply('12Ki', '12Ki')", - err: true, - }, - { - name: "Quantity * Quantity -> error", - test: "multiply('12Ki', '12')", - err: true, - }, - { - name: "Quantity * Duration -> error", - test: "multiply('12Ki', '12s')", - err: true, - }, - // Duration - { - name: "Duration * Scalar -> Duration", - test: "multiply('12s', `2`)", - expectedResult: `24s`, - }, - { - name: "Duration * Quantity -> error", - test: "multiply('12s', '12Ki')", - err: true, - }, - { - name: "Duration * Quantity -> error", - test: "multiply('12s', '12')", - err: true, - }, - { - name: "Duration * Duration -> error", - test: "multiply('12s', '12s')", - err: true, - }, - // Scalar - { - name: "Scalar * Scalar -> Scalar", - test: "multiply(`2.5`, `2.5`)", - expectedResult: 2.5 * 2.5, - retFloat: true, - }, - { - name: "Scalar * Quantity -> Quantity", - test: "multiply(`2.5`, '12Ki')", - expectedResult: "30Ki", - }, - { - name: "Scalar * Quantity -> Quantity", - test: "multiply(`2.5`, '12')", - expectedResult: "30", - }, - { - name: "Scalar * Duration -> Duration", - test: "multiply(`2.5`, '40s')", - expectedResult: "1m40s", - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - jp, err := New(tc.test) - assert.NilError(t, err) - - result, err := jp.Search("") - if !tc.err { - assert.NilError(t, err) - } else { - assert.Assert(t, err != nil) - return - } - - if tc.retFloat { - equal, ok := result.(float64) - assert.Assert(t, ok) - assert.Equal(t, equal, tc.expectedResult.(float64)) - } else { - equal, ok := result.(string) - assert.Assert(t, ok) - assert.Equal(t, equal, tc.expectedResult.(string)) - } - }) - } -} - -func Test_Divide(t *testing.T) { - testCases := []struct { - name string - test string - expectedResult interface{} - err bool - retFloat bool - }{ - // Quantity - { - name: "Quantity / Quantity -> Scalar", - test: "divide('256M', '256M')", - expectedResult: 1.0, - retFloat: true, - }, - { - name: "Quantity / Quantity -> Scalar", - test: "divide('512M', '256M')", - expectedResult: 2.0, - retFloat: true, - }, - { - name: "Quantity / Quantity -> Scalar", - test: "divide('8', '3')", - expectedResult: 8.0 / 3.0, - retFloat: true, - }, - { - name: "Quantity / Quantity -> Scalar", - test: "divide('128M', '256M')", - expectedResult: 0.5, - retFloat: true, - }, - { - name: "Quantity / Scalar -> Quantity", - test: "divide('12Ki', `3`)", - expectedResult: "4Ki", - }, - { - name: "Quantity / Quantity -> Scalar", - test: "divide('12Ki', '2Ki')", - expectedResult: 6.0, - retFloat: true, - }, - { - name: "Quantity / Quantity -> Scalar", - test: "divide('12Ki', '200')", - expectedResult: 61.44, - retFloat: true, - }, - { - name: "Quantity / Duration -> error", - test: "divide('12Ki', '2s')", - err: true, - }, - // Duration - { - name: "Duration / Scalar -> Duration", - test: "divide('12s', `3`)", - expectedResult: "4s", - }, - { - name: "Duration / Duration -> Scalar", - test: "divide('12s', '5s')", - expectedResult: 2.4, - retFloat: true, - }, - { - name: "Duration / Quantity -> error", - test: "divide('12s', '4Ki')", - err: true, - }, - { - name: "Duration / Quantity -> error", - test: "divide('12s', '4')", - err: true, - }, - // Scalar - { - name: "Scalar / Scalar -> Scalar", - test: "divide(`14`, `3`)", - expectedResult: 4.666666666666667, - retFloat: true, - }, - { - name: "Scalar / Duration -> error", - test: "divide(`14`, '5s')", - err: true, - }, - { - name: "Scalar / Quantity -> error", - test: "divide(`14`, '5Ki')", - err: true, - }, - { - name: "Scalar / Quantity -> error", - test: "divide(`14`, '5')", - err: true, - }, - // Divide by 0 - { - name: "Scalar / Zero -> error", - test: "divide(`14`, `0`)", - err: true, - }, - { - name: "Quantity / Zero -> error", - test: "divide('4Ki', `0`)", - err: true, - }, - { - name: "Quantity / Zero -> error", - test: "divide('4Ki', '0Ki')", - err: true, - }, - { - name: "Quantity / Zero -> error", - test: "divide('4', `0`)", - err: true, - }, - { - name: "Quantity / Zero -> error", - test: "divide('4', '0')", - err: true, - }, - { - name: "Duration / Zero -> error", - test: "divide('4s', `0`)", - err: true, - }, - { - name: "Duration / Zero -> error", - test: "divide('4s', '0s')", - err: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - jp, err := New(tc.test) - assert.NilError(t, err) - - result, err := jp.Search("") - if !tc.err { - assert.NilError(t, err) - } else { - assert.Assert(t, err != nil) - return - } - - if tc.retFloat { - equal, ok := result.(float64) - assert.Assert(t, ok) - assert.Equal(t, equal, tc.expectedResult.(float64)) - } else { - equal, ok := result.(string) - assert.Assert(t, ok) - assert.Equal(t, equal, tc.expectedResult.(string)) - } - }) - } -} - -func Test_Modulo(t *testing.T) { - testCases := []struct { - name string - test string - expectedResult interface{} - err bool - retFloat bool - }{ - // Quantity - { - name: "Quantity % Duration -> error", - test: "modulo('12', '13s')", - err: true, - }, - { - name: "Quantity % Duration -> error", - test: "modulo('12Ki', '13s')", - err: true, - }, - { - name: "Quantity % Scalar -> error", - test: "modulo('12Ki', `13`)", - err: true, - }, - { - name: "Quantity % Quantity -> Quantity", - test: "modulo('12Ki', '5Ki')", - expectedResult: `2Ki`, - }, - // Duration - { - name: "Duration % Quantity -> error", - test: "modulo('13s', '12')", - err: true, - }, - { - name: "Duration % Quantity -> error", - test: "modulo('13s', '12Ki')", - err: true, - }, - { - name: "Duration % Duration -> Duration", - test: "modulo('13s', '2s')", - expectedResult: `1s`, - }, - { - name: "Duration % Scalar -> error", - test: "modulo('13s', `2`)", - err: true, - }, - // Scalar - { - name: "Scalar % Quantity -> error", - test: "modulo(`13`, '12')", - err: true, - }, - { - name: "Scalar % Quantity -> error", - test: "modulo(`13`, '12Ki')", - err: true, - }, - { - name: "Scalar % Duration -> error", - test: "modulo(`13`, '5s')", - err: true, - }, - { - name: "Scalar % Scalar -> Scalar", - test: "modulo(`13`, `5`)", - expectedResult: 3.0, - retFloat: true, - }, - // Modulo by 0 - { - name: "Scalar % Zero -> error", - test: "modulo(`14`, `0`)", - err: true, - }, - { - name: "Quantity % Zero -> error", - test: "modulo('4Ki', `0`)", - err: true, - }, - { - name: "Quantity % Zero -> error", - test: "modulo('4Ki', '0Ki')", - err: true, - }, - { - name: "Quantity % Zero -> error", - test: "modulo('4', `0`)", - err: true, - }, - { - name: "Quantity % Zero -> error", - test: "modulo('4', '0')", - err: true, - }, - { - name: "Duration % Zero -> error", - test: "modulo('4s', `0`)", - err: true, - }, - { - name: "Duration % Zero -> error", - test: "modulo('4s', '0s')", - err: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - jp, err := New(tc.test) - assert.NilError(t, err) - - result, err := jp.Search("") - if !tc.err { - assert.NilError(t, err) - } else { - assert.Assert(t, err != nil) - return - } - - if tc.retFloat { - equal, ok := result.(float64) - assert.Assert(t, ok) - assert.Equal(t, equal, tc.expectedResult.(float64)) - } else { - equal, ok := result.(string) - assert.Assert(t, ok) - assert.Equal(t, equal, tc.expectedResult.(string)) - } - }) - } -} - func Test_Base64Decode(t *testing.T) { jp, err := New("base64_decode('SGVsbG8sIHdvcmxkIQ==')") assert.NilError(t, err)