From d9272d9b938158e55bb5ffe6f15df6e7ad2ca6f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Mon, 5 Dec 2022 12:15:32 +0100 Subject: [PATCH] refactor: jmespath arithmetic operations (#5544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: add and subtract jmespath operations Signed-off-by: Charles-Edouard Brétéché * fix tests Signed-off-by: Charles-Edouard Brétéché * fix tests Signed-off-by: Charles-Edouard Brétéché * fix multiply Signed-off-by: Charles-Edouard Brétéché * divide Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * modulo Signed-off-by: Charles-Edouard Brétéché Signed-off-by: Charles-Edouard Brétéché --- pkg/engine/jmespath/arithmetic.go | 331 +++++---------- pkg/engine/jmespath/functions_test.go | 590 ++++++++++++++------------ 2 files changed, 423 insertions(+), 498 deletions(-) diff --git a/pkg/engine/jmespath/arithmetic.go b/pkg/engine/jmespath/arithmetic.go index 34b754b584..379e9ec5c7 100644 --- a/pkg/engine/jmespath/arithmetic.go +++ b/pkg/engine/jmespath/arithmetic.go @@ -1,6 +1,7 @@ package jmespath import ( + "errors" "fmt" "math" "reflect" @@ -31,6 +32,8 @@ 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} @@ -68,53 +71,39 @@ func ParseArithemticOperands(arguments []interface{}, operator string) (Operand, return op[0], op[1], nil } +// Quantity +|- Quantity -> Quantity +// Quantity +|- Duration|Scalar -> error +// Duration +|- Duration -> Duration +// Duration +|- Quantity|Scalar -> error +// Scalar +|- Scalar -> Scalar +// Scalar +|- Quantity|Duration -> error + func (op1 Quantity) Add(op2 interface{}) (interface{}, error) { switch v := op2.(type) { case Quantity: op1.Quantity.Add(v.Quantity) return op1.String(), nil - case Scalar: - q, err := resource.ParseQuantity(fmt.Sprintf("%v", v.float64)) - if err != nil { - return nil, err - } - op1.Quantity.Add(q) - return op1.String(), nil + default: + return nil, errTypeMismatch } - - return nil, nil } func (op1 Duration) Add(op2 interface{}) (interface{}, error) { - var sum int64 - switch v := op2.(type) { case Duration: - sum = op1.Nanoseconds() + v.Nanoseconds() - case Scalar: - // Converting the duration to nanoseconds for more precision - sum = op1.Nanoseconds() + int64(v.float64*math.Pow10(9)) + return (op1.Duration + v.Duration).String(), nil + default: + return nil, errTypeMismatch } - - res, err := time.ParseDuration(fmt.Sprintf("%vns", sum)) - if err != nil { - return nil, err - } - - return res.String(), nil } func (op1 Scalar) Add(op2 interface{}) (interface{}, error) { switch v := op2.(type) { case Scalar: return op1.float64 + v.float64, nil - case Quantity: - return v.Add(op1) - case Duration: - return v.Add(op1) + default: + return nil, errTypeMismatch } - - return nil, nil } func (op1 Quantity) Subtract(op2 interface{}) (interface{}, error) { @@ -122,64 +111,41 @@ func (op1 Quantity) Subtract(op2 interface{}) (interface{}, error) { case Quantity: op1.Quantity.Sub(v.Quantity) return op1.String(), nil - case Scalar: - q, err := resource.ParseQuantity(fmt.Sprintf("%v", v.float64)) - if err != nil { - return nil, err - } - op1.Quantity.Sub(q) - return op1.String(), nil + default: + return nil, errTypeMismatch } - - return nil, nil } func (op1 Duration) Subtract(op2 interface{}) (interface{}, error) { - var diff int64 - switch v := op2.(type) { case Duration: - diff = op1.Nanoseconds() - v.Nanoseconds() - case Scalar: - // Converting the duration to nanoseconds for more precision - diff = op1.Nanoseconds() - int64(v.float64*math.Pow10(9)) + return (op1.Duration - v.Duration).String(), nil + default: + return nil, errTypeMismatch } - - res, err := time.ParseDuration(fmt.Sprintf("%vns", diff)) - if err != nil { - return nil, err - } - - return res.String(), nil } func (op1 Scalar) Subtract(op2 interface{}) (interface{}, error) { switch v := op2.(type) { case Scalar: return op1.float64 - v.float64, nil - case Quantity: - v.Neg() - return v.Add(op1) - case Duration: - // Converting the duration to nanoseconds for more precision - diff := int64(op1.float64*math.Pow10(9)) - v.Nanoseconds() - res, err := time.ParseDuration(fmt.Sprintf("%vns", diff)) - if err != nil { - return nil, err - } - - return res.String(), nil + default: + return nil, errTypeMismatch } - - return nil, nil } +// Quantity * Quantity|Duration -> error +// Quantity * Scalar -> Quantity + +// Duration * Quantity|Duration -> error +// Duration * Scalar -> Duration + +// Scalar * Scalar -> Scalar +// Scalar * Quantity -> Quantity +// Scalar * Duration -> Duration + func (op1 Quantity) Multiply(op2 interface{}) (interface{}, error) { switch v := op2.(type) { - case Quantity: - var prod inf.Dec - prod.Mul(op1.Quantity.AsDec(), v.Quantity.AsDec()) - return resource.NewDecimalQuantity(prod, v.Quantity.Format).String(), nil case Scalar: q, err := resource.ParseQuantity(fmt.Sprintf("%v", v.float64)) if err != nil { @@ -188,27 +154,19 @@ func (op1 Quantity) Multiply(op2 interface{}) (interface{}, error) { var prod inf.Dec prod.Mul(op1.Quantity.AsDec(), q.AsDec()) return resource.NewDecimalQuantity(prod, op1.Quantity.Format).String(), nil + default: + return nil, errTypeMismatch } - - return nil, nil } func (op1 Duration) Multiply(op2 interface{}) (interface{}, error) { - var prod float64 - switch v := op2.(type) { - case Duration: - prod = op1.Seconds() * v.Seconds() case Scalar: - prod = op1.Seconds() * v.float64 + seconds := op1.Seconds() * v.float64 + return time.Duration(seconds * float64(time.Second)).String(), nil + default: + return nil, errTypeMismatch } - - res, err := time.ParseDuration(fmt.Sprintf("%.9fs", prod)) - if err != nil { - return nil, err - } - - return res.String(), nil } func (op1 Scalar) Multiply(op2 interface{}) (interface{}, error) { @@ -224,6 +182,18 @@ func (op1 Scalar) Multiply(op2 interface{}) (interface{}, error) { return nil, nil } +// Quantity / Duration -> error +// Quantity / Quantity -> Scalar +// Quantity / Scalar -> Quantity + +// Duration / Quantity -> error +// Duration / Duration -> Scalar +// Duration / Scalar -> Duration + +// Scalar / Scalar -> Scalar +// Scalar / Quantity -> error +// Scalar / Duration -> error + func (op1 Quantity) Divide(op2 interface{}) (interface{}, error) { switch v := op2.(type) { case Quantity: @@ -242,42 +212,31 @@ func (op1 Quantity) Divide(op2 interface{}) (interface{}, error) { if err != nil { return nil, err } - var quo inf.Dec scale := inf.Scale(math.Max(float64(op1.AsDec().Scale()), float64(q.AsDec().Scale()))) quo.QuoRound(op1.AsDec(), q.AsDec(), scale, inf.RoundDown) return resource.NewDecimalQuantity(quo, op1.Quantity.Format).String(), nil + default: + return nil, errTypeMismatch } - - return nil, nil } func (op1 Duration) Divide(op2 interface{}) (interface{}, error) { - var quo float64 - switch v := op2.(type) { case Duration: if v.Seconds() == 0 { return nil, fmt.Errorf(undefinedQuoError, divide) } - - quo = op1.Seconds() / v.Seconds() - return quo, nil + return op1.Seconds() / v.Seconds(), nil case Scalar: if v.float64 == 0 { return nil, fmt.Errorf(undefinedQuoError, divide) } - - quo = op1.Seconds() / v.float64 - res, err := time.ParseDuration(fmt.Sprintf("%.9fs", quo)) - if err != nil { - return nil, err - } - - return res.String(), nil + seconds := op1.Seconds() / v.float64 + return time.Duration(seconds * float64(time.Second)).String(), nil + default: + return nil, errTypeMismatch } - - return nil, nil } func (op1 Scalar) Divide(op2 interface{}) (interface{}, error) { @@ -286,99 +245,53 @@ func (op1 Scalar) Divide(op2 interface{}) (interface{}, error) { if v.float64 == 0 { return nil, fmt.Errorf(zeroDivisionError, divide) } - return op1.float64 / v.float64, nil - case Quantity: - if v.ToDec().AsApproximateFloat64() == 0 { - return nil, fmt.Errorf(zeroDivisionError, divide) - } - q, err := resource.ParseQuantity(fmt.Sprintf("%v", op1.float64)) - if err != nil { - return nil, err - } - - var quo inf.Dec - scale := inf.Scale(math.Max(float64(q.AsDec().Scale()), float64(v.AsDec().Scale()))) - quo.QuoRound(q.AsDec(), v.AsDec(), scale, inf.RoundDown) - - return resource.NewDecimalQuantity(quo, v.Format).String(), nil - case Duration: - var quo float64 - if op1.float64 == 0 { - return nil, fmt.Errorf(undefinedQuoError, divide) - } - - quo = op1.float64 / v.Seconds() - - res, err := time.ParseDuration(fmt.Sprintf("%.9fs", quo)) - if err != nil { - return nil, err - } - - return res.String(), nil + default: + return nil, errTypeMismatch } - - return nil, nil } +// Quantity % Duration|Scalar -> error +// Quantity % Quantity -> Quantity + +// Duration % Quantity|Scalar -> error +// Duration % Duration -> Duration + +// Scalar % Quantity|Duration -> error +// Scalar % Scalar -> Scalar + func (op1 Quantity) Modulo(op2 interface{}) (interface{}, error) { - quo, err := op1.Divide(op2) - if err != nil { - return nil, err + switch v := op2.(type) { + case Quantity: + f1 := op1.ToDec().AsApproximateFloat64() + f2 := v.ToDec().AsApproximateFloat64() + i1 := int64(f1) + i2 := int64(f2) + if f1 != float64(i1) { + return nil, fmt.Errorf(nonIntModuloError, modulo) + } + if f2 != float64(i2) { + return nil, fmt.Errorf(nonIntModuloError, modulo) + } + if i2 == 0 { + return nil, fmt.Errorf(zeroDivisionError, modulo) + } + return resource.NewQuantity(i1%i2, op1.Quantity.Format).String(), nil + default: + return nil, errTypeMismatch } - - var x resource.Quantity - switch y := quo.(type) { - case float64: - x, err = resource.ParseQuantity(fmt.Sprintf("%.9f", y)) - case string: - x, err = resource.ParseQuantity(y) - } - if err != nil { - return nil, err - } - - mul, err := op2.(Operand).Multiply(Quantity{x}) - if err != nil { - return nil, err - } - - y, err := resource.ParseQuantity(mul.(string)) - if err != nil { - return nil, err - } - - return op1.Subtract(Quantity{y}) } func (op1 Duration) Modulo(op2 interface{}) (interface{}, error) { - quo, err := op1.Divide(op2) - if err != nil { - return nil, err + switch v := op2.(type) { + case Duration: + if v.Duration == 0 { + return nil, fmt.Errorf(zeroDivisionError, modulo) + } + return (op1.Duration % v.Duration).String(), nil + default: + return nil, errTypeMismatch } - - var x time.Duration - switch y := quo.(type) { - case float64: - x, err = time.ParseDuration(fmt.Sprintf("%.9fs", y)) - case string: - x, err = time.ParseDuration(y) - } - if err != nil { - return nil, err - } - x = x.Truncate(time.Second) - - mul, err := op2.(Operand).Multiply(Duration{x}) - if err != nil { - return nil, err - } - y, err := time.ParseDuration(mul.(string)) - if err != nil { - return nil, err - } - - return op1.Subtract(Duration{y}) } func (op1 Scalar) Modulo(op2 interface{}) (interface{}, error) { @@ -386,65 +299,17 @@ func (op1 Scalar) Modulo(op2 interface{}) (interface{}, error) { case Scalar: val1 := int64(op1.float64) val2 := int64(v.float64) - if op1.float64 != float64(val1) { return nil, fmt.Errorf(nonIntModuloError, modulo) } - if v.float64 != float64(val2) { return nil, fmt.Errorf(nonIntModuloError, modulo) } - if val2 == 0 { return nil, fmt.Errorf(zeroDivisionError, modulo) } - return float64(val1 % val2), nil - case Quantity: - quo, err := op1.Divide(op2) - if err != nil { - return nil, err - } - - x, err := resource.ParseQuantity(quo.(string)) - if err != nil { - return nil, err - } - - mul, err := op2.(Operand).Multiply(Quantity{x}) - if err != nil { - return nil, err - } - - y, err := resource.ParseQuantity(mul.(string)) - if err != nil { - return nil, err - } - - return op1.Subtract(Quantity{y}) - case Duration: - quo, err := op1.Divide(op2) - if err != nil { - return nil, err - } - - x, err := time.ParseDuration(quo.(string)) - if err != nil { - return nil, err - } - x = x.Truncate(time.Second) - - mul, err := op2.(Operand).Multiply(Duration{x}) - if err != nil { - return nil, err - } - y, err := time.ParseDuration(mul.(string)) - if err != nil { - return nil, err - } - - return op1.Subtract(Duration{y}) + default: + return nil, errTypeMismatch } - - return nil, nil } diff --git a/pkg/engine/jmespath/functions_test.go b/pkg/engine/jmespath/functions_test.go index 2a2701c33a..f92159ff88 100644 --- a/pkg/engine/jmespath/functions_test.go +++ b/pkg/engine/jmespath/functions_test.go @@ -27,7 +27,6 @@ func Test_Compare(t *testing.T) { expectedResult: 1, }, } - for _, tc := range testCases { t.Run(tc.jmesPath, func(t *testing.T) { jp, err := New(tc.jmesPath) @@ -55,7 +54,6 @@ func Test_ParseJsonSerde(t *testing.T) { `1.2`, `[1.2,true,{"a":{"a":"b"}}]`, } - for _, tc := range testCases { t.Run(tc, func(t *testing.T) { jp, err := New(fmt.Sprintf(`to_string(parse_json('%s'))`, tc)) @@ -87,7 +85,6 @@ func Test_ParseJsonComplex(t *testing.T) { expectedResult: 2.0, }, } - for _, tc := range testCases { t.Run(tc.input, func(t *testing.T) { jp, err := New(tc.input) @@ -165,7 +162,6 @@ bar: null }, }, } - for _, tc := range testCases { t.Run(tc.input, func(t *testing.T) { jp, err := New(fmt.Sprintf(`parse_yaml('%s')`, tc.input)) @@ -195,7 +191,6 @@ func Test_EqualFold(t *testing.T) { expectedResult: false, }, } - for _, tc := range testCases { t.Run(tc.jmesPath, func(t *testing.T) { jp, err := New(tc.jmesPath) @@ -216,7 +211,6 @@ func Test_Replace(t *testing.T) { // https://github.com/jmespath/go-jmespath/issues/27 // // TODO: fix this in https://github.com/kyverno/go-jmespath - testCases := []struct { jmesPath string expectedResult string @@ -234,7 +228,6 @@ func Test_Replace(t *testing.T) { expectedResult: "Lorem muspi ipsum ipsum dolor sit amet", }, } - for _, tc := range testCases { t.Run(tc.jmesPath, func(t *testing.T) { jp, err := New(tc.jmesPath) @@ -280,7 +273,6 @@ func Test_ToUpper(t *testing.T) { expectedResult: "A#%&123BC", }, } - for _, tc := range testCases { t.Run(tc.jmesPath, func(t *testing.T) { jp, err := New(tc.jmesPath) @@ -314,7 +306,6 @@ func Test_ToLower(t *testing.T) { expectedResult: "a#%&123bc", }, } - for _, tc := range testCases { t.Run(tc.jmesPath, func(t *testing.T) { jp, err := New(tc.jmesPath) @@ -500,7 +491,6 @@ func Test_LabelMatch(t *testing.T) { } } `) - testCases := []struct { resource []byte test string @@ -527,7 +517,6 @@ func Test_LabelMatch(t *testing.T) { expectedResult: false, }, } - for i, tc := range testCases { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { var resource interface{} @@ -546,61 +535,83 @@ func Test_LabelMatch(t *testing.T) { assert.Equal(t, result, tc.expectedResult) }) } - } func Test_Add(t *testing.T) { testCases := []struct { + name string test string expectedResult interface{} err bool retFloat bool }{ + // Scalar { - test: "add('12', '13s')", - err: true, - }, - { - test: "add('12Ki', '13s')", - err: true, - }, - { - test: "add('12s', '13')", - err: true, - }, - { - test: "add('12s', '13Ki')", - err: true, - }, - { + name: "Scalar + Scalar -> Scalar", test: "add(`12`, `13`)", expectedResult: 25.0, retFloat: true, }, { - test: "add(`12`, '13s')", - expectedResult: `25s`, + 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`, }, { - test: "add(`12`, '-13s')", - expectedResult: `-1s`, + name: "Duration + Scalar -> error", + test: "add('12s', '13')", + err: true, }, { - test: "add(`12`, '13Ki')", - expectedResult: `13324`, + name: "Duration + Quantity -> error", + test: "add('12s', '13Ki')", + err: true, }, { - test: "add('12Ki', '13Ki')", - expectedResult: `25Ki`, + name: "Duration + Quantity -> error", + test: "add('12s', '13')", + err: true, }, } - - for i, tc := range testCases { - t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { jp, err := New(tc.test) assert.NilError(t, err) @@ -627,56 +638,79 @@ func Test_Add(t *testing.T) { func Test_Subtract(t *testing.T) { testCases := []struct { + name string test string expectedResult interface{} err bool retFloat bool }{ + // Scalar { - test: "subtract('12', '13s')", - err: true, - }, - { - test: "subtract('12Ki', '13s')", - err: true, - }, - { - test: "subtract('12s', '13')", - err: true, - }, - { - test: "subtract('12s', '13Ki')", - err: true, - }, - { + name: "Scalar - Scalar -> Scalar", test: "subtract(`12`, `13`)", expectedResult: -1.0, retFloat: true, }, { - test: "subtract(`12`, '13s')", - expectedResult: `-1s`, + 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`, }, { - test: "subtract(`12`, '-13s')", - expectedResult: `25s`, + name: "Duration - Scalar -> error", + test: "subtract('12s', '13')", + err: true, }, { - test: "subtract(`12`, '13Ki')", - expectedResult: `-13300`, + name: "Duration - Quantity -> error", + test: "subtract('12s', '13Ki')", + err: true, }, { - test: "subtract('12Ki', '13Ki')", - expectedResult: `-1Ki`, + name: "Duration - Quantity -> error", + test: "subtract('12s', '13')", + err: true, }, } - - for i, tc := range testCases { - t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { jp, err := New(tc.test) assert.NilError(t, err) @@ -703,56 +737,79 @@ func Test_Subtract(t *testing.T) { func Test_Multiply(t *testing.T) { testCases := []struct { + name string test string expectedResult interface{} err bool retFloat bool }{ + // Quantity { - test: "multiply('12', '13s')", + name: "Quantity * Scalar -> Quantity", + test: "multiply('12Ki', `2`)", + expectedResult: `24Ki`, + }, + { + name: "Quantity * Quantity -> error", + test: "multiply('12Ki', '12Ki')", err: true, }, { - test: "multiply('12Ki', '13s')", + name: "Quantity * Quantity -> error", + test: "multiply('12Ki', '12')", err: true, }, { - test: "multiply('12s', '13')", + 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, }, { - test: "multiply('12s', '13Ki')", + name: "Duration * Quantity -> error", + test: "multiply('12s', '12')", err: true, }, { - test: "multiply(`12`, `13`)", - expectedResult: 156.0, + 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, }, { - test: "multiply(`12`, '13s')", - expectedResult: `2m36s`, + name: "Scalar * Quantity -> Quantity", + test: "multiply(`2.5`, '12Ki')", + expectedResult: "30Ki", }, { - test: "multiply('12s', '13s')", - expectedResult: `2m36s`, + name: "Scalar * Quantity -> Quantity", + test: "multiply(`2.5`, '12')", + expectedResult: "30", }, { - test: "multiply(`12`, '-13s')", - expectedResult: `-2m36s`, - }, - { - test: "multiply(`12`, '13Ki')", - expectedResult: `156Ki`, - }, - { - test: "multiply('12Ki', '13Ki')", - expectedResult: `156Mi`, + name: "Scalar * Duration -> Duration", + test: "multiply(`2.5`, '40s')", + expectedResult: "1m40s", }, } - - for i, tc := range testCases { - t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { jp, err := New(tc.test) assert.NilError(t, err) @@ -779,110 +836,118 @@ func Test_Multiply(t *testing.T) { func Test_Divide(t *testing.T) { testCases := []struct { + name string test string expectedResult interface{} err bool retFloat bool }{ + // Quantity { - test: "divide('12', '13s')", - err: true, + name: "Quantity / Scalar -> Quantity", + test: "divide('12Ki', `3`)", + expectedResult: "4Ki", }, { - test: "divide('12Ki', '13s')", - err: true, - }, - { - test: "divide('12s', '13')", - err: true, - }, - { - test: "divide('12s', '13Ki')", - err: true, - }, - { - test: "divide('12s', `0`)", - err: true, - }, - { - test: "divide('12s', '0s')", - err: true, - }, - { - test: "divide(`12`, '0s')", - err: true, - }, - { - test: "divide('12M', '0Mi')", - err: true, - }, - { - test: "divide('12K', `0`)", - err: true, - }, - { - test: "divide('12K', '0m')", - err: true, - }, - { - test: "divide('12Ki', '0G')", - err: true, - }, - { - test: "divide('12Mi', '0Gi')", - err: true, - }, - { - test: "divide('12Mi', `0`)", - err: true, - }, - { - test: "divide(`12`, '0Gi')", - err: true, - }, - { - test: "divide(`12`, '0K')", - err: true, - }, - { - test: "divide(`12`, `0`)", - err: true, - }, - { - test: "divide(`25`, `2`)", - expectedResult: 12.5, + name: "Quantity / Quantity -> Scalar", + test: "divide('12Ki', '2Ki')", + expectedResult: 6.0, retFloat: true, }, { - test: "divide(`12`, '2s')", - expectedResult: `6s`, - }, - { - test: "divide('25m0s', '2s')", - expectedResult: 750.0, + name: "Quantity / Quantity -> Scalar", + test: "divide('12Ki', '200')", + expectedResult: 61.0, retFloat: true, }, { - test: "divide(`360`, '-2s')", - expectedResult: `-3m0s`, + name: "Quantity / Duration -> error", + test: "divide('12Ki', '2s')", + err: true, + }, + // Duration + { + name: "Duration / Scalar -> Duration", + test: "divide('12s', `3`)", + expectedResult: "4s", }, { - test: "divide(`13312`, '1Ki')", - expectedResult: `13`, - }, - { - test: "divide('26Gi', '13Ki')", - expectedResult: 2097152.0, + name: "Duration / Duration -> Scalar", + test: "divide('12s', '5s')", + expectedResult: 2.4, retFloat: true, }, { - test: "divide('500m', `2`)", - expectedResult: `250m`, + 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 i, tc := range testCases { - t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { jp, err := New(tc.test) assert.NilError(t, err) @@ -909,104 +974,115 @@ func Test_Divide(t *testing.T) { 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, }, { - test: "modulo('12s', '13')", + name: "Quantity % Scalar -> error", + test: "modulo('12Ki', `13`)", err: true, }, { - test: "modulo('12s', '13Ki')", + name: "Quantity % Quantity -> Quantity", + test: "modulo('12Ki', '5Ki')", + expectedResult: `2Ki`, + }, + // Duration + { + name: "Duration % Quantity -> error", + test: "modulo('13s', '12')", err: true, }, { - test: "modulo('12s', `0`)", + name: "Duration % Quantity -> error", + test: "modulo('13s', '12Ki')", err: true, }, { - test: "modulo('12s', '0s')", - err: true, - }, - { - test: "modulo(`12`, '0s')", - err: true, - }, - { - test: "modulo('12M', '0Mi')", - err: true, - }, - { - test: "modulo('12K', `0`)", - err: true, - }, - { - test: "modulo('12K', '0m')", - err: true, - }, - { - test: "modulo('12Ki', '0G')", - err: true, - }, - { - test: "modulo('12Mi', '0Gi')", - err: true, - }, - { - test: "modulo('12Mi', `0`)", - err: true, - }, - { - test: "modulo(`12`, '0Gi')", - err: true, - }, - { - test: "modulo(`12`, '0K')", - err: true, - }, - { - test: "modulo(`12`, `0`)", - err: true, - }, - { - test: "modulo(`25`, `2`)", - expectedResult: 1.0, - retFloat: true, - }, - { - test: "modulo(`13`, '2s')", + name: "Duration % Duration -> Duration", + test: "modulo('13s', '2s')", expectedResult: `1s`, }, { - test: "modulo('25m13s', '32s')", - expectedResult: `9s`, + name: "Duration % Scalar -> error", + test: "modulo('13s', `2`)", + err: true, + }, + // Scalar + { + name: "Scalar % Quantity -> error", + test: "modulo(`13`, '12')", + err: true, }, { - test: "modulo(`371`, '-13s')", - expectedResult: `7s`, + name: "Scalar % Quantity -> error", + test: "modulo(`13`, '12Ki')", + err: true, }, { - test: "modulo(`13312`, '513')", - expectedResult: `487`, + name: "Scalar % Duration -> error", + test: "modulo(`13`, '5s')", + err: true, }, { - test: "modulo('26Gi', '12Ki')", - expectedResult: `8Ki`, + 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 i, tc := range testCases { - t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { jp, err := New(tc.test) assert.NilError(t, err) @@ -1058,23 +1134,22 @@ func Test_Base64Encode(t *testing.T) { func Test_Base64Decode_Secret(t *testing.T) { resourceRaw := []byte(` { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "example", - "namespace": "default" - }, - "type": "Opaque", - "data": { - "example1": "SGVsbG8sIHdvcmxkIQ==", - "example2": "Rm9vQmFy" - } - } - `) - + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "example", + "namespace": "default" + }, + "type": "Opaque", + "data": { + "example1": "SGVsbG8sIHdvcmxkIQ==", + "example2": "Rm9vQmFy" + } + }`) var resource interface{} err := json.Unmarshal(resourceRaw, &resource) assert.NilError(t, err) + query, err := New(`base64_decode(data.example1)`) assert.NilError(t, err) @@ -1100,7 +1175,6 @@ func Test_TimeSince(t *testing.T) { expectedResult: "180h10m11s", }, } - for i, tc := range testCases { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { query, err := New(tc.test) @@ -1155,7 +1229,6 @@ func Test_PathCanonicalize(t *testing.T) { expectedResult: "/", }, } - testCasesForWindows := []struct { jmesPath string expectedResult string @@ -1177,11 +1250,9 @@ func Test_PathCanonicalize(t *testing.T) { expectedResult: "C:\\Users\\Downloads", }, } - if runtime.GOOS == "windows" { testCases = testCasesForWindows } - for _, tc := range testCases { t.Run(tc.jmesPath, func(t *testing.T) { jp, err := New(tc.jmesPath) @@ -1202,7 +1273,6 @@ func Test_Truncate(t *testing.T) { // https://github.com/jmespath/go-jmespath/issues/27 // // TODO: fix this in https://github.com/kyverno/go-jmespath - testCases := []struct { jmesPath string expectedResult string @@ -1232,7 +1302,6 @@ func Test_Truncate(t *testing.T) { expectedResult: "", }, } - for _, tc := range testCases { t.Run(tc.jmesPath, func(t *testing.T) { jp, err := New(tc.jmesPath) @@ -1286,7 +1355,6 @@ func Test_SemverCompare(t *testing.T) { } func Test_Items(t *testing.T) { - testCases := []struct { object string keyName string @@ -1312,7 +1380,6 @@ func Test_Items(t *testing.T) { expectedResult: `[{ "myKey": "key1", "myValue": "value1" }, { "myKey": "key2", "myValue": "value2" }]`, }, } - for i, tc := range testCases { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { @@ -1332,11 +1399,9 @@ func Test_Items(t *testing.T) { assert.DeepEqual(t, result, resource) }) } - } func Test_ObjectFromLists(t *testing.T) { - testCases := []struct { keys string values string @@ -1367,7 +1432,6 @@ func Test_ObjectFromLists(t *testing.T) { }, }, } - for i, tc := range testCases { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { query, err := New("object_from_lists(`" + tc.keys + "`,`" + tc.values + "`)") @@ -1379,7 +1443,6 @@ func Test_ObjectFromLists(t *testing.T) { assert.DeepEqual(t, result, tc.expectedResult) }) } - } func Test_x509Decode(t *testing.T) { @@ -1434,12 +1497,10 @@ UFOZZVoELaasWS559wy8og39Eq21dDMynb8Bndn/ `{"Raw":"MIIDSjCCAjKgAwIBAgIUWxmj40l+TDVJq98Xy7c6Leo3np8wDQYJKoZIhvcNAQELBQAwPTELMAkGA1UEBhMCeHgxCjAIBgNVBAgTAXgxCjAIBgNVBAcTAXgxCjAIBgNVBAoTAXgxCjAIBgNVBAsTAXgwHhcNMTgwMjAyMTIzODAwWhcNMjMwMjAxMTIzODAwWjA9MQswCQYDVQQGEwJ4eDEKMAgGA1UECBMBeDEKMAgGA1UEBxMBeDEKMAgGA1UEChMBeDEKMAgGA1UECxMBeDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANHkqOmVf23KMXdaZU2eFUx1h4wb09JINBB8x/HL7UE0KFJcnOoVnNQB0gRukUopiYCzrzMFyGWWmB/pAEKool+ZiI2uMy6mcYBDtOi4pOm7U0TQQMV6L/5Yfi65xRz3RTMd/tYAoFi4aCZbJAGjxU6UWNYDzTy8E/cP6ZnlNbVHRiA6/wHsoWcXtWTXYP5yn9cf7EWQi1hOBM4BWmOIyB1f6LEgQipZWMOMPPHO3hsuSBn0rk7jovSt5XTlbgRrtxqAJiNjJUykWzIF+lLnZCioippGv5vkdGvE83JoACXvZTUwzA+MLu49fkw3bweqkbhrer8kacjfGlw3aJN37eECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKXcb52bv6oqnD+D9fTNFHZL8IWxMA0GCSqGSIb3DQEBCwUAA4IBAQADvKvv3ym0XAYwKxPLLl3Lc6sJYHDbTN0donduG7PXeb1dhuukJ2lfufUYp2IGSAxuLecTYeeByOVp1gaMb5LsIGt2BVDmlMMkiH29LUHsvbyi85CpJo7A5RJG6AWW2VBCiDjz5v8JFM6pMkBRFfXH+pwIge65CE+MTSQcfb1/aIIoQ226P7E/3uUGX4k4pDXG/O7GNvykF40v1DB5y7DDBTQ4JWiJfyGkT69TmdOGLFAmjwxUjWyvEey4qJex/EGEm5RQcMv9iy7tba1wK7sykNGn5uDELGPGIIEAa5rIHm1FUFOZZVoELaasWS559wy8og39Eq21dDMynb8Bndn/","RawTBSCertificate":"MIICMqADAgECAhRbGaPjSX5MNUmr3xfLtzot6jeenzANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQGEwJ4eDEKMAgGA1UECBMBeDEKMAgGA1UEBxMBeDEKMAgGA1UEChMBeDEKMAgGA1UECxMBeDAeFw0xODAyMDIxMjM4MDBaFw0yMzAyMDExMjM4MDBaMD0xCzAJBgNVBAYTAnh4MQowCAYDVQQIEwF4MQowCAYDVQQHEwF4MQowCAYDVQQKEwF4MQowCAYDVQQLEwF4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0eSo6ZV/bcoxd1plTZ4VTHWHjBvT0kg0EHzH8cvtQTQoUlyc6hWc1AHSBG6RSimJgLOvMwXIZZaYH+kAQqiiX5mIja4zLqZxgEO06Lik6btTRNBAxXov/lh+LrnFHPdFMx3+1gCgWLhoJlskAaPFTpRY1gPNPLwT9w/pmeU1tUdGIDr/AeyhZxe1ZNdg/nKf1x/sRZCLWE4EzgFaY4jIHV/osSBCKllYw4w88c7eGy5IGfSuTuOi9K3ldOVuBGu3GoAmI2MlTKRbMgX6UudkKKiKmka/m+R0a8TzcmgAJe9lNTDMD4wu7j1+TDdvB6qRuGt6vyRpyN8aXDdok3ft4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUpdxvnZu/qiqcP4P19M0UdkvwhbE=","RawSubjectPublicKeyInfo":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0eSo6ZV/bcoxd1plTZ4VTHWHjBvT0kg0EHzH8cvtQTQoUlyc6hWc1AHSBG6RSimJgLOvMwXIZZaYH+kAQqiiX5mIja4zLqZxgEO06Lik6btTRNBAxXov/lh+LrnFHPdFMx3+1gCgWLhoJlskAaPFTpRY1gPNPLwT9w/pmeU1tUdGIDr/AeyhZxe1ZNdg/nKf1x/sRZCLWE4EzgFaY4jIHV/osSBCKllYw4w88c7eGy5IGfSuTuOi9K3ldOVuBGu3GoAmI2MlTKRbMgX6UudkKKiKmka/m+R0a8TzcmgAJe9lNTDMD4wu7j1+TDdvB6qRuGt6vyRpyN8aXDdok3ft4QIDAQAB","RawSubject":"MD0xCzAJBgNVBAYTAnh4MQowCAYDVQQIEwF4MQowCAYDVQQHEwF4MQowCAYDVQQKEwF4MQowCAYDVQQLEwF4","RawIssuer":"MD0xCzAJBgNVBAYTAnh4MQowCAYDVQQIEwF4MQowCAYDVQQHEwF4MQowCAYDVQQKEwF4MQowCAYDVQQLEwF4","Signature":"A7yr798ptFwGMCsTyy5dy3OrCWBw20zdHaJ3bhuz13m9XYbrpCdpX7n1GKdiBkgMbi3nE2HngcjladYGjG+S7CBrdgVQ5pTDJIh9vS1B7L28ovOQqSaOwOUSRugFltlQQog48+b/CRTOqTJAURX1x/qcCIHuuQhPjE0kHH29f2iCKENtuj+xP97lBl+JOKQ1xvzuxjb8pBeNL9QwecuwwwU0OCVoiX8hpE+vU5nThixQJo8MVI1srxHsuKiXsfxBhJuUUHDL/Ysu7W2tcCu7MpDRp+bgxCxjxiCBAGuayB5tRVBTmWVaBC2mrFkuefcMvKIN/RKttXQzMp2/AZ3Z/w==","SignatureAlgorithm":4,"PublicKeyAlgorithm":1,"PublicKey":{"N":"26496562094779491076553211809422098021949952483515703281510813808490953126660362388109632773224118754702902108388229193869554055094778177099185065933983949693842239539154549752097759985799130804083586220803335221114269832081649712810220640441076536231140807229028981655981643835428138719795509959624793308640711388215921808921435203036357847686892066058381787405708754578605922703585581205444932036212009496723589206933777338978604488048677611723712498345752655171502746679687404543368933776929978831813434358099337112479727796701588293884856604804625411358577626503349165930794262171211166398339413648296787152727521","E":65537},"Version":3,"SerialNumber":520089955419326249038486015063014459614455897759,"Issuer":{"Country":["xx"],"Organization":["x"],"OrganizationalUnit":["x"],"Locality":["x"],"Province":["x"],"StreetAddress":null,"PostalCode":null,"SerialNumber":"","CommonName":"","Names":[{"Type":[2,5,4,6],"Value":"xx"},{"Type":[2,5,4,8],"Value":"x"},{"Type":[2,5,4,7],"Value":"x"},{"Type":[2,5,4,10],"Value":"x"},{"Type":[2,5,4,11],"Value":"x"}],"ExtraNames":null},"Subject":{"Country":["xx"],"Organization":["x"],"OrganizationalUnit":["x"],"Locality":["x"],"Province":["x"],"StreetAddress":null,"PostalCode":null,"SerialNumber":"","CommonName":"","Names":[{"Type":[2,5,4,6],"Value":"xx"},{"Type":[2,5,4,8],"Value":"x"},{"Type":[2,5,4,7],"Value":"x"},{"Type":[2,5,4,10],"Value":"x"},{"Type":[2,5,4,11],"Value":"x"}],"ExtraNames":null},"NotBefore":"2018-02-02T12:38:00Z","NotAfter":"2023-02-01T12:38:00Z","KeyUsage":96,"Extensions":[{"Id":[2,5,29,15],"Critical":true,"Value":"AwIBBg=="},{"Id":[2,5,29,19],"Critical":true,"Value":"MAMBAf8="},{"Id":[2,5,29,14],"Critical":false,"Value":"BBSl3G+dm7+qKpw/g/X0zRR2S/CFsQ=="}],"ExtraExtensions":null,"UnhandledCriticalExtensions":null,"ExtKeyUsage":null,"UnknownExtKeyUsage":null,"BasicConstraintsValid":true,"IsCA":true,"MaxPathLen":-1,"MaxPathLenZero":false,"SubjectKeyId":"pdxvnZu/qiqcP4P19M0UdkvwhbE=","AuthorityKeyId":null,"OCSPServer":null,"IssuingCertificateURL":null,"DNSNames":null,"EmailAddresses":null,"IPAddresses":null,"URIs":null,"PermittedDNSDomainsCritical":false,"PermittedDNSDomains":null,"ExcludedDNSDomains":null,"PermittedIPRanges":null,"ExcludedIPRanges":null,"PermittedEmailAddresses":null,"ExcludedEmailAddresses":null,"PermittedURIDomains":null,"ExcludedURIDomains":null,"CRLDistributionPoints":null,"PolicyIdentifiers":null}`, } resExpected := make([]map[string]interface{}, 2) - for i, v := range resList { err := json.Unmarshal([]byte(v), &resExpected[i]) assert.NilError(t, err) } - testCases := []struct { jmesPath string expectedResult map[string]interface{} @@ -1473,7 +1534,6 @@ UFOZZVoELaasWS559wy8og39Eq21dDMynb8Bndn/ expectedResult: map[string]interface{}{}, }, } - for _, tc := range testCases { t.Run(tc.jmesPath, func(t *testing.T) { jp, err := New(tc.jmesPath)