blob: bc83bda0ded6c80ed752c77f0f8b059a98faf777 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package jsonplan
import (
"encoding/json"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/plans"
)
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 TestOutputs(t *testing.T) {
root := addrs.RootModuleInstance
child, diags := addrs.ParseModuleInstanceStr("module.child")
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
tests := map[string]struct {
changes *plans.Changes
expected map[string]Change
}{
"copies all outputs": {
changes: &plans.Changes{
Outputs: []*plans.OutputChangeSrc{
{
Addr: root.OutputValue("first"),
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
{
Addr: root.OutputValue("second"),
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
},
},
expected: map[string]Change{
"first": {
Actions: []string{"create"},
Before: json.RawMessage("null"),
After: json.RawMessage("null"),
AfterUnknown: json.RawMessage("false"),
BeforeSensitive: json.RawMessage("false"),
AfterSensitive: json.RawMessage("false"),
},
"second": {
Actions: []string{"create"},
Before: json.RawMessage("null"),
After: json.RawMessage("null"),
AfterUnknown: json.RawMessage("false"),
BeforeSensitive: json.RawMessage("false"),
AfterSensitive: json.RawMessage("false"),
},
},
},
"skips non root modules": {
changes: &plans.Changes{
Outputs: []*plans.OutputChangeSrc{
{
Addr: root.OutputValue("first"),
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
{
Addr: child.OutputValue("second"),
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
},
},
expected: map[string]Change{
"first": {
Actions: []string{"create"},
Before: json.RawMessage("null"),
After: json.RawMessage("null"),
AfterUnknown: json.RawMessage("false"),
BeforeSensitive: json.RawMessage("false"),
AfterSensitive: json.RawMessage("false"),
},
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
changes, err := MarshalOutputChanges(test.changes)
if err != nil {
t.Fatalf("unexpected err: %s", err)
}
if !cmp.Equal(changes, test.expected) {
t.Errorf("wrong result:\n %v\n", cmp.Diff(changes, test.expected))
}
})
}
}
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)
}
}