evaluator_test.go

Copyright 2022 Red Hat, Inc

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

package
evaluator_test

Documentation in literate-programming-style is available at: https://redhatinsights.github.io/insights-operator-utils/packages/evaluator/evaluator_test.html


import
(
"fmt"
"go/token"
"testing"
"github.com/stretchr/testify/assert"
"github.com/RedHatInsights/insights-operator-utils/evaluator"
)
type
TestCase
struct
{
name
string
expression
string
expectedValue
int
expectedError
bool
}

TestEvaluatorEmptyInput function checks the evaluator.Evaluate function for empty input

func
TestEvaluatorEmptyInput
(
t
*
testing
.
T
)
{
var
values
=
make
(
map
[
string
]
int
)
expression
:=
""
_
,
err
:=
evaluator
.
Evaluate
(
expression
,
values
)
assert
.
Error
(
t
,
err
,
"error is expected"
)
}

TestEvaluatorSingleToken function checks the evaluator.Evaluate function for single token input

func
TestEvaluatorSingleToken
(
t
*
testing
.
T
)
{
var
values
=
make
(
map
[
string
]
int
)
expression
:=
"42"
result
,
err
:=
evaluator
.
Evaluate
(
expression
,
values
)
assert
.
Nil
(
t
,
err
,
"unexpected error"
)
assert
.
Equal
(
t
,
42
,
result
)
}

TestEvaluatorArithmetic checks the evaluator.Evaluate function for simple arithmetic expression

func
TestEvaluatorArithmetic
(
t
*
testing
.
T
)
{
var
values
=
make
(
map
[
string
]
int
)
testCases
:=
[
]
TestCase
{
{
name
:
"short expression"
,
expression
:
"1+2*3"
,
expectedValue
:
7
,
}
,
{
name
:
"long expression"
,
expression
:
"4/2-1+5%2"
,
expectedValue
:
2
,
}
,
}
for
_
,
tc
:=
range
testCases
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
result
,
err
:=
evaluator
.
Evaluate
(
tc
.
expression
,
values
)
assert
.
NoError
(
t
,
err
,
"unexpected error"
)
assert
.
Equal
(
t
,
tc
.
expectedValue
,
result
)
}
)
}
}

TestEvaluatorParenthesis checks the evaluator.Evaluate function for simple arithmetic expression with parenthesis

func
TestEvaluatorParenthesis
(
t
*
testing
.
T
)
{
var
values
=
make
(
map
[
string
]
int
)
expression
:=
"(1+2)*3"
result
,
err
:=
evaluator
.
Evaluate
(
expression
,
values
)
assert
.
Nil
(
t
,
err
,
"unexpected error"
)
assert
.
Equal
(
t
,
9
,
result
)
}

TestEvaluatorRelational checks the evaluator.Evaluate function for simple relational expression

func
TestEvaluatorRelational
(
t
*
testing
.
T
)
{
var
values
=
make
(
map
[
string
]
int
)
testCases
:=
[
]
TestCase
{
{
name
:
"less than"
,
expression
:
"1 < 2"
,
expectedValue
:
1
,
}
,
{
name
:
"greater or equal"
,
expression
:
"1 >= 2"
,
expectedValue
:
0
,
}
,
{
name
:
"long expression"
,
expression
:
"1 < 2 && 1 > 2 && 1 <= 2 && 1 >= 2 && 1==2 && 1 != 2"
,
expectedValue
:
0
,
}
,
}
for
_
,
tc
:=
range
testCases
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
result
,
err
:=
evaluator
.
Evaluate
(
tc
.
expression
,
values
)
assert
.
NoError
(
t
,
err
,
"unexpected error"
)
assert
.
Equal
(
t
,
tc
.
expectedValue
,
result
)
}
)
}
}

TestEvaluatorBoolean checks the evaluator.Evaluate function for simple boolean expressions

