blob: a0307e89719abd262342e96c0575d993019e736f [file]
package govaluate
/*
Tests to make sure evaluation fails in the expected ways.
*/
import (
"errors"
"strings"
"testing"
)
type DebugStruct struct{}
/*
Represents a test for parsing failures
*/
type EvaluationFailureTest struct {
Name string
Input string
Functions map[string]ExpressionFunction
Parameters map[string]interface{}
Expected string
}
const (
INVALID_MODIFIER_TYPES = "cannot be used with the modifier"
INVALID_COMPARATOR_TYPES = "cannot be used with the comparator"
INVALID_LOGICALOP_TYPES = "cannot be used with the logical operator"
INVALID_TERNARY_TYPES = "cannot be used with the ternary operator"
ABSENT_PARAMETER = "No parameter"
INVALID_REGEX = "unable to compile regexp pattern"
INVALID_PARAMETER_CALL = "No method or field"
TOO_FEW_ARGS = "too few arguments to parameter call"
TOO_MANY_ARGS = "too many arguments to parameter call"
MISMATCHED_PARAMETERS = "Argument type conversion failed"
UNEXPORTED_ACCESSOR = "Unable to access unexported"
)
// preset parameter map of types that can be used in an evaluation failure test to check typing.
var EVALUATION_FAILURE_PARAMETERS = map[string]interface{}{
"number": 1,
"string": "foo",
"bool": true,
}
func TestComplexParameter(test *testing.T) {
var expression *EvaluableExpression
var err error
var v interface{}
parameters := map[string]interface{}{
"complex64": complex64(0),
"complex128": complex128(0),
}
expression, _ = NewEvaluableExpression("complex64")
v, err = expression.Evaluate(parameters)
if err != nil {
test.Errorf("Expected no error, but have %s", err)
}
if v.(complex64) != complex64(0) {
test.Errorf("Expected %v == %v", v, complex64(0))
}
expression, _ = NewEvaluableExpression("complex128")
v, err = expression.Evaluate(parameters)
if err != nil {
test.Errorf("Expected no error, but have %s", err)
}
if v.(complex128) != complex128(0) {
test.Errorf("Expected %v == %v", v, complex128(0))
}
}
func TestStructParameter(t *testing.T) {
expected := DebugStruct{}
expression, _ := NewEvaluableExpression("foo")
parameters := map[string]interface{}{"foo": expected}
v, err := expression.Evaluate(parameters)
if err != nil {
t.Errorf("Expected no error, but have %s", err)
} else if v.(DebugStruct) != expected {
t.Errorf("Values mismatch: %v != %v", expected, v)
}
}
func TestNilParameterUsage(test *testing.T) {
expression, _ := NewEvaluableExpression("2 > 1")
_, err := expression.Evaluate(nil)
if err != nil {
test.Errorf("Expected no error from nil parameter evaluation, got %v\n", err)
return
}
}
func TestModifierTyping(test *testing.T) {
evaluationTests := []EvaluationFailureTest{
{
Name: "PLUS literal number to literal bool",
Input: "1 + true",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "PLUS number to bool",
Input: "number + bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "MINUS number to bool",
Input: "number - bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "MINUS number to bool",
Input: "number - bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "MULTIPLY number to bool",
Input: "number * bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "DIVIDE number to bool",
Input: "number / bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "EXPONENT number to bool",
Input: "number ** bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "MODULUS number to bool",
Input: "number % bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "XOR number to bool",
Input: "number % bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "BITWISE_OR number to bool",
Input: "number | bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "BITWISE_AND number to bool",
Input: "number & bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "BITWISE_XOR number to bool",
Input: "number ^ bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "BITWISE_LSHIFT number to bool",
Input: "number << bool",
Expected: INVALID_MODIFIER_TYPES,
},
{
Name: "BITWISE_RSHIFT number to bool",
Input: "number >> bool",
Expected: INVALID_MODIFIER_TYPES,
},
}
runEvaluationFailureTests(evaluationTests, test)
}
func TestLogicalOperatorTyping(test *testing.T) {
evaluationTests := []EvaluationFailureTest{
{
Name: "AND number to number",
Input: "number && number",
Expected: INVALID_LOGICALOP_TYPES,
},
{
Name: "OR number to number",
Input: "number || number",
Expected: INVALID_LOGICALOP_TYPES,
},
{
Name: "AND string to string",
Input: "string && string",
Expected: INVALID_LOGICALOP_TYPES,
},
{
Name: "OR string to string",
Input: "string || string",
Expected: INVALID_LOGICALOP_TYPES,
},
{
Name: "AND number to string",
Input: "number && string",
Expected: INVALID_LOGICALOP_TYPES,
},
{
Name: "OR number to string",
Input: "number || string",
Expected: INVALID_LOGICALOP_TYPES,
},
{
Name: "AND bool to string",
Input: "bool && string",
Expected: INVALID_LOGICALOP_TYPES,
},
{
Name: "OR string to bool",
Input: "string || bool",
Expected: INVALID_LOGICALOP_TYPES,
},
}
runEvaluationFailureTests(evaluationTests, test)
}
/*
While there is type-safe transitions checked at parse-time, tested in the "parsing_test" and "parsingFailure_test" files,
we also need to make sure that we receive type mismatch errors during evaluation.
*/
func TestComparatorTyping(test *testing.T) {
evaluationTests := []EvaluationFailureTest{
{
Name: "GT literal bool to literal bool",
Input: "true > true",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "GT bool to bool",
Input: "bool > bool",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "GTE bool to bool",
Input: "bool >= bool",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "LT bool to bool",
Input: "bool < bool",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "LTE bool to bool",
Input: "bool <= bool",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "GT number to string",
Input: "number > string",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "GTE number to string",
Input: "number >= string",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "LT number to string",
Input: "number < string",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "REQ number to string",
Input: "number =~ string",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "REQ number to bool",
Input: "number =~ bool",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "REQ bool to number",
Input: "bool =~ number",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "REQ bool to string",
Input: "bool =~ string",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "NREQ number to string",
Input: "number !~ string",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "NREQ number to bool",
Input: "number !~ bool",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "NREQ bool to number",
Input: "bool !~ number",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "NREQ bool to string",
Input: "bool !~ string",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "IN non-array numeric",
Input: "1 in 2",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "IN non-array string",
Input: "1 in 'foo'",
Expected: INVALID_COMPARATOR_TYPES,
},
{
Name: "IN non-array boolean",
Input: "1 in true",
Expected: INVALID_COMPARATOR_TYPES,
},
}
runEvaluationFailureTests(evaluationTests, test)
}
func TestTernaryTyping(test *testing.T) {
evaluationTests := []EvaluationFailureTest{
{
Name: "Ternary with number",
Input: "10 ? true",
Expected: INVALID_TERNARY_TYPES,
},
{
Name: "Ternary with string",
Input: "'foo' ? true",
Expected: INVALID_TERNARY_TYPES,
},
}
runEvaluationFailureTests(evaluationTests, test)
}
func TestRegexParameterCompilation(test *testing.T) {
evaluationTests := []EvaluationFailureTest{
{
Name: "Regex equality runtime parsing",
Input: "'foo' =~ foo",
Parameters: map[string]interface{}{
"foo": "[foo",
},
Expected: INVALID_REGEX,
},
{
Name: "Regex inequality runtime parsing",
Input: "'foo' =~ foo",
Parameters: map[string]interface{}{
"foo": "[foo",
},
Expected: INVALID_REGEX,
},
}
runEvaluationFailureTests(evaluationTests, test)
}
func TestFunctionExecution(test *testing.T) {
evaluationTests := []EvaluationFailureTest{
{
Name: "Function error bubbling",
Input: "error()",
Functions: map[string]ExpressionFunction{
"error": func(arguments ...interface{}) (interface{}, error) {
return nil, errors.New("Huge problems")
},
},
Expected: "Huge problems",
},
}
runEvaluationFailureTests(evaluationTests, test)
}
func TestInvalidParameterCalls(test *testing.T) {
evaluationTests := []EvaluationFailureTest{
{
Name: "Missing parameter field reference",
Input: "foo.NotExists",
Parameters: fooFailureParameters,
Expected: INVALID_PARAMETER_CALL,
},
{
Name: "Parameter method call on missing function",
Input: "foo.NotExist()",
Parameters: fooFailureParameters,
Expected: INVALID_PARAMETER_CALL,
},
{
Name: "Nested missing parameter field reference",
Input: "foo.Nested.NotExists",
Parameters: fooFailureParameters,
Expected: INVALID_PARAMETER_CALL,
},
{
Name: "Parameter method call returns error",
Input: "foo.AlwaysFail()",
Parameters: fooFailureParameters,
Expected: "function should always fail",
},
{
Name: "Too few arguments to parameter call",
Input: "foo.FuncArgStr()",
Parameters: fooFailureParameters,
Expected: TOO_FEW_ARGS,
},
{
Name: "Too many arguments to parameter call",
Input: "foo.FuncArgStr('foo', 'bar', 15)",
Parameters: fooFailureParameters,
Expected: TOO_MANY_ARGS,
},
{
Name: "Mismatched parameters",
Input: "foo.FuncArgStr(5)",
Parameters: fooFailureParameters,
Expected: MISMATCHED_PARAMETERS,
},
{
Name: "Unexported parameter access",
Input: "foo.bar",
Parameters: map[string]interface{}{
"foo": struct {
bar string
}{
bar: "baz",
},
},
Expected: UNEXPORTED_ACCESSOR,
},
}
runEvaluationFailureTests(evaluationTests, test)
}
func runEvaluationFailureTests(evaluationTests []EvaluationFailureTest, test *testing.T) {
var expression *EvaluableExpression
var err error
// fmt.Printf("Running %d negative parsing test cases...\n", len(evaluationTests))
for _, testCase := range evaluationTests {
if len(testCase.Functions) > 0 {
expression, err = NewEvaluableExpressionWithFunctions(testCase.Input, testCase.Functions)
} else {
expression, err = NewEvaluableExpression(testCase.Input)
}
if err != nil {
test.Logf("Test '%s' failed", testCase.Name)
test.Logf("Expected evaluation error, but got parsing error: '%s'", err)
test.Fail()
continue
}
if testCase.Parameters == nil {
testCase.Parameters = EVALUATION_FAILURE_PARAMETERS
}
_, err = expression.Evaluate(testCase.Parameters)
if err == nil {
test.Logf("Test '%s' failed", testCase.Name)
test.Logf("Expected error, received none.")
test.Fail()
continue
}
if !strings.Contains(err.Error(), testCase.Expected) {
test.Logf("Test '%s' failed", testCase.Name)
test.Logf("Got error: '%s', expected '%s'", err.Error(), testCase.Expected)
test.Fail()
continue
}
}
}