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)),
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
for it := args[0].ElementIterator(); it.Next(); {
for it := listVal.ElementIterator(); it.Next(); {
_, v := it.Element()
if v.AsString() == "" {
continue
@ -263,11 +270,19 @@ var DistinctFunc = function.New(&function.Spec{
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) {
listVal := args[0]
if !listVal.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
var list []cty.Value
for it := args[0].ElementIterator(); it.Next(); {
for it := listVal.ElementIterator(); it.Next(); {
_, v := it.Element()
list, err = appendIfMissing(list, v)
if err != nil {
@ -296,6 +311,11 @@ var ChunklistFunc = function.New(&function.Spec{
return cty.List(args[0].Type()), nil
},
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
err = gocty.FromCtyValue(args[1], &size)
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 == 0 {
output = append(output, args[0])
output = append(output, listVal)
return cty.ListVal(output), nil
}
@ -319,7 +339,7 @@ var ChunklistFunc = function.New(&function.Spec{
l := args[0].LengthInt()
i := 0
for it := args[0].ElementIterator(); it.Next(); {
for it := listVal.ElementIterator(); it.Next(); {
_, v := it.Element()
chunk = append(chunk, v)
@ -347,9 +367,12 @@ var FlattenFunc = function.New(&function.Spec{
Type: function.StaticReturnType(cty.List(cty.DynamicPseudoType)),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
inputList := args[0]
if !inputList.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
if inputList.LengthInt() == 0 {
return cty.ListValEmpty(cty.DynamicPseudoType), nil
return cty.ListValEmpty(retType.ElementType()), nil
}
outputList := make([]cty.Value, 0)
@ -458,11 +481,14 @@ var LookupFunc = function.New(&function.Spec{
AllowDynamicType: true,
AllowNull: true,
},
Type: function.StaticReturnType(cty.DynamicPseudoType),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
Type: func(args []cty.Value) (ret cty.Type, err error) {
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
defaultValueSet := false
@ -474,6 +500,10 @@ var LookupFunc = function.New(&function.Spec{
mapVar := args[0]
lookupKey := args[1].AsString()
if !mapVar.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True {
v := mapVar.Index(cty.StringVal(lookupKey))
if ty := v.Type(); !ty.Equals(cty.NilType) {
@ -489,13 +519,11 @@ var LookupFunc = function.New(&function.Spec{
}
if defaultValueSet {
defaultType := defaultVal.Type()
switch {
case defaultType.Equals(cty.String):
return cty.StringVal(defaultVal.AsString()), nil
case defaultType.Equals(cty.Number):
return cty.NumberVal(defaultVal.AsBigFloat()), nil
defaultVal, err = convert.Convert(defaultVal, retType)
if err != nil {
return cty.NilVal, err
}
return defaultVal, nil
}
return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf(
@ -537,6 +565,12 @@ var MapFunc = function.New(&function.Spec{
return cty.Map(valType), nil
},
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)
for i := 0; i < len(args); i += 2 {
@ -612,6 +646,10 @@ var MatchkeysFunc = function.New(&function.Spec{
return cty.ListValEmpty(retType.ElementType()), nil
}
if !args[0].IsWhollyKnown() || !args[0].IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
i := 0
for it := keys.ElementIterator(); it.Next(); {
_, key := it.Element()
@ -659,7 +697,9 @@ var MergeFunc = function.New(&function.Spec{
outputMap := make(map[string]cty.Value)
for _, arg := range args {
if !arg.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
if !arg.Type().IsObjectType() && !arg.Type().IsMapType() {
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) {
inputList := args[0]
if !inputList.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
var startIndex, endIndex int
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) {
values := args[0]
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
return cty.List(args[0].Type().ElementType()), nil
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
mapVar := args[0]
if !mapVar.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
keys, err := Keys(mapVar)
if err != nil {
return cty.NilVal, err

View File

@ -299,6 +299,34 @@ func TestCoalesceList(t *testing.T) {
}),
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.MapValEmpty(cty.DynamicPseudoType),
@ -375,6 +403,15 @@ func TestCompact(t *testing.T) {
}),
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
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
@ -422,6 +459,12 @@ func TestContains(t *testing.T) {
cty.NumberIntVal(3),
cty.NumberIntVal(4),
})
listWithUnknown := cty.ListVal([]cty.Value{
cty.StringVal("the"),
cty.StringVal("quick"),
cty.StringVal("brown"),
cty.UnknownVal(cty.String),
})
tests := []struct {
List cty.Value
@ -435,6 +478,12 @@ func TestContains(t *testing.T) {
cty.BoolVal(true),
false,
},
{
listWithUnknown,
cty.StringVal("the"),
cty.BoolVal(true),
false,
},
{
listOfStrings,
cty.StringVal("penguin"),
@ -509,6 +558,16 @@ func TestIndex(t *testing.T) {
cty.NumberIntVal(0),
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.StringVal("a"),
@ -620,6 +679,16 @@ func TestDistinct(t *testing.T) {
}),
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.StringVal("a"),
@ -765,6 +834,16 @@ func TestChunklist(t *testing.T) {
}),
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 {
@ -812,6 +891,20 @@ func TestFlatten(t *testing.T) {
}),
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.DynamicPseudoType),
@ -861,6 +954,11 @@ func TestKeys(t *testing.T) {
cty.NilVal,
true,
},
{ // Unknown map
cty.UnknownVal(cty.Map(cty.String)),
cty.UnknownVal(cty.List(cty.String)),
false,
},
}
for _, test := range tests {
@ -927,6 +1025,17 @@ func TestList(t *testing.T) {
}),
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 {
@ -962,6 +1071,10 @@ func TestLookup(t *testing.T) {
cty.StringVal("baz"),
}),
})
mapWithUnknowns := cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
"baz": cty.UnknownVal(cty.String),
})
tests := []struct {
Values []cty.Value
@ -1046,6 +1159,14 @@ func TestLookup(t *testing.T) {
cty.NilVal,
true,
},
{
[]cty.Value{
mapWithUnknowns,
cty.StringVal("baz"),
},
cty.UnknownVal(cty.String),
false,
},
}
for _, test := range tests {
@ -1084,6 +1205,14 @@ func TestMap(t *testing.T) {
}),
false,
},
{
[]cty.Value{
cty.StringVal("hello"),
cty.UnknownVal(cty.String),
},
cty.UnknownVal(cty.Map(cty.String)),
false,
},
{
[]cty.Value{
cty.StringVal("hello"),
@ -1307,6 +1436,23 @@ func TestMatchkeys(t *testing.T) {
}),
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
{ // different types
cty.ListVal([]cty.Value{
@ -1396,6 +1542,18 @@ func TestMerge(t *testing.T) {
}),
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
[]cty.Value{
cty.MapVal(map[string]cty.Value{
@ -1548,6 +1706,10 @@ func TestSlice(t *testing.T) {
cty.NumberIntVal(1),
cty.NumberIntVal(2),
})
listWithUnknowns := cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.UnknownVal(cty.String),
})
tests := []struct {
List cty.Value
StartIndex cty.Value
@ -1564,6 +1726,13 @@ func TestSlice(t *testing.T) {
}),
false,
},
{ // unknowns in the list
listWithUnknowns,
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.UnknownVal(cty.List(cty.String)),
false,
},
{ // normal usage
listOfInts,
cty.NumberIntVal(1),
@ -1661,6 +1830,13 @@ func TestTranspose(t *testing.T) {
}),
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
cty.MapVal(map[string]cty.Value{
"key1": cty.ListValEmpty(cty.String),
@ -1736,6 +1912,14 @@ func TestValues(t *testing.T) {
}),
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 {