func
TestEvaluatorBoolean
(
t
*
testing
.
T
)
{
var
values
=
make
(
map
[
string
]
int
)
testCases
:=
[
]
TestCase
{
{
name
:
"and"
,
expression
:
"1 && 0"
,
expectedValue
:
0
,
}
,
{
name
:
"or"
,
expression
:
"1 || 0"
,
expectedValue
:
1
,
}
,
}
for
_
,
tc
:=
range
testCases
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
result
,
err
:=
evaluator
.
Evaluate
(
tc
.
expression
,
values
)
assert
.
NoError
(
t
,
err
,
"unexpected error"
)
assert
.
Equal
(
t
,
tc
.
expectedValue
,
result
)
}
)
}
}

TestEvaluatorValues checks the evaluator.Evaluate function for expression with named values

func
TestEvaluatorValues
(
t
*
testing
.
T
)
{
var
values
=
make
(
map
[
string
]
int
)
values
[
"x"
]
=
1
values
[
"y"
]
=
2
expression
:=
"x+y*2"
result
,
err
:=
evaluator
.
Evaluate
(
expression
,
values
)
assert
.
Nil
(
t
,
err
,
"unexpected error"
)
assert
.
Equal
(
t
,
5
,
result
)
}

TestEvaluatorWrongInput checks the evaluator.Evaluate function for expression that is not correct

func
TestEvaluatorWrongInput
(
t
*
testing
.
T
)
{
var
values
=
make
(
map
[
string
]
int
)
testCases
:=
[
]
TestCase
{
{
name
:
"mul instead of right operand"
,
expression
:
"1**"
,
expectedError
:
true
,
}
,
{
name
:
"forgot closing parenthesis"
,
expression
:
"(1+2*"
,
expectedError
:
true
,
}
,
{
name
:
"no operands"
,
expression
:
"+"
,
expectedError
:
true
,
}
,
{
name
:
"no right operand"
,
expression
:
"2+"
,
expectedError
:
true
,
}
,
{
name
:
"no left operand"
,
expression
:
"+2"
,
expectedError
:
true
,
}
,
{
name
:
"no left operand (minus)"
,
expression
:
"-2"
,
expectedError
:
true
,
}
,
{
name
:
"== typo"
,
expression
:
"0=0"
,
expectedError
:
true
,
}
,
{
name
:
"zero division"
,
expression
:
"1/0"
,
expectedError
:
true
,
}
,
}
for
_
,
tc
:=
range
testCases
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
if
tc
.
expectedError
{
result
,
err
:=
evaluator
.
Evaluate
(
tc
.
expression
,
values
)
assert
.
Error
(
t
,
err
,
"error is expected"
)
assert
.
Equal
(
t
,
-
1
,
result
)
}
}
)
}
}

TestEvaluatorMissingValue checks the evaluator.Evaluate function for expression that use value not provided

func
TestEvaluatorMissingValue
(
t
*
testing
.
T
)
{
var
values
=
make
(
map
[
string
]
int
)
expression
:=
"value"
_
,
err
:=
evaluator
.
Evaluate
(
expression
,
values
)
assert
.
Error
(
t
,
err
,
"error is expected"
)
}

TestEdgeCases tests expressions that rarely happen in the real world

func
TestEdgeCases
(
t
*
testing
.
T
)
{
var
values
=
make
(
map
[
string
]
int
)
testCases
:=
[
]
TestCase
{
{
name
:
"useless parenthesis"
,
expression
:
"(2)*(2)"
,
expectedValue
:
4
,
}
,
{
name
:
"multiple useless parenthesis"
,
expression
:
"(((0))) >= 0"
,
expectedValue
:
1
,
}
,
{
name
:
"scrambled useless parenthesis"
,
expression
:
"((((0==0)))+1)"
,
expectedValue
:
2
,
}
,
{
name
:
"0 addition idempotence"
,
expression
:
"1+0+0+0+0"
,
expectedValue
:
1
,
}
,
{
name
:
"1 division idempotence"
,
expression
:
"5/1/1/1/1/1"
,
expectedValue
:
5
,
}
,
{
name
:
"transitivity"
,
expression
:
"(3 > 2) && (2 > 1) == (3 > 1)"
,
expectedValue
:
1
,
}
,
{
name
:
"big integer"
,
expression
:
"9223372036854775807+100-100"
,
expectedValue
:
9223372036854775807
,
}
,
{
name
:
"overflow"
,
expression
:
"9223372036854775807+1"
,
expectedValue
:
-
9223372036854775808
,
}
,
}
for
_
,
tc
:=
range
testCases
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
result
,
err
:=
evaluator
.
Evaluate
(
tc
.
expression
,
values
)
assert
.
NoError
(
t
,
err
,
"unexpected error"
)
assert
.
Equal
(
t
,
tc
.
expectedValue
,
result
)
}
)
}
}

