mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-26 00:16:25 -06:00
9497b2cd6f
The JSON output for sequences previously omitted unknown values for tuples and sets, which made it impossible to interpret the corresponding unknown marks. For example, consider this resource: resource "example_resource" "example" { tags = toset(["alpha", timestamp(), "charlie"]) } This would previously be encoded in JSON as: "after": { "tags": ["alpha", "charlie"] }, "after_unknown": { "id": true, "tags": [false, true, false] }, That is, the timestamp value would be omitted from the output altogether, while the corresponding unknown marks would include a value for each of the set members. This commit changes the behaviour to: "after": { "tags": ["alpha", null, "charlie"] }, "after_unknown": { "id": true, "tags": [false, true, false] }, This aligns tuples and sets with the prior behaviour for lists, and makes it clear which elements are known and which are unknown.
376 lines
7.7 KiB
Go
376 lines
7.7 KiB
Go
package jsonplan
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func TestOmitUnknowns(t *testing.T) {
|
|
tests := []struct {
|
|
Input cty.Value
|
|
Want cty.Value
|
|
}{
|
|
{
|
|
cty.StringVal("hello"),
|
|
cty.StringVal("hello"),
|
|
},
|
|
{
|
|
cty.NullVal(cty.String),
|
|
cty.NullVal(cty.String),
|
|
},
|
|
{
|
|
cty.UnknownVal(cty.String),
|
|
cty.NilVal,
|
|
},
|
|
{
|
|
cty.ListValEmpty(cty.String),
|
|
cty.EmptyTupleVal,
|
|
},
|
|
{
|
|
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
|
|
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
|
|
},
|
|
{
|
|
cty.ListVal([]cty.Value{cty.NullVal(cty.String)}),
|
|
cty.TupleVal([]cty.Value{cty.NullVal(cty.String)}),
|
|
},
|
|
{
|
|
cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
|
|
cty.TupleVal([]cty.Value{cty.NullVal(cty.String)}),
|
|
},
|
|
{
|
|
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
|
|
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
|
|
},
|
|
//
|
|
{
|
|
cty.ListVal([]cty.Value{
|
|
cty.StringVal("hello"),
|
|
cty.UnknownVal(cty.String)}),
|
|
cty.TupleVal([]cty.Value{
|
|
cty.StringVal("hello"),
|
|
cty.NullVal(cty.String),
|
|
}),
|
|
},
|
|
{
|
|
cty.MapVal(map[string]cty.Value{
|
|
"hello": cty.True,
|
|
"world": cty.UnknownVal(cty.Bool),
|
|
}),
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"hello": cty.True,
|
|
}),
|
|
},
|
|
{
|
|
cty.TupleVal([]cty.Value{
|
|
cty.StringVal("alpha"),
|
|
cty.UnknownVal(cty.String),
|
|
cty.StringVal("charlie"),
|
|
}),
|
|
cty.TupleVal([]cty.Value{
|
|
cty.StringVal("alpha"),
|
|
cty.NullVal(cty.String),
|
|
cty.StringVal("charlie"),
|
|
}),
|
|
},
|
|
{
|
|
cty.SetVal([]cty.Value{
|
|
cty.StringVal("dev"),
|
|
cty.StringVal("foo"),
|
|
cty.StringVal("stg"),
|
|
cty.UnknownVal(cty.String),
|
|
}),
|
|
cty.TupleVal([]cty.Value{
|
|
cty.StringVal("dev"),
|
|
cty.StringVal("foo"),
|
|
cty.StringVal("stg"),
|
|
cty.NullVal(cty.String),
|
|
}),
|
|
},
|
|
{
|
|
cty.SetVal([]cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.UnknownVal(cty.String),
|
|
}),
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.StringVal("known"),
|
|
}),
|
|
}),
|
|
cty.TupleVal([]cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.StringVal("known"),
|
|
}),
|
|
cty.EmptyObjectVal,
|
|
}),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
got := omitUnknowns(test.Input)
|
|
if !reflect.DeepEqual(got, test.Want) {
|
|
t.Errorf(
|
|
"wrong result\ninput: %#v\ngot: %#v\nwant: %#v",
|
|
test.Input, got, test.Want,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUnknownAsBool(t *testing.T) {
|
|
tests := []struct {
|
|
Input cty.Value
|
|
Want cty.Value
|
|
}{
|
|
{
|
|
cty.StringVal("hello"),
|
|
cty.False,
|
|
},
|
|
{
|
|
cty.NullVal(cty.String),
|
|
cty.False,
|
|
},
|
|
{
|
|
cty.UnknownVal(cty.String),
|
|
cty.True,
|
|
},
|
|
|
|
{
|
|
cty.NullVal(cty.DynamicPseudoType),
|
|
cty.False,
|
|
},
|
|
{
|
|
cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})),
|
|
cty.False,
|
|
},
|
|
{
|
|
cty.DynamicVal,
|
|
cty.True,
|
|
},
|
|
|
|
{
|
|
cty.ListValEmpty(cty.String),
|
|
cty.EmptyTupleVal,
|
|
},
|
|
{
|
|
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
|
|
cty.TupleVal([]cty.Value{cty.False}),
|
|
},
|
|
{
|
|
cty.ListVal([]cty.Value{cty.NullVal(cty.String)}),
|
|
cty.TupleVal([]cty.Value{cty.False}),
|
|
},
|
|
{
|
|
cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
|
|
cty.TupleVal([]cty.Value{cty.True}),
|
|
},
|
|
{
|
|
cty.SetValEmpty(cty.String),
|
|
cty.EmptyTupleVal,
|
|
},
|
|
{
|
|
cty.SetVal([]cty.Value{cty.StringVal("hello")}),
|
|
cty.TupleVal([]cty.Value{cty.False}),
|
|
},
|
|
{
|
|
cty.SetVal([]cty.Value{cty.NullVal(cty.String)}),
|
|
cty.TupleVal([]cty.Value{cty.False}),
|
|
},
|
|
{
|
|
cty.SetVal([]cty.Value{cty.UnknownVal(cty.String)}),
|
|
cty.TupleVal([]cty.Value{cty.True}),
|
|
},
|
|
{
|
|
cty.EmptyTupleVal,
|
|
cty.EmptyTupleVal,
|
|
},
|
|
{
|
|
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
|
|
cty.TupleVal([]cty.Value{cty.False}),
|
|
},
|
|
{
|
|
cty.TupleVal([]cty.Value{cty.NullVal(cty.String)}),
|
|
cty.TupleVal([]cty.Value{cty.False}),
|
|
},
|
|
{
|
|
cty.TupleVal([]cty.Value{cty.UnknownVal(cty.String)}),
|
|
cty.TupleVal([]cty.Value{cty.True}),
|
|
},
|
|
{
|
|
cty.MapValEmpty(cty.String),
|
|
cty.EmptyObjectVal,
|
|
},
|
|
{
|
|
cty.MapVal(map[string]cty.Value{"greeting": cty.StringVal("hello")}),
|
|
cty.EmptyObjectVal,
|
|
},
|
|
{
|
|
cty.MapVal(map[string]cty.Value{"greeting": cty.NullVal(cty.String)}),
|
|
cty.EmptyObjectVal,
|
|
},
|
|
{
|
|
cty.MapVal(map[string]cty.Value{"greeting": cty.UnknownVal(cty.String)}),
|
|
cty.ObjectVal(map[string]cty.Value{"greeting": cty.True}),
|
|
},
|
|
{
|
|
cty.EmptyObjectVal,
|
|
cty.EmptyObjectVal,
|
|
},
|
|
{
|
|
cty.ObjectVal(map[string]cty.Value{"greeting": cty.StringVal("hello")}),
|
|
cty.EmptyObjectVal,
|
|
},
|
|
{
|
|
cty.ObjectVal(map[string]cty.Value{"greeting": cty.NullVal(cty.String)}),
|
|
cty.EmptyObjectVal,
|
|
},
|
|
{
|
|
cty.ObjectVal(map[string]cty.Value{"greeting": cty.UnknownVal(cty.String)}),
|
|
cty.ObjectVal(map[string]cty.Value{"greeting": cty.True}),
|
|
},
|
|
{
|
|
cty.SetVal([]cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.UnknownVal(cty.String),
|
|
}),
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.StringVal("known"),
|
|
}),
|
|
}),
|
|
cty.TupleVal([]cty.Value{
|
|
cty.EmptyObjectVal,
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.True,
|
|
}),
|
|
}),
|
|
},
|
|
{
|
|
cty.SetVal([]cty.Value{
|
|
cty.MapValEmpty(cty.String),
|
|
cty.MapVal(map[string]cty.Value{
|
|
"a": cty.StringVal("known"),
|
|
}),
|
|
cty.MapVal(map[string]cty.Value{
|
|
"a": cty.UnknownVal(cty.String),
|
|
}),
|
|
}),
|
|
cty.TupleVal([]cty.Value{
|
|
cty.EmptyObjectVal,
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.True,
|
|
}),
|
|
cty.EmptyObjectVal,
|
|
}),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
got := unknownAsBool(test.Input)
|
|
if !reflect.DeepEqual(got, test.Want) {
|
|
t.Errorf(
|
|
"wrong result\ninput: %#v\ngot: %#v\nwant: %#v",
|
|
test.Input, got, test.Want,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEncodePaths(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Input cty.PathSet
|
|
Want json.RawMessage
|
|
}{
|
|
"empty set": {
|
|
cty.NewPathSet(),
|
|
json.RawMessage(nil),
|
|
},
|
|
"index path with string and int steps": {
|
|
cty.NewPathSet(cty.IndexStringPath("boop").IndexInt(0)),
|
|
json.RawMessage(`[["boop",0]]`),
|
|
},
|
|
"get attr path with one step": {
|
|
cty.NewPathSet(cty.GetAttrPath("triggers")),
|
|
json.RawMessage(`[["triggers"]]`),
|
|
},
|
|
"multiple paths of different types": {
|
|
cty.NewPathSet(
|
|
cty.GetAttrPath("alpha").GetAttr("beta").GetAttr("gamma"),
|
|
cty.GetAttrPath("triggers").IndexString("name"),
|
|
cty.IndexIntPath(0).IndexInt(1).IndexInt(2).IndexInt(3),
|
|
),
|
|
json.RawMessage(`[["alpha","beta","gamma"],["triggers","name"],[0,1,2,3]]`),
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
got, err := encodePaths(test.Input)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if !cmp.Equal(got, test.Want) {
|
|
t.Errorf("wrong result:\n %v\n", cmp.Diff(got, test.Want))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func deepObjectValue(depth int) cty.Value {
|
|
v := cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.StringVal("a"),
|
|
"b": cty.NumberIntVal(2),
|
|
"c": cty.True,
|
|
"d": cty.UnknownVal(cty.String),
|
|
})
|
|
|
|
result := v
|
|
|
|
for i := 0; i < depth; i++ {
|
|
result = cty.ObjectVal(map[string]cty.Value{
|
|
"a": result,
|
|
"b": result,
|
|
"c": result,
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func BenchmarkUnknownAsBool_2(b *testing.B) {
|
|
value := deepObjectValue(2)
|
|
for n := 0; n < b.N; n++ {
|
|
unknownAsBool(value)
|
|
}
|
|
}
|
|
|
|
func BenchmarkUnknownAsBool_3(b *testing.B) {
|
|
value := deepObjectValue(3)
|
|
for n := 0; n < b.N; n++ {
|
|
unknownAsBool(value)
|
|
}
|
|
}
|
|
|
|
func BenchmarkUnknownAsBool_5(b *testing.B) {
|
|
value := deepObjectValue(5)
|
|
for n := 0; n < b.N; n++ {
|
|
unknownAsBool(value)
|
|
}
|
|
}
|
|
|
|
func BenchmarkUnknownAsBool_7(b *testing.B) {
|
|
value := deepObjectValue(7)
|
|
for n := 0; n < b.N; n++ {
|
|
unknownAsBool(value)
|
|
}
|
|
}
|
|
|
|
func BenchmarkUnknownAsBool_9(b *testing.B) {
|
|
value := deepObjectValue(9)
|
|
for n := 0; n < b.N; n++ {
|
|
unknownAsBool(value)
|
|
}
|
|
}
|