route/vendor/github.com/hashicorp/hil/eval_test.go

2056 lines
35 KiB
Go

package hil
import (
"fmt"
"reflect"
"strconv"
"strings"
"testing"
"github.com/hashicorp/hil/ast"
)
func TestEval(t *testing.T) {
cases := []struct {
Input string
Scope *ast.BasicScope
Error bool
Result interface{}
ResultType EvalType
}{
{
Input: "Hello World",
Scope: nil,
Result: "Hello World",
ResultType: TypeString,
},
{
Input: `${"foo\\bar"}`,
Scope: nil,
Result: `foo\bar`,
ResultType: TypeString,
},
{
Input: `${"foo\\\\bar"}`,
Scope: nil,
Result: `foo\\bar`,
ResultType: TypeString,
},
{
"${var.alist}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
false,
[]interface{}{"Hello", "World"},
TypeList,
},
{
"${var.alist[1]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
false,
"World",
TypeString,
},
{
`${var.alist["1"]}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
false,
"World",
TypeString,
},
{
"${var.alist[1]} ${var.alist[0]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
false,
"World Hello",
TypeString,
},
{
"${var.alist[2-1]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
false,
"World",
TypeString,
},
{
"${var.alist[1]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
},
},
false,
UnknownValue,
TypeUnknown,
},
{
"${var.alist[var.index]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
},
},
"var.index": ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
},
},
false,
UnknownValue,
TypeUnknown,
},
{
"${var.alist} ${var.alist}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
true,
nil,
TypeInvalid,
},
{
"${var.alist[1]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
false,
"World",
TypeString,
},
{
`${foo}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
},
},
false,
map[string]interface{}{
"foo": "hello",
"bar": "world",
},
TypeMap,
},
{
`${foo["bar"]}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
},
},
false,
"world",
TypeString,
},
{
`${foo["foo"]}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeUnknown,
},
},
},
},
},
false,
"hello",
TypeString,
},
{
`${foo["bar"]}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeUnknown,
},
},
},
},
},
false,
UnknownValue,
TypeUnknown,
},
{
`${foo["foo"]} foo`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeUnknown,
},
},
},
},
},
false,
"hello foo",
TypeString,
},
{
`${foo["bar"]} foo`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeUnknown,
},
},
},
},
},
false,
UnknownValue,
TypeUnknown,
},
{
`${foo[3]}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"3": ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
},
},
false,
"world",
TypeString,
},
{
`${foo["bar"]} ${foo["foo"]}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
},
},
false,
"world hello",
TypeString,
},
{
`${foo} ${foo}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
},
},
true,
nil,
TypeInvalid,
},
{
`${foo} ${bar}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
"bar": ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
false,
"Hello World",
TypeString,
},
{
`${foo} ${bar}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
"bar": ast.Variable{
Type: ast.TypeInt,
Value: 4,
},
},
},
false,
"Hello 4",
TypeString,
},
{
`${foo} ${bar}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
"bar": ast.Variable{
Type: ast.TypeUnknown,
},
},
},
false,
UnknownValue,
TypeUnknown,
},
{
`${foo}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
},
},
false,
map[string]interface{}{
"foo": "hello",
"bar": "world",
},
TypeMap,
},
{
"${var.alist}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
false,
[]interface{}{
"Hello",
"World",
},
TypeList,
},
{
"${var.alist[0] + var.alist[1]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
ast.Variable{
Type: ast.TypeInt,
Value: 2,
},
},
},
},
},
false,
UnknownValue,
TypeUnknown,
},
{
// Unknowns can short-circuit bits of our type checking
// AST transform, such as the promotion of arithmetic to
// functions. This test ensures that the evaluator and the
// type checker co-operate to ensure that this doesn't cause
// raw arithmetic nodes to be evaluated (which is not supported).
"${var.alist[0 + var.unknown]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 2,
},
},
},
"var.unknown": ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
},
},
false,
UnknownValue,
TypeUnknown,
},
{
"${join(var.alist)}",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"join": ast.Function{
ArgTypes: []ast.Type{ast.TypeList},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
return nil, fmt.Errorf("should never actually be called")
},
},
},
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
false,
UnknownValue,
TypeUnknown,
},
{
"${upper(var.alist[1])}",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"upper": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
return strings.ToUpper(args[0].(string)), nil
},
},
},
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
false,
"WORLD",
TypeString,
},
{
`${foo[upper(bar)]}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"upper": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
return strings.ToUpper(args[0].(string)), nil
},
},
},
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"KEY": ast.Variable{
Type: ast.TypeString,
Value: "value",
},
},
},
"bar": ast.Variable{
Value: "key",
Type: ast.TypeString,
},
},
},
false,
"value",
TypeString,
},
{
`${foo[upper(bar)]}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"upper": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
return strings.ToUpper(args[0].(string)), nil
},
},
},
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"KEY": ast.Variable{
Type: ast.TypeString,
Value: "value",
},
},
},
"bar": ast.Variable{
Type: ast.TypeUnknown,
},
},
},
false,
UnknownValue,
TypeUnknown,
},
{
`${upper(foo)}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"upper": ast.Function{
ArgTypes: []ast.Type{ast.TypeMap},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
return "foo", nil
},
},
},
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"KEY": ast.Variable{
Type: ast.TypeUnknown,
},
},
},
},
},
false,
UnknownValue,
TypeUnknown,
},
{
Input: `${"foo\\"}`,
Scope: nil,
Result: `foo\`,
ResultType: TypeString,
},
{
Input: `${"foo\\\\"}`,
Scope: nil,
Result: `foo\\`,
ResultType: TypeString,
},
{
`${second("foo", "\\", "/", "bar")}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"second": {
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString, ast.TypeString},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
return args[1].(string), nil
},
},
},
},
false,
`\`,
TypeString,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Input), func(t *testing.T) {
node, err := Parse(tc.Input)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
result, err := Eval(node, &EvalConfig{GlobalScope: tc.Scope})
if err != nil != tc.Error {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
if tc.ResultType != TypeInvalid && result.Type != tc.ResultType {
t.Fatalf("Bad: %s\n\nInput: %s", result.Type, tc.Input)
}
if !reflect.DeepEqual(result.Value, tc.Result) {
t.Fatalf("\n Got: %#v\nExpected: %#v\n\n Input: %s\n", result.Value, tc.Result, tc.Input)
}
})
}
}
func TestEvalInternal(t *testing.T) {
cases := []struct {
Input string
Scope *ast.BasicScope
Error bool
Result interface{}
ResultType ast.Type
}{
{
"foo",
nil,
false,
"foo",
ast.TypeString,
},
{
"foo ${bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "baz",
Type: ast.TypeString,
},
},
},
false,
"foo baz",
ast.TypeString,
},
{
"${var.alist}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.alist": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
false,
[]ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "Hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
ast.TypeList,
},
{
"foo ${-29}",
nil,
false,
"foo -29",
ast.TypeString,
},
{
"foo ${42+1}",
nil,
false,
"foo 43",
ast.TypeString,
},
{
"foo ${42-1}",
nil,
false,
"foo 41",
ast.TypeString,
},
{
"foo ${42*2}",
nil,
false,
"foo 84",
ast.TypeString,
},
{
"foo ${42/2}",
nil,
false,
"foo 21",
ast.TypeString,
},
{
"foo ${42/0}",
nil,
true,
"foo ",
ast.TypeInvalid,
},
{
"foo ${42%4}",
nil,
false,
"foo 2",
ast.TypeString,
},
{
"foo ${42%0}",
nil,
true,
"foo ",
ast.TypeInvalid,
},
{
"foo ${42.0+1.0}",
nil,
false,
"foo 43",
ast.TypeString,
},
{
"foo ${42.0+1}",
nil,
false,
"foo 43",
ast.TypeString,
},
{
"foo ${42+1.0}",
nil,
false,
"foo 43",
ast.TypeString,
},
{
"foo ${0.5 * 75}",
nil,
false,
"foo 37.5",
ast.TypeString,
},
{
"foo ${75 * 0.5}",
nil,
false,
"foo 37.5",
ast.TypeString,
},
{
"foo ${42+2*2}",
nil,
false,
"foo 46",
ast.TypeString,
},
{
"foo ${42+(2*2)}",
nil,
false,
"foo 46",
ast.TypeString,
},
{
"foo ${true && false}",
nil,
false,
"foo false",
ast.TypeString,
},
{
"foo ${false || true}",
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${"true" || true}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${true || "true"}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${"true" || "true"}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
"foo ${1 == 2}",
nil,
false,
"foo false",
ast.TypeString,
},
{
"foo ${1 == 1}",
nil,
false,
"foo true",
ast.TypeString,
},
{
"foo ${1 > 2}",
nil,
false,
"foo false",
ast.TypeString,
},
{
"foo ${2 > 1}",
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${"hello" == "hello"}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${"hello" == "goodbye"}`,
nil,
false,
"foo false",
ast.TypeString,
},
{
`foo ${1 == "2"}`,
nil,
false,
"foo false",
ast.TypeString,
},
{
`foo ${1 == "1"}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${"1" == 1}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${1.2 == 1}`,
nil,
false,
"foo false",
ast.TypeString,
},
{
// implicit conversion of float to int makes this equal
`foo ${1 == 1.2}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${true == false}`,
nil,
false,
"foo false",
ast.TypeString,
},
{
`foo ${false == false}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${"true" == true}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${true == "true"}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
`foo ${! true}`,
nil,
false,
"foo false",
ast.TypeString,
},
{
`foo ${! false}`,
nil,
false,
"foo true",
ast.TypeString,
},
{
"foo ${true ? 5 : 7}",
nil,
false,
"foo 5",
ast.TypeString,
},
{
"foo ${false ? 5 : 7}",
nil,
false,
"foo 7",
ast.TypeString,
},
{
`foo ${"true" ? 5 : 7}`,
nil,
false,
"foo 5",
ast.TypeString,
},
{
// false expression is type-converted to match true expression
`foo ${false ? 5 : 6.5}`,
nil,
false,
"foo 6",
ast.TypeString,
},
{
// true expression is type-converted to match false expression
// if the true expression is string
`foo ${false ? "12" : 16}`,
nil,
false,
"foo 16",
ast.TypeString,
},
{
"foo ${3 > 2 ? 5 : 7}",
nil,
false,
"foo 5",
ast.TypeString,
},
{
"${var.do_it ? 5 : 7}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.do_it": ast.Variable{
Value: UnknownValue,
Type: ast.TypeUnknown,
},
},
},
false,
UnknownValue,
ast.TypeUnknown,
},
{
// false expression can be unknown, and is returned
`foo ${false ? "12" : unknown}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"unknown": ast.Variable{
Value: UnknownValue,
Type: ast.TypeUnknown,
},
},
},
false,
UnknownValue,
ast.TypeUnknown,
},
{
// false expression can be unknown, and result is unknown even
// if it's not selected.
// (Ideally this would not be true, but we're accepting this
// for now since this assumption is built in to the core evaluator)
`foo ${true ? "12" : unknown}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"unknown": ast.Variable{
Value: UnknownValue,
Type: ast.TypeUnknown,
},
},
},
false,
UnknownValue,
ast.TypeUnknown,
},
{
// true expression can be unknown, and is returned
`foo ${false ? unknown : "bar"}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"unknown": ast.Variable{
Value: UnknownValue,
Type: ast.TypeUnknown,
},
},
},
false,
UnknownValue,
ast.TypeUnknown,
},
{
// true expression can be unknown, and result is unknown even
// if it's not selected.
// (Ideally this would not be true, but we're accepting this
// for now since this assumption is built in to the core evaluator)
`foo ${false ? unknown : "bar"}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"unknown": ast.Variable{
Value: UnknownValue,
Type: ast.TypeUnknown,
},
},
},
false,
UnknownValue,
ast.TypeUnknown,
},
{
// both values can be unknown
`foo ${false ? unknown : unknown}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"unknown": ast.Variable{
Value: UnknownValue,
Type: ast.TypeUnknown,
},
},
},
false,
UnknownValue,
ast.TypeUnknown,
},
{
// condition can be unknown, and result is unknown
`foo ${unknown ? "baz" : "bar"}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"unknown": ast.Variable{
Value: UnknownValue,
Type: ast.TypeUnknown,
},
},
},
false,
UnknownValue,
ast.TypeUnknown,
},
{
"foo ${-bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 41,
Type: ast.TypeInt,
},
},
},
false,
"foo -41",
ast.TypeString,
},
{
"foo ${bar+1}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 41,
Type: ast.TypeInt,
},
},
},
false,
"foo 42",
ast.TypeString,
},
{
"foo ${bar+1}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "41",
Type: ast.TypeString,
},
},
},
false,
"foo 42",
ast.TypeString,
},
{
"foo ${bar+baz}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "41",
Type: ast.TypeString,
},
"baz": ast.Variable{
Value: "1",
Type: ast.TypeString,
},
},
},
false,
"foo 42",
ast.TypeString,
},
{
"foo ${bar+baz}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 0.001,
Type: ast.TypeFloat,
},
"baz": ast.Variable{
Value: "0.002",
Type: ast.TypeString,
},
},
},
false,
"foo 0.003",
ast.TypeString,
},
{
"foo ${bar+baz}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: UnknownValue,
Type: ast.TypeUnknown,
},
"baz": ast.Variable{
Value: 1,
Type: ast.TypeInt,
},
},
},
false,
UnknownValue,
ast.TypeUnknown,
},
{
"foo ${rand()}",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
"foo 42",
ast.TypeString,
},
{
`foo ${rand("foo", "bar")}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
var result string
for _, a := range args {
result += a.(string)
}
return result, nil
},
},
},
},
false,
"foo foobar",
ast.TypeString,
},
{
`${foo["bar"]}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
},
},
false,
"world",
ast.TypeString,
},
{
`${foo[var.key]}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
"var.key": ast.Variable{
Type: ast.TypeString,
Value: "bar",
},
},
},
false,
"world",
ast.TypeString,
},
{
`${foo[bar[var.keyint]]}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
"bar": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "i dont exist",
},
ast.Variable{
Type: ast.TypeString,
Value: "bar",
},
},
},
"var.keyint": ast.Variable{
Type: ast.TypeInt,
Value: 1,
},
},
},
false,
"world",
ast.TypeString,
},
{
`${foo["bar"]} ${bar[1]}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
"bar": ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
"bar": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 10,
},
ast.Variable{
Type: ast.TypeInt,
Value: 20,
},
},
},
},
},
false,
"world 20",
ast.TypeString,
},
{
"${foo[0]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
},
},
false,
"hello",
ast.TypeString,
},
{
"${foo[bar]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
"bar": ast.Variable{
Type: ast.TypeInt,
Value: 1,
},
},
},
false,
"world",
ast.TypeString,
},
{
"${foo[bar[1]]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
"bar": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 1,
},
ast.Variable{
Type: ast.TypeInt,
Value: 0,
},
},
},
},
},
false,
"hello",
ast.TypeString,
},
{
"aaa ${foo} aaa",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeInt,
Value: 42,
},
},
},
false,
"aaa 42 aaa",
ast.TypeString,
},
{
"aaa ${foo[1]} aaa",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 42,
},
ast.Variable{
Type: ast.TypeInt,
Value: 24,
},
},
},
},
},
false,
"aaa 24 aaa",
ast.TypeString,
},
{
"aaa ${foo[1]} - ${foo[0]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 42,
},
ast.Variable{
Type: ast.TypeInt,
Value: 24,
},
},
},
},
},
false,
"aaa 24 - 42",
ast.TypeString,
},
{
"${var.foo} ${var.foo[0]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
},
},
true,
nil,
ast.TypeInvalid,
},
{
"${var.foo[0]} ${var.foo[1]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "hello",
},
ast.Variable{
Type: ast.TypeString,
Value: "world",
},
},
},
},
},
false,
"hello world",
ast.TypeString,
},
{
"${foo[1]} ${foo[0]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 42,
},
ast.Variable{
Type: ast.TypeInt,
Value: 24,
},
},
},
},
},
false,
"24 42",
ast.TypeString,
},
{
"${foo[1-3]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 42,
},
ast.Variable{
Type: ast.TypeInt,
Value: 24,
},
},
},
},
},
true,
nil,
ast.TypeInvalid,
},
{
"${foo[2]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 42,
},
ast.Variable{
Type: ast.TypeInt,
Value: 24,
},
},
},
},
},
true,
nil,
ast.TypeInvalid,
},
// Testing implicit type conversions
{
"foo ${bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 42,
Type: ast.TypeInt,
},
},
},
false,
"foo 42",
ast.TypeString,
},
{
`foo ${foo("42")}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"foo": ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
return strconv.FormatInt(int64(args[0].(int)), 10), nil
},
},
},
},
false,
"foo 42",
ast.TypeString,
},
// Multiline
{
"foo ${42+\n1.0}",
nil,
false,
"foo 43",
ast.TypeString,
},
// String vars should be able to implictly convert to floats
{
"${1.5 * var.foo}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.foo": ast.Variable{
Value: "42",
Type: ast.TypeString,
},
},
},
false,
"63",
ast.TypeString,
},
// Unary
{
"foo ${-46}",
nil,
false,
"foo -46",
ast.TypeString,
},
{
"foo ${-46 + 5}",
nil,
false,
"foo -41",
ast.TypeString,
},
{
"foo ${46 + -5}",
nil,
false,
"foo 41",
ast.TypeString,
},
{
"foo ${-bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 41,
Type: ast.TypeInt,
},
},
},
false,
"foo -41",
ast.TypeString,
},
{
"foo ${5 + -bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 41,
Type: ast.TypeInt,
},
},
},
false,
"foo -36",
ast.TypeString,
},
{
"${var.foo > 1 ? 5 : 0}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.foo": ast.Variable{
Type: ast.TypeString,
Value: "3",
},
},
},
false,
"5",
ast.TypeString,
},
{
"${var.foo > 1.5 ? 5 : 0}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.foo": ast.Variable{
Type: ast.TypeString,
Value: "3",
},
},
},
false,
"5",
ast.TypeString,
},
{
"${var.foo > 1.5 ? 5 : 0}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"var.foo": ast.Variable{
Type: ast.TypeString,
Value: "1.2",
},
},
},
false,
"0",
ast.TypeString,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Input), func(t *testing.T) {
node, err := Parse(tc.Input)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
out, outType, err := internalEval(node, &EvalConfig{GlobalScope: tc.Scope})
if err != nil != tc.Error {
t.Fatalf("Error: %s\nInput: %s", err, tc.Input)
}
if tc.ResultType != ast.TypeInvalid && outType != tc.ResultType {
t.Fatalf("Wrong result type\nInput: %s\nGot: %#s\nWant: %s", tc.Input, outType, tc.ResultType)
}
if !reflect.DeepEqual(out, tc.Result) {
t.Fatalf("Wrong result value\nInput: %s\nGot: %#s\nWant: %s", tc.Input, out, tc.Result)
}
})
}
}