TestToInt tests the function toint

func
TestToInt
(
t
*
testing
.
T
)
{

conversion from false to 0

	
result
:=
evaluator
.
ToInt
(
false
)
assert
.
Equal
(
t
,
0
,
result
)

conversion from true to 1

	
result
=
evaluator
.
ToInt
(
true
)
assert
.
Equal
(
t
,
1
,
result
)
}

TestToBool tests the function tobool

func
TestToBool
(
t
*
testing
.
T
)
{

conversion 0 to false

	
result
:=
evaluator
.
ToBool
(
0
)
assert
.
False
(
t
,
result
)

conversion 1 to true

	
result
=
evaluator
.
ToBool
(
1
)
assert
.
True
(
t
,
result
)
}

TestEvaluateRPNNoTokens tests the function evaluateRPN when no tokens are provided at input

func
TestEvaluateRPNNoTokens
(
t
*
testing
.
T
)
{

tokens to be tokenized

	
tokens
:=
[
]
evaluator
.
TokenWithValue
{
}

value map used during evaluation

	
var
values
=
make
(
map
[
string
]
int
)

evaluate expression represented as sequence of tokens in RPN order

	
stack
,
err
:=
evaluator
.
EvaluateRPN
(
tokens
,
values
)

check the output

	
assert
.
NoError
(
t
,
err
)
assert
.
True
(
t
,
stack
.
Empty
(
)
)
}

TestEvaluateRPNInvalidToken tests the function evaluateRPN when invalid token is provided at input

func
TestEvaluateRPNInvalidToken
(
t
*
testing
.
T
)
{

these tokens are not supported

	
invalidTokens
:=
[
]
token
.
Token
{
token
.
ILLEGAL
,
token
.
EOF
,
token
.
COMMENT
,
token
.
BREAK
,
token
.
CASE
,
token
.
CHAN
,
token
.
CONST
,
token
.
CONTINUE
,
token
.
DEFAULT
,
token
.
DEFER
,
token
.
ELSE
,
token
.
FALLTHROUGH
,
token
.
FOR
,
token
.
FUNC
,
token
.
GO
,
token
.
GOTO
,
token
.
IF
,
token
.
IMPORT
,
token
.
INTERFACE
,
token
.
MAP
,
token
.
PACKAGE
,
token
.
RANGE
,
token
.
RETURN
,
token
.
SELECT
,
token
.
STRUCT
,
token
.
SWITCH
,
token
.
TYPE
,
token
.
VAR
,
}

check all invalid tokens

	
for
_
,
invalidToken
:=
range
invalidTokens
{
name
:=
fmt
.
Sprintf
(
"EvaluatingInvalidToken %v"
,
invalidToken
)
t
.
Run
(
name
,
func
(
t
*
testing
.
T
)
{

tokens to be tokenized

			
tokens
:=
[
]
evaluator
.
TokenWithValue
{
{
invalidToken
,
-
1
,
""
}
,
}

value map used during evaluation

			
var
values
=
make
(
map
[
string
]
int
)

evaluate expression represented as sequence of tokens in RPN order

			
_
,
err
:=
evaluator
.
EvaluateRPN
(
tokens
,
values
)

check the output -> error needs to be detected

			
assert
.
Error
(
t
,
err
)
}
)
}
}

TestEvaluateRPNIntValue tests the function evaluateRPN when just one token with integer values is provided at input

func
TestEvaluateRPNIntValue
(
t
*
testing
.
T
)
{

tokens to be tokenized

	
tokens
:=
[
]
evaluator
.
TokenWithValue
{
evaluator
.
ValueToken
(
token
.
INT
,
42
)
,
}

value map used during evaluation

	
var
values
=
make
(
map
[
string
]
int
)

evaluate expression represented as sequence of tokens in RPN order

	
stack
,
err
:=
evaluator
.
EvaluateRPN
(
tokens
,
values
)

check the output

	
assert
.
NoError
(
t
,
err
)
assert
.
False
(
t
,
stack
.
Empty
(
)
)
assert
.
Equal
(
t
,
stack
.
Size
(
)
,
1
)
value
,
err
:=
stack
.
Pop
(
)
assert
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
value
,
42
)
}

