opentofu/internal/terraform/transform_root_test.go
Martin Atkins 4bc1696fd1 core: Simplify our idea of "root node" and require it for DynamicExpand
The graph walking mechanism is specified as requiring a graph with a single
root, which in practice means there's exactly one node in the graph
which doesn't have any dependencies.

However, we previously weren't verifying that invariant is true for
subgraphs returned from DynamicExpand. It was working anyway, but it's not
ideal to be relying on a behavior that isn't guaranteed by our underlying
infrastructure.

We also previously had the RootTransformer being a bit clever and trying
to avoid adding a new node if there is already only a single graph with
no dependencies. That special case isn't particularly valuable since
there's no harm in turning a one-node graph into a two-node graph with
an explicit separate root node, and doing that allows us to assume that
the root node is always present and is always exactly terraform.rootNode.

Many existing DynamicExpand implementations were not producing valid
graphs and were previously getting away with it. All of them now produce
properly-rooted graphs that should pass validation, and we will guarantee
that with an explicit check of the DynamicExpand return value before we
try to walk that subgraph. For good measure we also verify that the root
node is exactly terraform.rootNode, even though that isn't strictly
required by our graph walker, just to help us catch potential future bugs
where a DynamicExpand implementation neglects to add our singleton root
node.
2022-10-13 14:01:08 -07:00

96 lines
2.0 KiB
Go

package terraform
import (
"strings"
"testing"
"github.com/hashicorp/terraform/internal/addrs"
)
func TestRootTransformer(t *testing.T) {
t.Run("many nodes", func(t *testing.T) {
mod := testModule(t, "transform-root-basic")
g := Graph{Path: addrs.RootModuleInstance}
{
tf := &ConfigTransformer{Config: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &MissingProviderTransformer{}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &ProviderTransformer{}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &RootTransformer{}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformRootBasicStr)
if actual != expected {
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
}
root, err := g.Root()
if err != nil {
t.Fatalf("err: %s", err)
}
if _, ok := root.(graphNodeRoot); !ok {
t.Fatalf("bad: %#v", root)
}
})
t.Run("only one initial node", func(t *testing.T) {
g := Graph{Path: addrs.RootModuleInstance}
g.Add("foo")
addRootNodeToGraph(&g)
got := strings.TrimSpace(g.String())
want := strings.TrimSpace(`
foo
root
foo
`)
if got != want {
t.Errorf("wrong final graph\ngot:\n%s\nwant:\n%s", got, want)
}
})
t.Run("graph initially empty", func(t *testing.T) {
g := Graph{Path: addrs.RootModuleInstance}
addRootNodeToGraph(&g)
got := strings.TrimSpace(g.String())
want := `root`
if got != want {
t.Errorf("wrong final graph\ngot:\n%s\nwant:\n%s", got, want)
}
})
}
const testTransformRootBasicStr = `
aws_instance.foo
provider["registry.terraform.io/hashicorp/aws"]
do_droplet.bar
provider["registry.terraform.io/hashicorp/do"]
provider["registry.terraform.io/hashicorp/aws"]
provider["registry.terraform.io/hashicorp/do"]
root
aws_instance.foo
do_droplet.bar
`