functions: add tests and support for unknown values

This commit is contained in:
Kristin Laemmert 2018-06-06 10:43:58 -07:00 committed by Martin Atkins
parent d802d5c624
commit a213c4a648
2 changed files with 248 additions and 32 deletions

View File

@ -171,9 +171,16 @@ var CompactFunc = function.New(&function.Spec{
}, },
Type: function.StaticReturnType(cty.List(cty.String)), Type: function.StaticReturnType(cty.List(cty.String)),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
listVal := args[0]
if !listVal.IsWhollyKnown() {
// If some of the element values aren't known yet then we
// can't yet return a compacted list
return cty.UnknownVal(retType), nil
}
var outputList []cty.Value var outputList []cty.Value
for it := args[0].ElementIterator(); it.Next(); { for it := listVal.ElementIterator(); it.Next(); {
_, v := it.Element() _, v := it.Element()
if v.AsString() == "" { if v.AsString() == "" {
continue continue
@ -263,11 +270,19 @@ var DistinctFunc = function.New(&function.Spec{
Type: cty.List(cty.DynamicPseudoType), Type: cty.List(cty.DynamicPseudoType),
}, },
}, },
Type: function.StaticReturnType(cty.List(cty.DynamicPseudoType)), Type: func(args []cty.Value) (cty.Type, error) {
return args[0].Type(), nil
},
// Type: function.StaticReturnType(cty.List(cty.DynamicPseudoType)),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
listVal := args[0]
if !listVal.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
var list []cty.Value var list []cty.Value
for it := args[0].ElementIterator(); it.Next(); { for it := listVal.ElementIterator(); it.Next(); {
_, v := it.Element() _, v := it.Element()
list, err = appendIfMissing(list, v) list, err = appendIfMissing(list, v)
if err != nil { if err != nil {
@ -296,6 +311,11 @@ var ChunklistFunc = function.New(&function.Spec{
return cty.List(args[0].Type()), nil return cty.List(args[0].Type()), nil
}, },
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
listVal := args[0]
if !listVal.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
var size int var size int
err = gocty.FromCtyValue(args[1], &size) err = gocty.FromCtyValue(args[1], &size)
if err != nil { if err != nil {
@ -310,7 +330,7 @@ var ChunklistFunc = function.New(&function.Spec{
// if size is 0, returns a list made of the initial list // if size is 0, returns a list made of the initial list
if size == 0 { if size == 0 {
output = append(output, args[0]) output = append(output, listVal)
return cty.ListVal(output), nil return cty.ListVal(output), nil
} }
@ -319,7 +339,7 @@ var ChunklistFunc = function.New(&function.Spec{
l := args[0].LengthInt() l := args[0].LengthInt()
i := 0 i := 0
for it := args[0].ElementIterator(); it.Next(); { for it := listVal.ElementIterator(); it.Next(); {
_, v := it.Element() _, v := it.Element()
chunk = append(chunk, v) chunk = append(chunk, v)
@ -347,9 +367,12 @@ var FlattenFunc = function.New(&function.Spec{
Type: function.StaticReturnType(cty.List(cty.DynamicPseudoType)), Type: function.StaticReturnType(cty.List(cty.DynamicPseudoType)),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
inputList := args[0] inputList := args[0]
if !inputList.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
if inputList.LengthInt() == 0 { if inputList.LengthInt() == 0 {
return cty.ListValEmpty(cty.DynamicPseudoType), nil return cty.ListValEmpty(retType.ElementType()), nil
} }
outputList := make([]cty.Value, 0) outputList := make([]cty.Value, 0)
@ -458,11 +481,14 @@ var LookupFunc = function.New(&function.Spec{
AllowDynamicType: true, AllowDynamicType: true,
AllowNull: true, AllowNull: true,
}, },
Type: function.StaticReturnType(cty.DynamicPseudoType), Type: func(args []cty.Value) (ret cty.Type, err error) {
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
if len(args) < 1 || len(args) > 3 { if len(args) < 1 || len(args) > 3 {
return cty.NilVal, fmt.Errorf("lookup() takes two or three arguments, got %d", len(args)) return cty.NilType, fmt.Errorf("lookup() takes two or three arguments, got %d", len(args))
} }
return args[0].Type().ElementType(), nil
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var defaultVal cty.Value var defaultVal cty.Value
defaultValueSet := false defaultValueSet := false
@ -474,6 +500,10 @@ var LookupFunc = function.New(&function.Spec{
mapVar := args[0] mapVar := args[0]
lookupKey := args[1].AsString() lookupKey := args[1].AsString()
if !mapVar.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True { if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True {
v := mapVar.Index(cty.StringVal(lookupKey)) v := mapVar.Index(cty.StringVal(lookupKey))
if ty := v.Type(); !ty.Equals(cty.NilType) { if ty := v.Type(); !ty.Equals(cty.NilType) {
@ -489,13 +519,11 @@ var LookupFunc = function.New(&function.Spec{
} }
if defaultValueSet { if defaultValueSet {
defaultType := defaultVal.Type() defaultVal, err = convert.Convert(defaultVal, retType)
switch { if err != nil {
case defaultType.Equals(cty.String): return cty.NilVal, err
return cty.StringVal(defaultVal.AsString()), nil
case defaultType.Equals(cty.Number):
return cty.NumberVal(defaultVal.AsBigFloat()), nil
} }
return defaultVal, nil
} }
return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf( return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf(
@ -537,6 +565,12 @@ var MapFunc = function.New(&function.Spec{
return cty.Map(valType), nil return cty.Map(valType), nil
}, },
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
for _, arg := range args {
if !arg.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
}
outputMap := make(map[string]cty.Value) outputMap := make(map[string]cty.Value)
for i := 0; i < len(args); i += 2 { for i := 0; i < len(args); i += 2 {
@ -612,6 +646,10 @@ var MatchkeysFunc = function.New(&function.Spec{
return cty.ListValEmpty(retType.ElementType()), nil return cty.ListValEmpty(retType.ElementType()), nil
} }
if !args[0].IsWhollyKnown() || !args[0].IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
i := 0 i := 0
for it := keys.ElementIterator(); it.Next(); { for it := keys.ElementIterator(); it.Next(); {
_, key := it.Element() _, key := it.Element()
@ -659,7 +697,9 @@ var MergeFunc = function.New(&function.Spec{
outputMap := make(map[string]cty.Value) outputMap := make(map[string]cty.Value)
for _, arg := range args { for _, arg := range args {
if !arg.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
if !arg.Type().IsObjectType() && !arg.Type().IsMapType() { if !arg.Type().IsObjectType() && !arg.Type().IsMapType() {
return cty.NilVal, fmt.Errorf("arguments must be maps or objects, got %#v", arg.Type().FriendlyName()) return cty.NilVal, fmt.Errorf("arguments must be maps or objects, got %#v", arg.Type().FriendlyName())
} }
@ -694,6 +734,9 @@ var SliceFunc = function.New(&function.Spec{
}, },
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
inputList := args[0] inputList := args[0]
if !inputList.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
var startIndex, endIndex int var startIndex, endIndex int
if err = gocty.FromCtyValue(args[1], &startIndex); err != nil { if err = gocty.FromCtyValue(args[1], &startIndex); err != nil {
@ -791,25 +834,14 @@ var ValuesFunc = function.New(&function.Spec{
}, },
}, },
Type: func(args []cty.Value) (ret cty.Type, err error) { Type: func(args []cty.Value) (ret cty.Type, err error) {
values := args[0] return cty.List(args[0].Type().ElementType()), nil
argTypes := make([]cty.Type, values.LengthInt())
index := 0
for it := values.ElementIterator(); it.Next(); {
_, v := it.Element()
argTypes[index] = v.Type()
index++
}
valType, _ := convert.UnifyUnsafe(argTypes)
if valType == cty.NilType {
return cty.NilType, fmt.Errorf("map elements must have the same type")
}
return cty.List(valType), nil
}, },
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
mapVar := args[0] mapVar := args[0]
if !mapVar.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
keys, err := Keys(mapVar) keys, err := Keys(mapVar)
if err != nil { if err != nil {
return cty.NilVal, err return cty.NilVal, err

View File

@ -299,6 +299,34 @@ func TestCoalesceList(t *testing.T) {
}), }),
false, false,
}, },
{ // list with unknown values
[]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("first"), cty.StringVal("second"),
}),
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.String),
}),
},
cty.ListVal([]cty.Value{
cty.StringVal("first"), cty.StringVal("second"),
}),
false,
},
{ // list with unknown values
[]cty.Value{
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.String),
}),
cty.ListVal([]cty.Value{
cty.StringVal("third"), cty.StringVal("fourth"),
}),
},
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.String),
}),
false,
},
{ {
[]cty.Value{ []cty.Value{
cty.MapValEmpty(cty.DynamicPseudoType), cty.MapValEmpty(cty.DynamicPseudoType),
@ -375,6 +403,15 @@ func TestCompact(t *testing.T) {
}), }),
false, false,
}, },
{
cty.ListVal([]cty.Value{
cty.StringVal("test"),
cty.UnknownVal(cty.String),
cty.StringVal(""),
}),
cty.UnknownVal(cty.List(cty.String)),
false,
},
{ // errors on list of lists { // errors on list of lists
cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{
@ -422,6 +459,12 @@ func TestContains(t *testing.T) {
cty.NumberIntVal(3), cty.NumberIntVal(3),
cty.NumberIntVal(4), cty.NumberIntVal(4),
}) })
listWithUnknown := cty.ListVal([]cty.Value{
cty.StringVal("the"),
cty.StringVal("quick"),
cty.StringVal("brown"),
cty.UnknownVal(cty.String),
})
tests := []struct { tests := []struct {
List cty.Value List cty.Value
@ -435,6 +478,12 @@ func TestContains(t *testing.T) {
cty.BoolVal(true), cty.BoolVal(true),
false, false,
}, },
{
listWithUnknown,
cty.StringVal("the"),
cty.BoolVal(true),
false,
},
{ {
listOfStrings, listOfStrings,
cty.StringVal("penguin"), cty.StringVal("penguin"),
@ -509,6 +558,16 @@ func TestIndex(t *testing.T) {
cty.NumberIntVal(0), cty.NumberIntVal(0),
false, false,
}, },
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.UnknownVal(cty.String),
}),
cty.StringVal("a"),
cty.NumberIntVal(0),
false,
},
{ {
cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{
cty.StringVal("a"), cty.StringVal("a"),
@ -620,6 +679,16 @@ func TestDistinct(t *testing.T) {
}), }),
false, false,
}, },
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("a"),
cty.UnknownVal(cty.String),
}),
cty.UnknownVal(cty.List(cty.String)),
false,
},
{ {
cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{
cty.StringVal("a"), cty.StringVal("a"),
@ -765,6 +834,16 @@ func TestChunklist(t *testing.T) {
}), }),
false, false,
}, },
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.UnknownVal(cty.String),
}),
cty.NumberIntVal(1),
cty.UnknownVal(cty.List(cty.List(cty.String))),
false,
},
} }
for _, test := range tests { for _, test := range tests {
@ -812,6 +891,20 @@ func TestFlatten(t *testing.T) {
}), }),
false, false,
}, },
{
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.String),
cty.StringVal("d"),
}),
}),
cty.UnknownVal(cty.List(cty.DynamicPseudoType)),
false,
},
{ {
cty.ListValEmpty(cty.String), cty.ListValEmpty(cty.String),
cty.ListValEmpty(cty.DynamicPseudoType), cty.ListValEmpty(cty.DynamicPseudoType),
@ -861,6 +954,11 @@ func TestKeys(t *testing.T) {
cty.NilVal, cty.NilVal,
true, true,
}, },
{ // Unknown map
cty.UnknownVal(cty.Map(cty.String)),
cty.UnknownVal(cty.List(cty.String)),
false,
},
} }
for _, test := range tests { for _, test := range tests {
@ -927,6 +1025,17 @@ func TestList(t *testing.T) {
}), }),
false, false,
}, },
{
[]cty.Value{
cty.StringVal("Hello"),
cty.UnknownVal(cty.String),
},
cty.ListVal([]cty.Value{
cty.StringVal("Hello"),
cty.UnknownVal(cty.String),
}),
false,
},
} }
for _, test := range tests { for _, test := range tests {
@ -962,6 +1071,10 @@ func TestLookup(t *testing.T) {
cty.StringVal("baz"), cty.StringVal("baz"),
}), }),
}) })
mapWithUnknowns := cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
"baz": cty.UnknownVal(cty.String),
})
tests := []struct { tests := []struct {
Values []cty.Value Values []cty.Value
@ -1046,6 +1159,14 @@ func TestLookup(t *testing.T) {
cty.NilVal, cty.NilVal,
true, true,
}, },
{
[]cty.Value{
mapWithUnknowns,
cty.StringVal("baz"),
},
cty.UnknownVal(cty.String),
false,
},
} }
for _, test := range tests { for _, test := range tests {
@ -1084,6 +1205,14 @@ func TestMap(t *testing.T) {
}), }),
false, false,
}, },
{
[]cty.Value{
cty.StringVal("hello"),
cty.UnknownVal(cty.String),
},
cty.UnknownVal(cty.Map(cty.String)),
false,
},
{ {
[]cty.Value{ []cty.Value{
cty.StringVal("hello"), cty.StringVal("hello"),
@ -1307,6 +1436,23 @@ func TestMatchkeys(t *testing.T) {
}), }),
false, false,
}, },
{ // unknowns
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.UnknownVal(cty.String),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref1"),
cty.StringVal("ref2"),
cty.UnknownVal(cty.String),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref1"),
}),
cty.UnknownVal(cty.List(cty.String)),
false,
},
// errors // errors
{ // different types { // different types
cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{
@ -1396,6 +1542,18 @@ func TestMerge(t *testing.T) {
}), }),
false, false,
}, },
{ // handle unknowns
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"a": cty.UnknownVal(cty.String),
}),
cty.MapVal(map[string]cty.Value{
"c": cty.StringVal("d"),
}),
},
cty.DynamicVal,
false,
},
{ // merge with conflicts is ok, last in wins { // merge with conflicts is ok, last in wins
[]cty.Value{ []cty.Value{
cty.MapVal(map[string]cty.Value{ cty.MapVal(map[string]cty.Value{
@ -1548,6 +1706,10 @@ func TestSlice(t *testing.T) {
cty.NumberIntVal(1), cty.NumberIntVal(1),
cty.NumberIntVal(2), cty.NumberIntVal(2),
}) })
listWithUnknowns := cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.UnknownVal(cty.String),
})
tests := []struct { tests := []struct {
List cty.Value List cty.Value
StartIndex cty.Value StartIndex cty.Value
@ -1564,6 +1726,13 @@ func TestSlice(t *testing.T) {
}), }),
false, false,
}, },
{ // unknowns in the list
listWithUnknowns,
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.UnknownVal(cty.List(cty.String)),
false,
},
{ // normal usage { // normal usage
listOfInts, listOfInts,
cty.NumberIntVal(1), cty.NumberIntVal(1),
@ -1661,6 +1830,13 @@ func TestTranspose(t *testing.T) {
}), }),
false, false,
}, },
{ // map - unknown value
cty.MapVal(map[string]cty.Value{
"key1": cty.UnknownVal(cty.List(cty.String)),
}),
cty.UnknownVal(cty.Map(cty.List(cty.String))),
false,
},
{ // bad map - empty value { // bad map - empty value
cty.MapVal(map[string]cty.Value{ cty.MapVal(map[string]cty.Value{
"key1": cty.ListValEmpty(cty.String), "key1": cty.ListValEmpty(cty.String),
@ -1736,6 +1912,14 @@ func TestValues(t *testing.T) {
}), }),
false, false,
}, },
{ // map with unknowns
cty.MapVal(map[string]cty.Value{
"hello": cty.ListVal([]cty.Value{cty.StringVal("world")}),
"what's": cty.UnknownVal(cty.List(cty.String)),
}),
cty.UnknownVal(cty.List(cty.List(cty.String))),
false,
},
} }
for _, test := range tests { for _, test := range tests {