TestEvaluateRPNTwoIntValues tests the function evaluateRPN when two tokens with integer values are provided at input

func
TestEvaluateRPNTwoIntValues
(
t
*
testing
.
T
)
{

tokens to be tokenized

	
tokens
:=
[
]
evaluator
.
TokenWithValue
{
evaluator
.
ValueToken
(
token
.
INT
,
1
)
,
evaluator
.
ValueToken
(
token
.
INT
,
2
)
,
}

value map used during evaluation

	
var
values
=
make
(
map
[
string
]
int
)

evaluate expression represented as sequence of tokens in RPN order

	
stack
,
err
:=
evaluator
.
EvaluateRPN
(
tokens
,
values
)

check the output

	
assert
.
NoError
(
t
,
err
)
assert
.
False
(
t
,
stack
.
Empty
(
)
)
assert
.
Equal
(
t
,
stack
.
Size
(
)
,
2
)

values to be poped from stack in reverse order

	
value
,
err
:=
stack
.
Pop
(
)
assert
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
value
,
2
)

values to be poped from stack in reverse order

	
value
,
err
=
stack
.
Pop
(
)
assert
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
value
,
1
)
}

TestEvaluateRPNArithmeticOperation tests the function evaluateRPN when three tokens representing arithmetic expression is evaluated

func
TestEvaluateRPNArithmeticOperation
(
t
*
testing
.
T
)
{

tokens to be tokenized

	
tokens
:=
[
]
evaluator
.
TokenWithValue
{

RPN order (postfix)

		
evaluator
.
ValueToken
(
token
.
INT
,
1
)
,
evaluator
.
ValueToken
(
token
.
INT
,
2
)
,
evaluator
.
OperatorToken
(
token
.
ADD
)
,
}

value map used during evaluation

	
var
values
=
make
(
map
[
string
]
int
)

evaluate expression represented as sequence of tokens in RPN order

	
stack
,
err
:=
evaluator
.
EvaluateRPN
(
tokens
,
values
)

check the output

	
assert
.
NoError
(
t
,
err
)
assert
.
False
(
t
,
stack
.
Empty
(
)
)
assert
.
Equal
(
t
,
stack
.
Size
(
)
,
1
)
value
,
err
:=
stack
.
Pop
(
)
assert
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
value
,
3
)
}

TestEvaluateRPNJustArithmeticOperator tests the function evaluateRPN when just arithmetic operator is provided

func
TestEvaluateRPNJustArithmeticOperator
(
t
*
testing
.
T
)
{

tokens to be tokenized

	
tokens
:=
[
]
evaluator
.
TokenWithValue
{
evaluator
.
OperatorToken
(
token
.
ADD
)
,
}

value map used during evaluation

	
var
values
=
make
(
map
[
string
]
int
)

evaluate expression represented as sequence of tokens in RPN order

	
_
,
err
:=
evaluator
.
EvaluateRPN
(
tokens
,
values
)

check the output -> error needs to be detected

	
assert
.
Error
(
t
,
err
)
}

TestEvaluateRPNInsuficientOperand the function evaluateRPN when just arithmetic operator and one operand are provided

func
TestEvaluateRPNInsuficientOperand
(
t
*
testing
.
T
)
{

tokens to be tokenized

	
tokens
:=
[
]
evaluator
.
TokenWithValue
{
evaluator
.
ValueToken
(
token
.
INT
,
1
)
,
evaluator
.
OperatorToken
(
token
.
ADD
)
,
}

value map used during evaluation

	
var
values
=
make
(
map
[
string
]
int
)

evaluate expression represented as sequence of tokens in RPN order

	
_
,
err
:=
evaluator
.
EvaluateRPN
(
tokens
,
values
)

check the output -> error needs to be detected

	
assert
.
Error
(
t
,
err
)
}

TestPerformArithmeticOperation check the behaviour of function performArithmeticOperation for correct operators and tokens

