terraform/internal/lang/funcs/collection_test.go

1829 lines
37 KiB
Go

package funcs
import (
"fmt"
"math"
"testing"
"github.com/hashicorp/terraform/internal/lang/marks"
"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),
},
{ // Marked collections return a marked length
cty.ListVal([]cty.Value{
cty.StringVal("hello"),
cty.StringVal("world"),
}).Mark("secret"),
cty.NumberIntVal(2).Mark("secret"),
},
{ // Marks on values in unmarked collections do not propagate
cty.ListVal([]cty.Value{
cty.StringVal("hello").Mark("a"),
cty.StringVal("world").Mark("b"),
}),
cty.NumberIntVal(2),
},
{ // Marked strings return a marked length
cty.StringVal("hello world").Mark("secret"),
cty.NumberIntVal(11).Mark("secret"),
},
{ // Marked tuples return a marked length
cty.TupleVal([]cty.Value{
cty.StringVal("hello"),
cty.StringVal("world"),
}).Mark("secret"),
cty.NumberIntVal(2).Mark("secret"),
},
{ // Marks on values in unmarked tuples do not propagate
cty.TupleVal([]cty.Value{
cty.StringVal("hello").Mark("a"),
cty.StringVal("world").Mark("b"),
}),
cty.NumberIntVal(2),
},
{ // Marked objects return a marked length
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("hello"),
"b": cty.StringVal("world"),
"c": cty.StringVal("nice to meet you"),
}).Mark("secret"),
cty.NumberIntVal(3).Mark("secret"),
},
{ // Marks on object attribute values do not propagate
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("hello").Mark("a"),
"b": cty.StringVal("world").Mark("b"),
"c": cty.StringVal("nice to meet you").Mark("c"),
}),
cty.NumberIntVal(3),
},
}
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)
}
})
}
}
func TestIndex(t *testing.T) {
tests := []struct {
List cty.Value
Value cty.Value
Want cty.Value
Err bool
}{
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.StringVal("a"),
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"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.StringVal("b"),
cty.NumberIntVal(1),
false,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.StringVal("z"),
cty.NilVal,
true,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("1"),
cty.StringVal("2"),
cty.StringVal("3"),
}),
cty.NumberIntVal(1),
cty.NumberIntVal(0),
true,
},
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.NumberIntVal(3),
}),
cty.NumberIntVal(2),
cty.NumberIntVal(1),
false,
},
{
cty.ListVal([]cty.Value{
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),
}),
cty.NumberIntVal(1),
cty.NumberIntVal(0),
false,
},
}
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),
})
tests := []struct {
Values []cty.Value
Want cty.Value
Err bool
}{
{
[]cty.Value{
simpleMap,
cty.StringVal("foo"),
},
cty.StringVal("bar"),
false,
},
{
[]cty.Value{
mapWithObjects,
cty.StringVal("foo"),
},
cty.StringVal("bar"),
false,
},
{
[]cty.Value{
intsMap,
cty.StringVal("foo"),
},
cty.NumberIntVal(42),
false,
},
{
[]cty.Value{
mapOfMaps,
cty.StringVal("foo"),
},
cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("bar"),
}),
false,
},
{
[]cty.Value{
objectOfMaps,
cty.StringVal("foo"),
},
cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("bar"),
}),
false,
},
{
[]cty.Value{
mapOfTuples,
cty.StringVal("foo"),
},
cty.TupleVal([]cty.Value{cty.StringVal("bar")}),
false,
},
{ // Invalid key
[]cty.Value{
simpleMap,
cty.StringVal("bar"),
},
cty.NilVal,
true,
},
{ // Invalid key
[]cty.Value{
mapWithObjects,
cty.StringVal("bar"),
},
cty.NilVal,
true,
},
{ // Supplied default with valid key
[]cty.Value{
simpleMap,
cty.StringVal("foo"),
cty.StringVal(""),
},
cty.StringVal("bar"),
false,
},
{ // Supplied default with valid (int) key
[]cty.Value{
simpleMap,
cty.StringVal("foo"),
cty.NumberIntVal(-1),
},
cty.StringVal("bar"),
false,
},
{ // Supplied default with valid (int) key
[]cty.Value{
simpleMap,
cty.StringVal("foobar"),
cty.NumberIntVal(-1),
},
cty.StringVal("-1"),
false,
},
{ // Supplied default with valid key
[]cty.Value{
mapWithObjects,
cty.StringVal("foobar"),
cty.StringVal(""),
},
cty.StringVal(""),
false,
},
{ // Supplied default with invalid key
[]cty.Value{
simpleMap,
cty.StringVal("baz"),
cty.StringVal(""),
},
cty.StringVal(""),
false,
},
{ // 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,
},
{
[]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,
},
{ // successful marked collection lookup returns marked value
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep"),
}).Mark("a"),
cty.StringVal("boop"),
cty.StringVal("nope"),
},
cty.StringVal("beep").Mark("a"),
false,
},
{ // apply collection marks to unknown return vaue
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep"),
"frob": cty.UnknownVal(cty.String),
}).Mark("a"),
cty.StringVal("frob"),
cty.StringVal("nope"),
},
cty.UnknownVal(cty.String).Mark("a"),
false,
},
{ // propagate collection marks to default when returning
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep"),
}).Mark("a"),
cty.StringVal("frob"),
cty.StringVal("nope").Mark("b"),
},
cty.StringVal("nope").WithMarks(cty.NewValueMarks("a", "b")),
false,
},
{ // on unmarked collection, return only marks from found value
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep").Mark("a"),
"frob": cty.StringVal("honk").Mark("b"),
}),
cty.StringVal("frob"),
cty.StringVal("nope").Mark("c"),
},
cty.StringVal("honk").Mark("b"),
false,
},
{ // on unmarked collection, return default exactly on missing
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep").Mark("a"),
"frob": cty.StringVal("honk").Mark("b"),
}),
cty.StringVal("squish"),
cty.StringVal("nope").Mark("c"),
},
cty.StringVal("nope").Mark("c"),
false,
},
{ // retain marks on default if converted
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep").Mark("a"),
"frob": cty.StringVal("honk").Mark("b"),
}),
cty.StringVal("squish"),
cty.NumberIntVal(5).Mark("c"),
},
cty.StringVal("5").Mark("c"),
false,
},
{ // propagate marks from key
[]cty.Value{
cty.MapVal(map[string]cty.Value{
"boop": cty.StringVal("beep"),
"frob": cty.StringVal("honk"),
}),
cty.StringVal("boop").Mark("a"),
cty.StringVal("nope"),
},
cty.StringVal("beep").Mark("a"),
false,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("lookup(%#v)", test.Values), func(t *testing.T) {
got, err := Lookup(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)
}
})
}
}
func TestLookup_error(t *testing.T) {
simpleMap := cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
})
tests := map[string]struct {
Values []cty.Value
WantErr string
}{
"failed to find non-sensitive key": {
[]cty.Value{
simpleMap,
cty.StringVal("boop"),
},
`lookup failed to find key "boop"`,
},
"failed to find sensitive key": {
[]cty.Value{
simpleMap,
cty.StringVal("boop").Mark(marks.Sensitive),
},
"lookup failed to find key (sensitive value)",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
_, err := Lookup(test.Values...)
if err == nil {
t.Fatal("succeeded; want error")
}
if err.Error() != test.WantErr {
t.Errorf("wrong error\ngot: %#v\nwant: %#v", err, test.WantErr)
}
})
}
}
func TestMatchkeys(t *testing.T) {
tests := []struct {
Keys cty.Value
Values cty.Value
Searchset cty.Value
Want cty.Value
Err bool
}{
{ // normal usage
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref1"),
cty.StringVal("ref2"),
cty.StringVal("ref3"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref1"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
false,
},
{ // normal usage 2, check the order
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
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"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
false,
},
{ // no matches
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref1"),
cty.StringVal("ref2"),
cty.StringVal("ref3"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref4"),
}),
cty.ListValEmpty(cty.String),
false,
},
{ // no matches 2
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("ref1"),
cty.StringVal("ref2"),
cty.StringVal("ref3"),
}),
cty.ListValEmpty(cty.String),
cty.ListValEmpty(cty.String),
false,
},
{ // zero case
cty.ListValEmpty(cty.String),
cty.ListValEmpty(cty.String),
cty.ListVal([]cty.Value{cty.StringVal("nope")}),
cty.ListValEmpty(cty.String),
false,
},
{ // complex values
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("a"),
}),
}),
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"),
cty.StringVal("a"),
}),
}),
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,
},
{ // different types that can be unified
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.ListVal([]cty.Value{
cty.NumberIntVal(1),
}),
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.ListValEmpty(cty.String),
false,
},
{ // complex values: values is a different type from keys and searchset
cty.ListVal([]cty.Value{
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,
},
// 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,
},
{ // 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,
},
}
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)
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 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)
}
})
}
}
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)
}
})
}
}
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,
},
{ // bad map - empty value
cty.MapVal(map[string]cty.Value{
"key1": cty.ListValEmpty(cty.String),
}),
cty.MapValEmpty(cty.List(cty.String)),
false,
},
{ // bad map - value not a list
cty.MapVal(map[string]cty.Value{
"key1": cty.StringVal("a"),
}),
cty.NilVal,
true,
},
{ // marks (deep or shallow) on any elements will propegate to the entire return value
cty.MapVal(map[string]cty.Value{
"key1": cty.ListVal([]cty.Value{
cty.StringVal("a").Mark("beep"), // mark on the inner list element
cty.StringVal("b"),
}),
"key2": cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}).Mark("boop"), // mark on the map element
"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")}),
}).WithMarks(cty.NewValueMarks("beep", "boop")),
false,
},
{ // Marks on the input value will be applied to the return value
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"),
}),
}).Mark("beep"), // mark on the entire input value
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"),
}),
}).Mark("beep"),
false,
},
{ // Marks on the entire input value AND inner elements (deep or shallow) ALL apply to the return
cty.MapVal(map[string]cty.Value{
"key1": cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}).Mark("beep"), // mark on the map element
"key2": cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
"key3": cty.ListVal([]cty.Value{
cty.StringVal("c").Mark("boop"), // mark on the inner list element
}),
}).Mark("bloop"), // mark on the entire input value
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"),
}),
}).WithMarks(cty.NewValueMarks("beep", "boop", "bloop")),
false,
},
}
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)
}
})
}
}