opentofu/internal/terraform/transform_reference_test.go
Martin Atkins 36d0a50427 Move terraform/ to internal/terraform/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

320 lines
7.5 KiB
Go

package terraform
import (
"reflect"
"sort"
"strings"
"testing"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/dag"
)
func TestReferenceTransformer_simple(t *testing.T) {
g := Graph{Path: addrs.RootModuleInstance}
g.Add(&graphNodeRefParentTest{
NameValue: "A",
Names: []string{"A"},
})
g.Add(&graphNodeRefChildTest{
NameValue: "B",
Refs: []string{"A"},
})
tf := &ReferenceTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformRefBasicStr)
if actual != expected {
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
}
}
func TestReferenceTransformer_self(t *testing.T) {
g := Graph{Path: addrs.RootModuleInstance}
g.Add(&graphNodeRefParentTest{
NameValue: "A",
Names: []string{"A"},
})
g.Add(&graphNodeRefChildTest{
NameValue: "B",
Refs: []string{"A", "B"},
})
tf := &ReferenceTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformRefBasicStr)
if actual != expected {
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
}
}
func TestReferenceTransformer_path(t *testing.T) {
g := Graph{Path: addrs.RootModuleInstance}
g.Add(&graphNodeRefParentTest{
NameValue: "A",
Names: []string{"A"},
})
g.Add(&graphNodeRefChildTest{
NameValue: "B",
Refs: []string{"A"},
})
g.Add(&graphNodeRefParentTest{
NameValue: "child.A",
PathValue: addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "child"}},
Names: []string{"A"},
})
g.Add(&graphNodeRefChildTest{
NameValue: "child.B",
PathValue: addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "child"}},
Refs: []string{"A"},
})
tf := &ReferenceTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformRefPathStr)
if actual != expected {
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
}
}
func TestReferenceTransformer_resourceInstances(t *testing.T) {
// Our reference analyses are all done based on unexpanded addresses
// so that we can use this transformer both in the plan graph (where things
// are not expanded yet) and the apply graph (where resource instances are
// pre-expanded but nothing else is.)
// However, that would make the result too conservative about instances
// of the same resource in different instances of the same module, so we
// make an exception for that situation in particular, keeping references
// between resource instances segregated by their containing module
// instance.
g := Graph{Path: addrs.RootModuleInstance}
moduleInsts := []addrs.ModuleInstance{
{
{
Name: "foo", InstanceKey: addrs.IntKey(0),
},
},
{
{
Name: "foo", InstanceKey: addrs.IntKey(1),
},
},
}
resourceAs := make([]addrs.AbsResourceInstance, len(moduleInsts))
for i, moduleInst := range moduleInsts {
resourceAs[i] = addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "thing",
Name: "a",
}.Instance(addrs.NoKey).Absolute(moduleInst)
}
resourceBs := make([]addrs.AbsResourceInstance, len(moduleInsts))
for i, moduleInst := range moduleInsts {
resourceBs[i] = addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "thing",
Name: "b",
}.Instance(addrs.NoKey).Absolute(moduleInst)
}
g.Add(&graphNodeFakeResourceInstance{
Addr: resourceAs[0],
})
g.Add(&graphNodeFakeResourceInstance{
Addr: resourceBs[0],
Refs: []*addrs.Reference{
{
Subject: resourceAs[0].Resource,
},
},
})
g.Add(&graphNodeFakeResourceInstance{
Addr: resourceAs[1],
})
g.Add(&graphNodeFakeResourceInstance{
Addr: resourceBs[1],
Refs: []*addrs.Reference{
{
Subject: resourceAs[1].Resource,
},
},
})
tf := &ReferenceTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("unexpected error: %s", err)
}
// Resource B should be connected to resource A in each module instance,
// but there should be no connections between the two module instances.
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(`
module.foo[0].thing.a
module.foo[0].thing.b
module.foo[0].thing.a
module.foo[1].thing.a
module.foo[1].thing.b
module.foo[1].thing.a
`)
if actual != expected {
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
}
}
func TestReferenceMapReferences(t *testing.T) {
cases := map[string]struct {
Nodes []dag.Vertex
Check dag.Vertex
Result []string
}{
"simple": {
Nodes: []dag.Vertex{
&graphNodeRefParentTest{
NameValue: "A",
Names: []string{"A"},
},
},
Check: &graphNodeRefChildTest{
NameValue: "foo",
Refs: []string{"A"},
},
Result: []string{"A"},
},
}
for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
rm := NewReferenceMap(tc.Nodes)
result := rm.References(tc.Check)
var resultStr []string
for _, v := range result {
resultStr = append(resultStr, dag.VertexName(v))
}
sort.Strings(resultStr)
sort.Strings(tc.Result)
if !reflect.DeepEqual(resultStr, tc.Result) {
t.Fatalf("bad: %#v", resultStr)
}
})
}
}
type graphNodeRefParentTest struct {
NameValue string
PathValue addrs.ModuleInstance
Names []string
}
var _ GraphNodeReferenceable = (*graphNodeRefParentTest)(nil)
func (n *graphNodeRefParentTest) Name() string {
return n.NameValue
}
func (n *graphNodeRefParentTest) ReferenceableAddrs() []addrs.Referenceable {
ret := make([]addrs.Referenceable, len(n.Names))
for i, name := range n.Names {
ret[i] = addrs.LocalValue{Name: name}
}
return ret
}
func (n *graphNodeRefParentTest) Path() addrs.ModuleInstance {
return n.PathValue
}
func (n *graphNodeRefParentTest) ModulePath() addrs.Module {
return n.PathValue.Module()
}
type graphNodeRefChildTest struct {
NameValue string
PathValue addrs.ModuleInstance
Refs []string
}
var _ GraphNodeReferencer = (*graphNodeRefChildTest)(nil)
func (n *graphNodeRefChildTest) Name() string {
return n.NameValue
}
func (n *graphNodeRefChildTest) References() []*addrs.Reference {
ret := make([]*addrs.Reference, len(n.Refs))
for i, name := range n.Refs {
ret[i] = &addrs.Reference{
Subject: addrs.LocalValue{Name: name},
}
}
return ret
}
func (n *graphNodeRefChildTest) Path() addrs.ModuleInstance {
return n.PathValue
}
func (n *graphNodeRefChildTest) ModulePath() addrs.Module {
return n.PathValue.Module()
}
type graphNodeFakeResourceInstance struct {
Addr addrs.AbsResourceInstance
Refs []*addrs.Reference
}
var _ GraphNodeResourceInstance = (*graphNodeFakeResourceInstance)(nil)
var _ GraphNodeReferenceable = (*graphNodeFakeResourceInstance)(nil)
var _ GraphNodeReferencer = (*graphNodeFakeResourceInstance)(nil)
func (n *graphNodeFakeResourceInstance) ResourceInstanceAddr() addrs.AbsResourceInstance {
return n.Addr
}
func (n *graphNodeFakeResourceInstance) ModulePath() addrs.Module {
return n.Addr.Module.Module()
}
func (n *graphNodeFakeResourceInstance) ReferenceableAddrs() []addrs.Referenceable {
return []addrs.Referenceable{n.Addr.Resource}
}
func (n *graphNodeFakeResourceInstance) References() []*addrs.Reference {
return n.Refs
}
func (n *graphNodeFakeResourceInstance) StateDependencies() []addrs.ConfigResource {
return nil
}
func (n *graphNodeFakeResourceInstance) String() string {
return n.Addr.String()
}
const testTransformRefBasicStr = `
A
B
A
`
const testTransformRefPathStr = `
A
B
A
child.A
child.B
child.A
`