func
TestPerformArithmeticOperation
(
t
*
testing
.
T
)
{

operand stack (also known as data stack)

	
stack
:=
evaluator
.
Stack
{
}

push two values onto the stack

	
stack
.
Push
(
1
)
stack
.
Push
(
2
)

any token that is not token.QUO or token.REM

	
tok
:=
token
.
ADD

perform the selected arithmetic operation

	
addOperation
:=
func
(
x
int
,
y
int
)
int
{
return
x
+
y
}

perform the selected arithmetic operation

	
err
:=
evaluator
.
PerformArithmeticOperation
(
&
stack
,
addOperation
,
tok
)
assert
.
NoError
(
t
,
err
)

check stack

	
assert
.
False
(
t
,
stack
.
Empty
(
)
)
assert
.
Equal
(
t
,
stack
.
Size
(
)
,
1
)

stack should contain one value

	
value
,
err
:=
stack
.
Pop
(
)
assert
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
value
,
3
)
}

TestPerformArithmeticOperationMissingOperand check the behaviour of function performArithmeticOperation for incorrect number of operands

func
TestPerformArithmeticOperationMissingOperand
(
t
*
testing
.
T
)
{

operand stack (also known as data stack)

	
stack
:=
evaluator
.
Stack
{
}

push just one value onto the stack

	
stack
.
Push
(
1
)

any token that is not token.QUO or token.REM

	
tok
:=
token
.
ADD

perform the selected arithmetic operation

	
addOperation
:=
func
(
x
int
,
y
int
)
int
{
return
x
+
y
}

perform the selected arithmetic operation

	
err
:=
evaluator
.
PerformArithmeticOperation
(
&
stack
,
addOperation
,
tok
)
assert
.
Error
(
t
,
err
)
}

TestPerformArithmeticOperationMissingBothOperands check the behaviour of function performArithmeticOperation for incorrect number of operands

func
TestPerformArithmeticOperationMissingBothOperands
(
t
*
testing
.
T
)
{

operand stack (also known as data stack)

	
stack
:=
evaluator
.
Stack
{
}

stack is empty!


any token that is not token.QUO or token.REM

	
tok
:=
token
.
ADD

perform the selected arithmetic operation

	
addOperation
:=
func
(
x
int
,
y
int
)
int
{
return
x
+
y
}

perform the selected arithmetic operation

	
err
:=
evaluator
.
PerformArithmeticOperation
(
&
stack
,
addOperation
,
tok
)
assert
.
Error
(
t
,
err
)
}

TestPerformArithmeticOperationDivideByNotZero check the behaviour of function performArithmeticOperation for divide by any value different from zero

func
TestPerformArithmeticOperationDivideByNotZero
(
t
*
testing
.
T
)
{

operand stack (also known as data stack)

	
stack
:=
evaluator
.
Stack
{
}

push two values onto the stack

	
stack
.
Push
(
4
)
stack
.
Push
(
2
)

any token that is not token.QUO or token.REM

	
tok
:=
token
.
QUO

perform the selected arithmetic operation

	
addOperation
:=
func
(
x
int
,
y
int
)
int
{
return
x
+
y
}

perform the selected arithmetic operation

	
err
:=
evaluator
.
PerformArithmeticOperation
(
&
stack
,
addOperation
,
tok
)
assert
.
NoError
(
t
,
err
)
}

TestPerformArithmeticOperationDivideByZero check the behaviour of function performArithmeticOperation for divide by zero

func
TestPerformArithmeticOperationDivideByZero
(
t
*
testing
.
T
)
{

operand stack (also known as data stack)

	
stack
:=
evaluator
.
Stack
{
}

push two values onto the stack

	
stack
.
Push
(
1
)
stack
.
Push
(
0
)

any token that is not token.QUO or token.REM

	
tok
:=
token
.
QUO

perform the selected arithmetic operation

	
addOperation
:=
func
(
x
int
,
y
int
)
int
{
return
x
+
y
}

perform the selected arithmetic operation

	
err
:=
evaluator
.
PerformArithmeticOperation
(
&
stack
,
addOperation
,
tok
)
assert
.
Error
(
t
,
err
)
}