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

547 lines
9.9 KiB
Go

package hil
import (
"testing"
"github.com/hashicorp/hil/ast"
)
func TestTypeCheck(t *testing.T) {
cases := []struct {
Input string
Scope ast.Scope
Error bool
}{
{
"foo",
&ast.BasicScope{},
false,
},
{
"foo ${bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "baz",
Type: ast.TypeString,
},
},
},
false,
},
{
"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 ${rand("42")}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
{
`foo ${rand(42)}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
true,
},
{
`foo ${rand()}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: nil,
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
{
`foo ${rand("42")}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: nil,
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
{
`foo ${rand("42", 42)}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: nil,
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
true,
},
{
"${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,
},
{
"${foo[0]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 3,
},
ast.Variable{
Type: ast.TypeString,
Value: "World",
},
},
},
},
},
true,
},
{
"${foo[0]}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"foo": ast.Variable{
Type: ast.TypeString,
Value: "Hello World",
},
},
},
true,
},
{
"foo ${bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 42,
Type: ast.TypeInt,
},
},
},
true,
},
{
"foo ${rand()}",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeInt,
Callback: func([]interface{}) (interface{}, error) {
return 42, nil
},
},
},
},
true,
},
{
`foo ${true ? "foo" : "bar"}`,
&ast.BasicScope{},
false,
},
{
// can't use different types for true and false expressions
`foo ${true ? 1 : "baz"}`,
&ast.BasicScope{},
true,
},
{
// condition must be boolean
`foo ${"foo" ? 1 : 5}`,
&ast.BasicScope{},
true,
},
{
// conditional with unknown value is permitted
`foo ${true ? known : unknown}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"known": ast.Variable{
Type: ast.TypeString,
Value: "bar",
},
"unknown": ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
},
},
false,
},
{
// conditional with unknown value the other way permitted too
`foo ${true ? unknown : known}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"known": ast.Variable{
Type: ast.TypeString,
Value: "bar",
},
"unknown": ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
},
},
false,
},
{
// conditional with two unknowns is allowed
`foo ${true ? unknown : unknown}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"unknown": ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
},
},
false,
},
{
// conditional with unknown condition is allowed
`foo ${unknown ? 1 : 2}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"unknown": ast.Variable{
Type: ast.TypeUnknown,
Value: UnknownValue,
},
},
},
false,
},
{
// currently lists are not allowed at all
`foo ${true ? arr1 : arr2}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"arr1": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 3,
},
},
},
"arr2": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 4,
},
},
},
},
},
true,
},
{
// mismatching element types are invalid
`foo ${true ? arr1 : arr2}`,
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"arr1": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeInt,
Value: 3,
},
},
},
"arr2": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "foo",
},
},
},
},
},
true,
},
}
for _, tc := range cases {
node, err := Parse(tc.Input)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
visitor := &TypeCheck{Scope: tc.Scope}
err = visitor.Visit(node)
if err != nil != tc.Error {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
}
}
func TestTypeCheck_implicit(t *testing.T) {
implicitMap := map[ast.Type]map[ast.Type]string{
ast.TypeInt: {
ast.TypeString: "intToString",
},
}
cases := []struct {
Input string
Scope *ast.BasicScope
Error bool
}{
{
"foo ${bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 42,
Type: ast.TypeInt,
},
},
},
false,
},
{
"foo ${foo(42)}",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"foo": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
},
},
},
false,
},
{
`foo ${foo("42", 42)}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"foo": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
Variadic: true,
VariadicType: ast.TypeString,
ReturnType: ast.TypeString,
},
},
},
false,
},
{
"${foo[1]}",
&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: 23,
},
},
},
},
},
false,
},
{
"${foo[1]}",
&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.TypeUnknown,
},
},
},
},
},
false,
},
{
`${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,
},
}
for _, tc := range cases {
t.Run(tc.Input, func(t *testing.T) {
node, err := Parse(tc.Input)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
// Modify the scope to add our conversion functions.
if tc.Scope.FuncMap == nil {
tc.Scope.FuncMap = make(map[string]ast.Function)
}
tc.Scope.FuncMap["intToString"] = ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
}
// Do the first pass...
visitor := &TypeCheck{Scope: tc.Scope, Implicit: implicitMap}
err = visitor.Visit(node)
if err != nil != tc.Error {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
if err != nil {
return
}
// If we didn't error, then the next type check should not fail
// WITHOUT implicits.
visitor = &TypeCheck{Scope: tc.Scope}
err = visitor.Visit(node)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
})
}
}