terraform/lang/funcs/collection_test.go

1565 lines
30 KiB
Go
Raw Normal View History

package funcs
import (
"fmt"
"math"
"testing"
"github.com/zclconf/go-cty/cty"
)
func TestLength(t *testing.T) {
tests := []struct {
Value cty.Value
Want cty.Value
}{
{
cty.ListValEmpty(cty.Number),
cty.NumberIntVal(0),
},
{
cty.ListVal([]cty.Value{cty.True}),
cty.NumberIntVal(1),
},
{
cty.ListVal([]cty.Value{cty.UnknownVal(cty.Bool)}),
cty.NumberIntVal(1),
},
{
cty.SetValEmpty(cty.Number),
cty.NumberIntVal(0),
},
{
cty.SetVal([]cty.Value{cty.True}),
cty.NumberIntVal(1),
},
{
cty.MapValEmpty(cty.Bool),
cty.NumberIntVal(0),
},
{
cty.MapVal(map[string]cty.Value{"hello": cty.True}),
cty.NumberIntVal(1),
},
{
cty.EmptyTupleVal,
cty.NumberIntVal(0),
},
{
cty.UnknownVal(cty.EmptyTuple),
cty.NumberIntVal(0),
},
{
cty.TupleVal([]cty.Value{cty.True}),
cty.NumberIntVal(1),
},
{
cty.EmptyObjectVal,
cty.NumberIntVal(0),
},
{
cty.UnknownVal(cty.EmptyObject),
cty.NumberIntVal(0),
},
{
cty.ObjectVal(map[string]cty.Value{"true": cty.True}),
cty.NumberIntVal(1),
},
{
cty.UnknownVal(cty.List(cty.Bool)),
cty.UnknownVal(cty.Number),
},
{
cty.DynamicVal,
cty.UnknownVal(cty.Number),
},
{
cty.StringVal("hello"),
cty.NumberIntVal(5),
},
{
cty.StringVal(""),
cty.NumberIntVal(0),
},
{
cty.StringVal("1"),
cty.NumberIntVal(1),
},
{
cty.StringVal("Живой Журнал"),
cty.NumberIntVal(12),
},
{
// note that the dieresis here is intentionally a combining
// ligature.
cty.StringVal("noël"),
cty.NumberIntVal(4),
},
{
// The Es in this string has three combining acute accents.
// This tests something that NFC-normalization cannot collapse
// into a single precombined codepoint, since otherwise we might
// be cheating and relying on the single-codepoint forms.
cty.StringVal("wé́́é́́é́́!"),
cty.NumberIntVal(5),
},
{
// Go's normalization forms don't handle this ligature, so we
// will produce the wrong result but this is now a compatibility
// constraint and so we'll test it.
cty.StringVal("baffle"),
cty.NumberIntVal(4),
},
{
cty.StringVal("😸😾"),
cty.NumberIntVal(2),
},
{
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.Number),
},
{
cty.DynamicVal,
cty.UnknownVal(cty.Number),
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("Length(%#v)", test.Value), func(t *testing.T) {
got, err := Length(test.Value)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
func TestAllTrue(t *testing.T) {
tests := []struct {
Collection cty.Value
Want cty.Value
Err bool
}{
{
cty.ListValEmpty(cty.Bool),
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.True}),
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.False}),
cty.False,
false,
},
{
cty.ListVal([]cty.Value{cty.True, cty.False}),
cty.False,
false,
},
{
cty.ListVal([]cty.Value{cty.False, cty.True}),
cty.False,
false,
},
{
cty.ListVal([]cty.Value{cty.True, cty.NullVal(cty.Bool)}),
cty.False,
false,
},
{
cty.ListVal([]cty.Value{cty.UnknownVal(cty.Bool)}),
cty.UnknownVal(cty.Bool),
false,
},
{
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.Bool),
cty.UnknownVal(cty.Bool),
}),
cty.UnknownVal(cty.Bool),
false,
},
{
cty.UnknownVal(cty.List(cty.Bool)),
cty.UnknownVal(cty.Bool),
false,
},
{
cty.NullVal(cty.List(cty.Bool)),
cty.NilVal,
true,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("alltrue(%#v)", test.Collection), func(t *testing.T) {
got, err := AllTrue(test.Collection)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
func TestAnyTrue(t *testing.T) {
tests := []struct {
Collection cty.Value
Want cty.Value
Err bool
}{
{
cty.ListValEmpty(cty.Bool),
cty.False,
false,
},
{
cty.ListVal([]cty.Value{cty.True}),
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.False}),
cty.False,
false,
},
{
cty.ListVal([]cty.Value{cty.True, cty.False}),
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.False, cty.True}),
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.NullVal(cty.Bool), cty.True}),
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.UnknownVal(cty.Bool)}),
cty.UnknownVal(cty.Bool),
false,
},
{
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.Bool),
cty.False,
}),
cty.UnknownVal(cty.Bool),
false,
},
{
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.Bool),
cty.True,
}),
cty.True,
false,
},
{
cty.UnknownVal(cty.List(cty.Bool)),
cty.UnknownVal(cty.Bool),
false,
},
{
cty.NullVal(cty.List(cty.Bool)),
cty.NilVal,
true,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("anytrue(%#v)", test.Collection), func(t *testing.T) {
got, err := AnyTrue(test.Collection)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
func TestCoalesce(t *testing.T) {
tests := []struct {
Values []cty.Value
Want cty.Value
Err bool
}{
{
[]cty.Value{cty.StringVal("first"), cty.StringVal("second"), cty.StringVal("third")},
cty.StringVal("first"),
false,
},
{
[]cty.Value{cty.StringVal(""), cty.StringVal("second"), cty.StringVal("third")},
cty.StringVal("second"),
false,
},
{
[]cty.Value{cty.StringVal(""), cty.StringVal("")},
cty.NilVal,
true,
},
{
[]cty.Value{cty.True},
cty.True,
false,
},
{
[]cty.Value{cty.NullVal(cty.Bool), cty.True},
cty.True,
false,
},
{
[]cty.Value{cty.NullVal(cty.Bool), cty.False},
cty.False,
false,
},
{
[]cty.Value{cty.NullVal(cty.Bool), cty.False, cty.StringVal("hello")},
cty.StringVal("false"),
false,
},
{
[]cty.Value{cty.True, cty.UnknownVal(cty.Bool)},
cty.True,
false,
},
{
[]cty.Value{cty.UnknownVal(cty.Bool), cty.True},
cty.UnknownVal(cty.Bool),
false,
},
{
[]cty.Value{cty.UnknownVal(cty.Bool), cty.StringVal("hello")},
cty.UnknownVal(cty.String),
false,
},
{
[]cty.Value{cty.DynamicVal, cty.True},
cty.UnknownVal(cty.Bool),
false,
},
{
[]cty.Value{cty.DynamicVal},
cty.DynamicVal,
false,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("Coalesce(%#v...)", test.Values), func(t *testing.T) {
got, err := Coalesce(test.Values...)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
2020-02-24 15:38:00 +01:00
func TestIndex(t *testing.T) {
tests := []struct {
2020-02-24 15:38:00 +01:00
List cty.Value
Value cty.Value
Want cty.Value
Err bool
}{
{
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
2020-02-24 15:38:00 +01:00
cty.StringVal("a"),
cty.NumberIntVal(0),
false,
},
{
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("a"),
cty.StringVal("b"),
cty.UnknownVal(cty.String),
}),
2020-02-24 15:38:00 +01:00
cty.StringVal("a"),
cty.NumberIntVal(0),
false,
},
{
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
2020-02-24 15:38:00 +01:00
cty.StringVal("b"),
cty.NumberIntVal(1),
false,
},
2020-02-24 15:38:00 +01:00
{
2018-05-24 23:46:03 +02:00
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
2018-05-24 23:46:03 +02:00
}),
2020-02-24 15:38:00 +01:00
cty.StringVal("z"),
cty.NilVal,
true,
},
2020-02-24 15:38:00 +01:00
{
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("1"),
cty.StringVal("2"),
cty.StringVal("3"),
}),
2020-02-24 15:38:00 +01:00
cty.NumberIntVal(1),
cty.NumberIntVal(0),
true,
},
2020-02-24 15:38:00 +01:00
{
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.NumberIntVal(3),
}),
2020-02-24 15:38:00 +01:00
cty.NumberIntVal(2),
cty.NumberIntVal(1),
false,
},
2020-02-24 15:38:00 +01:00
{
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.NumberIntVal(3),
}),
cty.NumberIntVal(4),
cty.NilVal,
true,
},
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.NumberIntVal(3),
}),
cty.StringVal("1"),
cty.NumberIntVal(0),
true,
},
{
cty.TupleVal([]cty.Value{
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.NumberIntVal(3),
}),
2020-02-24 15:38:00 +01:00
cty.NumberIntVal(1),
cty.NumberIntVal(0),
false,
},
2020-02-24 15:38:00 +01:00
}
for _, test := range tests {
t.Run(fmt.Sprintf("index(%#v, %#v)", test.List, test.Value), func(t *testing.T) {
got, err := Index(test.List, test.Value)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
func TestLookup(t *testing.T) {
simpleMap := cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
})
intsMap := cty.MapVal(map[string]cty.Value{
"foo": cty.NumberIntVal(42),
})
mapOfLists := cty.MapVal(map[string]cty.Value{
"foo": cty.ListVal([]cty.Value{
cty.StringVal("bar"),
cty.StringVal("baz"),
}),
})
mapOfMaps := cty.MapVal(map[string]cty.Value{
"foo": cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("bar"),
}),
"baz": cty.MapVal(map[string]cty.Value{
"b": cty.StringVal("bat"),
}),
})
mapOfTuples := cty.MapVal(map[string]cty.Value{
"foo": cty.TupleVal([]cty.Value{cty.StringVal("bar")}),
"baz": cty.TupleVal([]cty.Value{cty.StringVal("bat")}),
})
objectOfMaps := cty.ObjectVal(map[string]cty.Value{
"foo": cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("bar"),
}),
"baz": cty.MapVal(map[string]cty.Value{
"b": cty.StringVal("bat"),
}),
})
mapWithUnknowns := cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
"baz": cty.UnknownVal(cty.String),
})
mapWithObjects := cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
"baz": cty.NumberIntVal(42),
})
2018-05-24 23:46:03 +02:00
tests := []struct {
2020-02-24 15:38:00 +01:00
Values []cty.Value
Want cty.Value
Err bool
2018-05-24 23:46:03 +02:00
}{
{
2020-02-24 15:38:00 +01:00
[]cty.Value{
simpleMap,
cty.StringVal("foo"),
},
cty.StringVal("bar"),
2018-05-24 23:46:03 +02:00
false,
},
{
2020-02-24 15:38:00 +01:00
[]cty.Value{
mapWithObjects,
cty.StringVal("foo"),
},
cty.StringVal("bar"),
false,
},
{
2020-02-24 15:38:00 +01:00
[]cty.Value{
intsMap,
cty.StringVal("foo"),
},
cty.NumberIntVal(42),
false,
},
{
2020-02-24 15:38:00 +01:00
[]cty.Value{
mapOfMaps,
cty.StringVal("foo"),
},
cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("bar"),
}),
false,
},
2018-05-25 00:20:22 +02:00
{
2020-02-24 15:38:00 +01:00
[]cty.Value{
objectOfMaps,
cty.StringVal("foo"),
},
cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("bar"),
2018-05-25 00:20:22 +02:00
}),
false,
},
{
2020-02-24 15:38:00 +01:00
[]cty.Value{
mapOfTuples,
cty.StringVal("foo"),
},
cty.TupleVal([]cty.Value{cty.StringVal("bar")}),
false,
},
2020-02-24 15:38:00 +01:00
{ // Invalid key
[]cty.Value{
simpleMap,
cty.StringVal("bar"),
},
2018-05-25 00:20:22 +02:00
cty.NilVal,
true,
},
2020-02-24 15:38:00 +01:00
{ // Invalid key
[]cty.Value{
mapWithObjects,
cty.StringVal("bar"),
},
cty.NilVal,
true,
2018-05-25 21:57:26 +02:00
},
2020-02-24 15:38:00 +01:00
{ // Supplied default with valid key
[]cty.Value{
simpleMap,
cty.StringVal("foo"),
cty.StringVal(""),
},
cty.StringVal("bar"),
2018-05-25 21:57:26 +02:00
false,
},
2020-02-24 15:38:00 +01:00
{ // Supplied default with valid (int) key
[]cty.Value{
simpleMap,
cty.StringVal("foo"),
cty.NumberIntVal(-1),
},
cty.StringVal("bar"),
2018-05-25 21:57:26 +02:00
false,
},
2020-02-24 15:38:00 +01:00
{ // Supplied default with valid (int) key
[]cty.Value{
simpleMap,
cty.StringVal("foobar"),
cty.NumberIntVal(-1),
},
cty.StringVal("-1"),
2018-05-25 21:57:26 +02:00
false,
},
2020-02-24 15:38:00 +01:00
{ // Supplied default with valid key
[]cty.Value{
mapWithObjects,
cty.StringVal("foobar"),
cty.StringVal(""),
},
cty.StringVal(""),
false,
},
2020-02-24 15:38:00 +01:00
{ // Supplied default with invalid key
[]cty.Value{
simpleMap,
cty.StringVal("baz"),
cty.StringVal(""),
},
cty.StringVal(""),
false,
},
2020-02-24 15:38:00 +01:00
{ // Supplied default with type mismatch: expects a map return
[]cty.Value{
mapOfMaps,
cty.StringVal("foo"),
cty.StringVal(""),
},
cty.NilVal,
true,
},
{ // Supplied non-empty default with invalid key
[]cty.Value{
simpleMap,
cty.StringVal("bar"),
cty.StringVal("xyz"),
},
cty.StringVal("xyz"),
false,
},
{ // too many args
[]cty.Value{
simpleMap,
cty.StringVal("foo"),
cty.StringVal("bar"),
cty.StringVal("baz"),
},
cty.NilVal,
true,
},
{ // cannot search a map of lists
[]cty.Value{
mapOfLists,
cty.StringVal("baz"),
},
cty.NilVal,
true,
},
{
[]cty.Value{
mapWithUnknowns,
cty.StringVal("baz"),
},
cty.UnknownVal(cty.String),
false,
},
{
[]cty.Value{
mapWithUnknowns,
cty.StringVal("foo"),
},
cty.StringVal("bar"),
false,
},
2020-02-24 15:38:00 +01:00
{
[]cty.Value{
simpleMap,
cty.UnknownVal(cty.String),
},
cty.UnknownVal(cty.String),
false,
},
{
[]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("a"),
"bar": cty.StringVal("b"),
}),
cty.UnknownVal(cty.String),
},
cty.DynamicVal, // if the key is unknown then we don't know which object attribute and thus can't know the type
false,
},
2018-05-25 21:57:26 +02:00
}
for _, test := range tests {
2020-02-24 15:38:00 +01:00
t.Run(fmt.Sprintf("lookup(%#v)", test.Values), func(t *testing.T) {
got, err := Lookup(test.Values...)
2018-05-25 21:57:26 +02:00
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
2018-05-25 21:57:26 +02:00
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
2020-02-24 15:38:00 +01:00
func TestMatchkeys(t *testing.T) {
2018-05-26 01:15:50 +02:00
tests := []struct {
2020-02-24 15:38:00 +01:00
Keys cty.Value
Values cty.Value
Searchset cty.Value
Want cty.Value
Err bool
2018-05-26 01:15:50 +02:00
}{
2020-02-24 15:38:00 +01:00
{ // normal usage
2018-05-26 01:15:50 +02:00
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
2020-02-24 15:38:00 +01:00
cty.StringVal("c"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref1"),
cty.StringVal("ref2"),
cty.StringVal("ref3"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref1"),
2018-05-26 01:15:50 +02:00
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
false,
},
2020-02-24 15:38:00 +01:00
{ // normal usage 2, check the order
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
2020-02-24 15:38:00 +01:00
cty.StringVal("c"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref1"),
cty.StringVal("ref2"),
cty.StringVal("ref3"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref2"),
cty.StringVal("ref1"),
}),
2018-05-26 01:15:50 +02:00
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
2020-02-24 15:38:00 +01:00
false,
},
{ // no matches
2018-05-26 01:15:50 +02:00
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("ref1"),
cty.StringVal("ref2"),
cty.StringVal("ref3"),
2018-05-26 01:15:50 +02:00
}),
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("ref4"),
2018-05-26 01:15:50 +02:00
}),
2020-02-24 15:38:00 +01:00
cty.ListValEmpty(cty.String),
2018-05-26 01:15:50 +02:00
false,
},
2020-02-24 15:38:00 +01:00
{ // no matches 2
2018-05-26 01:15:50 +02:00
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
2018-05-26 01:15:50 +02:00
}),
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("ref1"),
cty.StringVal("ref2"),
cty.StringVal("ref3"),
2018-05-26 01:15:50 +02:00
}),
2020-02-24 15:38:00 +01:00
cty.ListValEmpty(cty.String),
cty.ListValEmpty(cty.String),
2018-05-26 01:15:50 +02:00
false,
},
2020-02-24 15:38:00 +01:00
{ // zero case
cty.ListValEmpty(cty.String),
cty.ListValEmpty(cty.String),
cty.ListVal([]cty.Value{cty.StringVal("nope")}),
cty.ListValEmpty(cty.String),
2018-05-26 01:15:50 +02:00
false,
},
2020-02-24 15:38:00 +01:00
{ // complex values
2018-05-26 01:15:50 +02:00
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
2020-02-24 15:38:00 +01:00
cty.StringVal("a"),
2018-05-26 01:15:50 +02:00
}),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
2020-02-24 15:38:00 +01:00
cty.StringVal("a"),
2018-05-26 01:15:50 +02:00
}),
}),
false,
},
2020-02-24 15:38:00 +01:00
{ // unknowns
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.UnknownVal(cty.String),
}),
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("ref1"),
cty.StringVal("ref2"),
cty.UnknownVal(cty.String),
}),
2018-05-30 16:26:19 +02:00
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.StringVal("ref1"),
2018-05-30 16:26:19 +02:00
}),
2020-02-24 15:38:00 +01:00
cty.UnknownVal(cty.List(cty.String)),
2018-05-30 16:26:19 +02:00
false,
},
2020-02-24 15:38:00 +01:00
{ // different types that can be unified
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
2020-02-24 15:38:00 +01:00
cty.ListVal([]cty.Value{
cty.NumberIntVal(1),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
2018-05-30 16:26:19 +02:00
cty.ListValEmpty(cty.String),
false,
},
2020-02-24 15:38:00 +01:00
{ // complex values: values is a different type from keys and searchset
2018-05-31 23:46:24 +02:00
cty.ListVal([]cty.Value{
2020-02-24 15:38:00 +01:00
cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("baz"),
}),
cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("beep"),
}),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("c"),
}),
cty.ListVal([]cty.Value{
cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("beep"),
}),
}),
false,
},
2020-02-24 15:38:00 +01:00
// errors
{ // different types
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.NilVal,
true,
},
2020-02-24 15:38:00 +01:00
{ // lists of different length
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.NilVal,
true,
},
2018-05-31 23:46:24 +02:00
}
2020-02-24 15:38:00 +01:00
for _, test := range tests {
t.Run(fmt.Sprintf("matchkeys(%#v, %#v, %#v)", test.Keys, test.Values, test.Searchset), func(t *testing.T) {
got, err := Matchkeys(test.Keys, test.Values, test.Searchset)
2018-05-31 23:46:24 +02:00
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
lang/funcs: "one" function In the Terraform language we typically use lists of zero or one values in some sense interchangably with single values that might be null, because various Terraform language constructs are designed to work with collections rather than with nullable values. In Terraform v0.12 we made the splat operator [*] have a "special power" of concisely converting from a possibly-null single value into a zero-or-one list as a way to make that common operation more concise. In a sense this "one" function is the opposite operation to that special power: it goes from a zero-or-one collection (list, set, or tuple) to a possibly-null single value. This is a concise alternative to the following clunky conditional expression, with the additional benefit that the following expression is also not viable for set values, and it also properly handles the case where there's unexpectedly more than one value: length(var.foo) != 0 ? var.foo[0] : null Instead, we can write: one(var.foo) As with the splat operator, this is a tricky tradeoff because it could be argued that it's not something that'd be immediately intuitive to someone unfamiliar with Terraform. However, I think that's justified given how often zero-or-one collections arise in typical Terraform configurations. Unlike the splat operator, it should at least be easier to search for its name and find its documentation the first time you see it in a configuration. My expectation that this will become a common pattern is also my justification for giving it a short, concise name. Arguably it could be better named something like "oneornull", but that's a pretty clunky name and I'm not convinced it really adds any clarity for someone who isn't already familiar with it.
2021-01-09 01:02:56 +01:00
}
})
}
}
func TestOne(t *testing.T) {
tests := []struct {
List cty.Value
Want cty.Value
Err string
}{
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(1),
}),
cty.NumberIntVal(1),
"",
},
{
cty.ListValEmpty(cty.Number),
cty.NullVal(cty.Number),
"",
},
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.NumberIntVal(3),
}),
cty.NilVal,
"must be a list, set, or tuple value with either zero or one elements",
},
{
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.Number),
}),
cty.UnknownVal(cty.Number),
"",
},
{
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number),
}),
cty.NilVal,
"must be a list, set, or tuple value with either zero or one elements",
},
{
cty.UnknownVal(cty.List(cty.String)),
cty.UnknownVal(cty.String),
"",
},
{
cty.NullVal(cty.List(cty.String)),
cty.NilVal,
"argument must not be null",
},
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(1),
}).Mark("boop"),
cty.NumberIntVal(1).Mark("boop"),
"",
},
{
cty.ListValEmpty(cty.Bool).Mark("boop"),
cty.NullVal(cty.Bool).Mark("boop"),
"",
},
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(1).Mark("boop"),
}),
cty.NumberIntVal(1).Mark("boop"),
"",
},
{
cty.SetVal([]cty.Value{
cty.NumberIntVal(1),
}),
cty.NumberIntVal(1),
"",
},
{
cty.SetValEmpty(cty.Number),
cty.NullVal(cty.Number),
"",
},
{
cty.SetVal([]cty.Value{
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.NumberIntVal(3),
}),
cty.NilVal,
"must be a list, set, or tuple value with either zero or one elements",
},
{
cty.SetVal([]cty.Value{
cty.UnknownVal(cty.Number),
}),
cty.UnknownVal(cty.Number),
"",
},
{
cty.SetVal([]cty.Value{
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number),
}),
// The above would be valid if those two unknown values were
// equal known values, so this returns unknown rather than failing.
cty.UnknownVal(cty.Number),
"",
},
{
cty.UnknownVal(cty.Set(cty.String)),
cty.UnknownVal(cty.String),
"",
},
{
cty.NullVal(cty.Set(cty.String)),
cty.NilVal,
"argument must not be null",
},
{
cty.SetVal([]cty.Value{
cty.NumberIntVal(1),
}).Mark("boop"),
cty.NumberIntVal(1).Mark("boop"),
"",
},
{
cty.SetValEmpty(cty.Bool).Mark("boop"),
cty.NullVal(cty.Bool).Mark("boop"),
"",
},
{
cty.SetVal([]cty.Value{
cty.NumberIntVal(1).Mark("boop"),
}),
cty.NumberIntVal(1).Mark("boop"),
"",
},
{
cty.TupleVal([]cty.Value{
cty.NumberIntVal(1),
}),
cty.NumberIntVal(1),
"",
},
{
cty.EmptyTupleVal,
cty.NullVal(cty.DynamicPseudoType),
"",
},
{
cty.TupleVal([]cty.Value{
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.NumberIntVal(3),
}),
cty.NilVal,
"must be a list, set, or tuple value with either zero or one elements",
},
{
cty.TupleVal([]cty.Value{
cty.UnknownVal(cty.Number),
}),
cty.UnknownVal(cty.Number),
"",
},
{
cty.TupleVal([]cty.Value{
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number),
}),
cty.NilVal,
"must be a list, set, or tuple value with either zero or one elements",
},
{
cty.UnknownVal(cty.EmptyTuple),
// Could actually return null here, but don't for consistency with unknown lists
cty.UnknownVal(cty.DynamicPseudoType),
"",
},
{
cty.UnknownVal(cty.Tuple([]cty.Type{cty.Bool})),
cty.UnknownVal(cty.Bool),
"",
},
{
cty.UnknownVal(cty.Tuple([]cty.Type{cty.Bool, cty.Number})),
cty.NilVal,
"must be a list, set, or tuple value with either zero or one elements",
},
{
cty.NullVal(cty.EmptyTuple),
cty.NilVal,
"argument must not be null",
},
{
cty.NullVal(cty.Tuple([]cty.Type{cty.Bool})),
cty.NilVal,
"argument must not be null",
},
{
cty.NullVal(cty.Tuple([]cty.Type{cty.Bool, cty.Number})),
cty.NilVal,
"argument must not be null",
},
{
cty.TupleVal([]cty.Value{
cty.NumberIntVal(1),
}).Mark("boop"),
cty.NumberIntVal(1).Mark("boop"),
"",
},
{
cty.EmptyTupleVal.Mark("boop"),
cty.NullVal(cty.DynamicPseudoType).Mark("boop"),
"",
},
{
cty.TupleVal([]cty.Value{
cty.NumberIntVal(1).Mark("boop"),
}),
cty.NumberIntVal(1).Mark("boop"),
"",
},
{
cty.DynamicVal,
cty.DynamicVal,
"",
},
{
cty.NullVal(cty.DynamicPseudoType),
cty.NilVal,
"argument must not be null",
},
{
cty.MapValEmpty(cty.String),
cty.NilVal,
"must be a list, set, or tuple value with either zero or one elements",
},
{
cty.EmptyObjectVal,
cty.NilVal,
"must be a list, set, or tuple value with either zero or one elements",
},
{
cty.True,
cty.NilVal,
"must be a list, set, or tuple value with either zero or one elements",
},
{
cty.UnknownVal(cty.Bool),
cty.NilVal,
"must be a list, set, or tuple value with either zero or one elements",
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("one(%#v)", test.List), func(t *testing.T) {
got, err := One(test.List)
if test.Err != "" {
if err == nil {
t.Fatal("succeeded; want error")
} else if got, want := err.Error(), test.Err; got != want {
t.Fatalf("wrong error\n got: %s\nwant: %s", got, want)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !test.Want.RawEquals(got) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
2018-05-31 23:46:24 +02:00
}
})
}
}
func TestSum(t *testing.T) {
tests := []struct {
List cty.Value
Want cty.Value
Err string
}{
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.NumberIntVal(3),
}),
cty.NumberIntVal(6),
"",
},
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(1476),
cty.NumberIntVal(2093),
cty.NumberIntVal(2092495),
cty.NumberIntVal(64589234),
cty.NumberIntVal(234),
}),
cty.NumberIntVal(66685532),
"",
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.UnknownVal(cty.String),
"argument must be list, set, or tuple of number values",
},
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(10),
cty.NumberIntVal(-19),
cty.NumberIntVal(5),
}),
cty.NumberIntVal(-4),
"",
},
{
cty.ListVal([]cty.Value{
cty.NumberFloatVal(10.2),
cty.NumberFloatVal(19.4),
cty.NumberFloatVal(5.7),
}),
cty.NumberFloatVal(35.3),
"",
},
{
cty.ListVal([]cty.Value{
cty.NumberFloatVal(-10.2),
cty.NumberFloatVal(-19.4),
cty.NumberFloatVal(-5.7),
}),
cty.NumberFloatVal(-35.3),
"",
},
{
cty.ListVal([]cty.Value{cty.NullVal(cty.Number)}),
cty.NilVal,
"argument must be list, set, or tuple of number values",
},
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(5),
cty.NullVal(cty.Number),
}),
cty.NilVal,
"argument must be list, set, or tuple of number values",
},
{
cty.SetVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.UnknownVal(cty.String),
"argument must be list, set, or tuple of number values",
},
{
cty.SetVal([]cty.Value{
cty.NumberIntVal(10),
cty.NumberIntVal(-19),
cty.NumberIntVal(5),
}),
cty.NumberIntVal(-4),
"",
},
{
cty.SetVal([]cty.Value{
cty.NumberIntVal(10),
cty.NumberIntVal(25),
cty.NumberIntVal(30),
}),
cty.NumberIntVal(65),
"",
},
{
cty.SetVal([]cty.Value{
cty.NumberFloatVal(2340.8),
cty.NumberFloatVal(10.2),
cty.NumberFloatVal(3),
}),
cty.NumberFloatVal(2354),
"",
},
{
cty.SetVal([]cty.Value{
cty.NumberFloatVal(2),
}),
cty.NumberFloatVal(2),
"",
},
{
cty.SetVal([]cty.Value{
cty.NumberFloatVal(-2),
cty.NumberFloatVal(-50),
cty.NumberFloatVal(-20),
cty.NumberFloatVal(-123),
cty.NumberFloatVal(-4),
}),
cty.NumberFloatVal(-199),
"",
},
{
cty.TupleVal([]cty.Value{
cty.NumberIntVal(12),
cty.StringVal("a"),
cty.NumberIntVal(38),
}),
cty.UnknownVal(cty.String),
"argument must be list, set, or tuple of number values",
},
{
cty.NumberIntVal(12),
cty.NilVal,
"cannot sum noniterable",
},
{
cty.ListValEmpty(cty.Number),
cty.NilVal,
"cannot sum an empty list",
},
{
cty.MapVal(map[string]cty.Value{"hello": cty.True}),
cty.NilVal,
"argument must be list, set, or tuple. Received map of bool",
},
{
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number),
"",
},
{
cty.UnknownVal(cty.List(cty.Number)),
cty.UnknownVal(cty.Number),
"",
},
{ // known list containing unknown values
cty.ListVal([]cty.Value{cty.UnknownVal(cty.Number)}),
cty.UnknownVal(cty.Number),
"",
},
{ // numbers too large to represent as float64
cty.ListVal([]cty.Value{
cty.MustParseNumberVal("1e+500"),
cty.MustParseNumberVal("1e+500"),
}),
cty.MustParseNumberVal("2e+500"),
"",
},
{ // edge case we have a special error handler for
cty.ListVal([]cty.Value{
cty.NumberFloatVal(math.Inf(1)),
cty.NumberFloatVal(math.Inf(-1)),
}),
cty.NilVal,
"can't compute sum of opposing infinities",
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("sum(%#v)", test.List), func(t *testing.T) {
got, err := Sum(test.List)
if test.Err != "" {
if err == nil {
t.Fatal("succeeded; want error")
} else if got, want := err.Error(), test.Err; got != want {
t.Fatalf("wrong error\n got: %s\nwant: %s", got, want)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
2018-05-31 23:46:24 +02:00
func TestTranspose(t *testing.T) {
tests := []struct {
Values cty.Value
Want cty.Value
Err bool
}{
{
cty.MapVal(map[string]cty.Value{
"key1": cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
"key2": cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
"key3": cty.ListVal([]cty.Value{
cty.StringVal("c"),
}),
"key4": cty.ListValEmpty(cty.String),
}),
cty.MapVal(map[string]cty.Value{
"a": cty.ListVal([]cty.Value{
cty.StringVal("key1"),
cty.StringVal("key2"),
}),
"b": cty.ListVal([]cty.Value{
cty.StringVal("key1"),
cty.StringVal("key2"),
}),
"c": cty.ListVal([]cty.Value{
cty.StringVal("key2"),
cty.StringVal("key3"),
}),
}),
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,
},
2018-05-31 23:46:24 +02:00
{ // bad map - empty value
cty.MapVal(map[string]cty.Value{
"key1": cty.ListValEmpty(cty.String),
}),
cty.MapValEmpty(cty.List(cty.String)),
false,
2018-05-31 23:46:24 +02:00
},
{ // bad map - value not a list
cty.MapVal(map[string]cty.Value{
"key1": cty.StringVal("a"),
}),
cty.NilVal,
true,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("transpose(%#v)", test.Values), func(t *testing.T) {
got, err := Transpose(test.Values)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}