mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-11 08:32:19 -06:00
75ef61c783
Changing only the index on a nested module will cause all nested moves to create cycles, since their full addresses will match both the From and To addresses. When building the dependency graph, check if the parent is only changing the index of the containing module, and prevent the backwards edge for the move.
1746 lines
41 KiB
Go
1746 lines
41 KiB
Go
package addrs
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
func TestModuleInstanceMoveDestination(t *testing.T) {
|
|
tests := []struct {
|
|
DeclModule string
|
|
StmtFrom, StmtTo string
|
|
Receiver string
|
|
WantMatch bool
|
|
WantResult string
|
|
}{
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar`,
|
|
`module.foo`,
|
|
true,
|
|
`module.bar`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar`,
|
|
`module.foo[1]`,
|
|
true,
|
|
`module.bar[1]`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar`,
|
|
`module.foo["a"]`,
|
|
true,
|
|
`module.bar["a"]`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar.module.foo`,
|
|
`module.foo`,
|
|
true,
|
|
`module.bar.module.foo`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo.module.bar`,
|
|
`module.bar`,
|
|
`module.foo.module.bar`,
|
|
true,
|
|
`module.bar`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo[2]`,
|
|
`module.foo[1]`,
|
|
true,
|
|
`module.foo[2]`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo`,
|
|
`module.foo[1]`,
|
|
true,
|
|
`module.foo`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.foo[1]`,
|
|
`module.foo`,
|
|
true,
|
|
`module.foo[1]`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.foo[1]`,
|
|
`module.foo.module.bar`,
|
|
true,
|
|
`module.foo[1].module.bar`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.foo[1]`,
|
|
`module.foo.module.bar[0]`,
|
|
true,
|
|
`module.foo[1].module.bar[0]`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar.module.foo`,
|
|
`module.foo[0]`,
|
|
true,
|
|
`module.bar.module.foo[0]`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo.module.bar`,
|
|
`module.bar`,
|
|
`module.foo.module.bar[0]`,
|
|
true,
|
|
`module.bar[0]`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.baz`,
|
|
`module.foo.module.bar`,
|
|
true,
|
|
`module.foo.module.baz`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.baz`,
|
|
`module.foo[1].module.bar`,
|
|
true,
|
|
`module.foo[1].module.baz`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.bar[1]`,
|
|
`module.foo[1].module.bar`,
|
|
true,
|
|
`module.foo[1].module.bar[1]`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo[2]`,
|
|
`module.foo`,
|
|
false, // the receiver has a non-matching instance key (NoKey)
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo[2]`,
|
|
`module.foo[2]`,
|
|
false, // the receiver is already the "to" address
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar`,
|
|
``,
|
|
false, // the root module can never be moved
|
|
``,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.bar[1]`,
|
|
`module.boz`,
|
|
false, // the receiver is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
`foo.bar`,
|
|
`module.bar`,
|
|
`module.bar[1]`,
|
|
`module.boz`,
|
|
false, // the receiver is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
`foo.bar`,
|
|
`module.a`,
|
|
`module.b`,
|
|
`module.boz`,
|
|
false, // the receiver is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2`,
|
|
`module.b1.module.b2`,
|
|
`module.c`,
|
|
false, // the receiver is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2[0]`,
|
|
`module.b1.module.b2[1]`,
|
|
`module.c`,
|
|
false, // the receiver is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2`,
|
|
`module.b1.module.b2`,
|
|
`module.a1.module.b2`,
|
|
false, // the receiver is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2`,
|
|
`module.b1.module.b2`,
|
|
`module.b1.module.a2`,
|
|
false, // the receiver is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2[0]`,
|
|
`module.b1.module.b2[1]`,
|
|
`module.a1.module.b2[0]`,
|
|
false, // the receiver is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`foo_instance.bar`,
|
|
`foo_instance.baz`,
|
|
`module.foo`,
|
|
false, // a resource address can never match a module instance
|
|
``,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(
|
|
fmt.Sprintf(
|
|
"%s: %s to %s with %s",
|
|
test.DeclModule,
|
|
test.StmtFrom, test.StmtTo,
|
|
test.Receiver,
|
|
),
|
|
func(t *testing.T) {
|
|
|
|
parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
|
|
t.Helper()
|
|
|
|
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
|
|
if hclDiags.HasErrors() {
|
|
// We're not trying to test the HCL parser here, so any
|
|
// failures at this point are likely to be bugs in the
|
|
// test case itself.
|
|
t.Fatalf("syntax error: %s", hclDiags.Error())
|
|
}
|
|
|
|
moveEp, diags := ParseMoveEndpoint(traversal)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected error: %s", diags.Err().Error())
|
|
}
|
|
return moveEp
|
|
}
|
|
|
|
fromEPLocal := parseStmtEP(t, test.StmtFrom)
|
|
toEPLocal := parseStmtEP(t, test.StmtTo)
|
|
|
|
declModule := RootModule
|
|
if test.DeclModule != "" {
|
|
declModule = strings.Split(test.DeclModule, ".")
|
|
}
|
|
fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
|
|
if fromEP == nil || toEP == nil {
|
|
t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal)
|
|
}
|
|
|
|
receiverAddr := RootModuleInstance
|
|
if test.Receiver != "" {
|
|
var diags tfdiags.Diagnostics
|
|
receiverAddr, diags = ParseModuleInstanceStr(test.Receiver)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("invalid reciever address: %s", diags.Err().Error())
|
|
}
|
|
}
|
|
gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
|
|
if !test.WantMatch {
|
|
if gotMatch {
|
|
t.Errorf("unexpected match\nreceiver: %s\nfrom: %s\nto: %s\nresult: %s", test.Receiver, fromEP, toEP, gotAddr)
|
|
}
|
|
return
|
|
}
|
|
|
|
if !gotMatch {
|
|
t.Errorf("unexpected non-match\nreceiver: %s\nfrom: %s\nto: %s", test.Receiver, fromEP, toEP)
|
|
}
|
|
|
|
if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestAbsResourceInstanceMoveDestination(t *testing.T) {
|
|
tests := []struct {
|
|
DeclModule string
|
|
StmtFrom, StmtTo string
|
|
Receiver string
|
|
WantMatch bool
|
|
WantResult string
|
|
}{
|
|
{
|
|
``,
|
|
`test_object.beep`,
|
|
`test_object.boop`,
|
|
`test_object.beep`,
|
|
true,
|
|
`test_object.boop`,
|
|
},
|
|
{
|
|
``,
|
|
`test_object.beep`,
|
|
`test_object.beep[2]`,
|
|
`test_object.beep`,
|
|
true,
|
|
`test_object.beep[2]`,
|
|
},
|
|
{
|
|
``,
|
|
`test_object.beep`,
|
|
`module.foo.test_object.beep`,
|
|
`test_object.beep`,
|
|
true,
|
|
`module.foo.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`test_object.beep[2]`,
|
|
`module.foo.test_object.beep["a"]`,
|
|
`test_object.beep[2]`,
|
|
true,
|
|
`module.foo.test_object.beep["a"]`,
|
|
},
|
|
{
|
|
``,
|
|
`test_object.beep`,
|
|
`module.foo[0].test_object.beep`,
|
|
`test_object.beep`,
|
|
true,
|
|
`module.foo[0].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo.test_object.beep`,
|
|
`test_object.beep`,
|
|
`module.foo.test_object.beep`,
|
|
true,
|
|
`test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[0].test_object.beep`,
|
|
`test_object.beep`,
|
|
`module.foo[0].test_object.beep`,
|
|
true,
|
|
`test_object.beep`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`test_object.beep`,
|
|
`test_object.boop`,
|
|
`module.foo[0].test_object.beep`,
|
|
true,
|
|
`module.foo[0].test_object.boop`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`test_object.beep`,
|
|
`test_object.beep[1]`,
|
|
`module.foo[0].test_object.beep`,
|
|
true,
|
|
`module.foo[0].test_object.beep[1]`,
|
|
},
|
|
{
|
|
``,
|
|
`test_object.beep`,
|
|
`test_object.boop`,
|
|
`test_object.boop`,
|
|
false, // the reciever is already the "to" address
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`test_object.beep[1]`,
|
|
`test_object.beep[2]`,
|
|
`test_object.beep[5]`,
|
|
false, // the receiver has a non-matching instance key
|
|
``,
|
|
},
|
|
{
|
|
`foo`,
|
|
`test_object.beep`,
|
|
`test_object.boop`,
|
|
`test_object.beep`,
|
|
false, // the receiver is not inside an instance of module "foo"
|
|
``,
|
|
},
|
|
{
|
|
`foo.bar`,
|
|
`test_object.beep`,
|
|
`test_object.boop`,
|
|
`test_object.beep`,
|
|
false, // the receiver is not inside an instance of module "foo.bar"
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[0].test_object.beep`,
|
|
`test_object.beep`,
|
|
`module.foo[1].test_object.beep`,
|
|
false, // receiver is in a different instance of module.foo
|
|
``,
|
|
},
|
|
|
|
// Moving a module also moves all of the resources declared within it.
|
|
// The following tests all cover variations of that rule.
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar`,
|
|
`module.foo.test_object.beep`,
|
|
true,
|
|
`module.bar.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar`,
|
|
`module.foo[1].test_object.beep`,
|
|
true,
|
|
`module.bar[1].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar`,
|
|
`module.foo["a"].test_object.beep`,
|
|
true,
|
|
`module.bar["a"].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar.module.foo`,
|
|
`module.foo.test_object.beep`,
|
|
true,
|
|
`module.bar.module.foo.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo.module.bar`,
|
|
`module.bar`,
|
|
`module.foo.module.bar.test_object.beep`,
|
|
true,
|
|
`module.bar.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo[2]`,
|
|
`module.foo[1].test_object.beep`,
|
|
true,
|
|
`module.foo[2].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo`,
|
|
`module.foo[1].test_object.beep`,
|
|
true,
|
|
`module.foo.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.foo[1]`,
|
|
`module.foo.test_object.beep`,
|
|
true,
|
|
`module.foo[1].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.foo[1]`,
|
|
`module.foo.module.bar.test_object.beep`,
|
|
true,
|
|
`module.foo[1].module.bar.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.foo[1]`,
|
|
`module.foo.module.bar[0].test_object.beep`,
|
|
true,
|
|
`module.foo[1].module.bar[0].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar.module.foo`,
|
|
`module.foo[0].test_object.beep`,
|
|
true,
|
|
`module.bar.module.foo[0].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo.module.bar`,
|
|
`module.bar`,
|
|
`module.foo.module.bar[0].test_object.beep`,
|
|
true,
|
|
`module.bar[0].test_object.beep`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.baz`,
|
|
`module.foo.module.bar.test_object.beep`,
|
|
true,
|
|
`module.foo.module.baz.test_object.beep`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.baz`,
|
|
`module.foo[1].module.bar.test_object.beep`,
|
|
true,
|
|
`module.foo[1].module.baz.test_object.beep`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.bar[1]`,
|
|
`module.foo[1].module.bar.test_object.beep`,
|
|
true,
|
|
`module.foo[1].module.bar[1].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo[2]`,
|
|
`module.foo.test_object.beep`,
|
|
false, // the receiver module has a non-matching instance key (NoKey)
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo[2]`,
|
|
`module.foo[2].test_object.beep`,
|
|
false, // the receiver is already at the "to" address
|
|
``,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.bar[1]`,
|
|
`module.boz.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
`foo.bar`,
|
|
`module.bar`,
|
|
`module.bar[1]`,
|
|
`module.boz.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
`foo.bar`,
|
|
`module.a`,
|
|
`module.b`,
|
|
`module.boz.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2`,
|
|
`module.b1.module.b2`,
|
|
`module.c.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2[0]`,
|
|
`module.b1.module.b2[1]`,
|
|
`module.c.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2`,
|
|
`module.b1.module.b2`,
|
|
`module.a1.module.b2.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2`,
|
|
`module.b1.module.b2`,
|
|
`module.b1.module.a2.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2[0]`,
|
|
`module.b1.module.b2[1]`,
|
|
`module.a1.module.b2[0].test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`foo_instance.bar`,
|
|
`foo_instance.baz`,
|
|
`module.foo.test_object.beep`,
|
|
false, // the resource address is unrelated to the move statements
|
|
``,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(
|
|
fmt.Sprintf(
|
|
"%s: %s to %s with %s",
|
|
test.DeclModule,
|
|
test.StmtFrom, test.StmtTo,
|
|
test.Receiver,
|
|
),
|
|
func(t *testing.T) {
|
|
|
|
parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
|
|
t.Helper()
|
|
|
|
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
|
|
if hclDiags.HasErrors() {
|
|
// We're not trying to test the HCL parser here, so any
|
|
// failures at this point are likely to be bugs in the
|
|
// test case itself.
|
|
t.Fatalf("syntax error: %s", hclDiags.Error())
|
|
}
|
|
|
|
moveEp, diags := ParseMoveEndpoint(traversal)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected error: %s", diags.Err().Error())
|
|
}
|
|
return moveEp
|
|
}
|
|
|
|
fromEPLocal := parseStmtEP(t, test.StmtFrom)
|
|
toEPLocal := parseStmtEP(t, test.StmtTo)
|
|
|
|
declModule := RootModule
|
|
if test.DeclModule != "" {
|
|
declModule = strings.Split(test.DeclModule, ".")
|
|
}
|
|
fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
|
|
if fromEP == nil || toEP == nil {
|
|
t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal)
|
|
}
|
|
|
|
receiverAddr, diags := ParseAbsResourceInstanceStr(test.Receiver)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("invalid reciever address: %s", diags.Err().Error())
|
|
}
|
|
gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
|
|
if !test.WantMatch {
|
|
if gotMatch {
|
|
t.Errorf("unexpected match\nreceiver: %s\nfrom: %s\nto: %s\nresult: %s", test.Receiver, fromEP, toEP, gotAddr)
|
|
}
|
|
return
|
|
}
|
|
|
|
if !gotMatch {
|
|
t.Fatalf("unexpected non-match\nreceiver: %s (%T)\nfrom: %s\nto: %s\ngot: (no match)\nwant: %s", test.Receiver, receiverAddr, fromEP, toEP, test.WantResult)
|
|
}
|
|
|
|
if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestAbsResourceMoveDestination(t *testing.T) {
|
|
tests := []struct {
|
|
DeclModule string
|
|
StmtFrom, StmtTo string
|
|
Receiver string
|
|
WantMatch bool
|
|
WantResult string
|
|
}{
|
|
{
|
|
``,
|
|
`test_object.beep`,
|
|
`test_object.boop`,
|
|
`test_object.beep`,
|
|
true,
|
|
`test_object.boop`,
|
|
},
|
|
{
|
|
``,
|
|
`test_object.beep`,
|
|
`module.foo.test_object.beep`,
|
|
`test_object.beep`,
|
|
true,
|
|
`module.foo.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`test_object.beep`,
|
|
`module.foo[0].test_object.beep`,
|
|
`test_object.beep`,
|
|
true,
|
|
`module.foo[0].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo.test_object.beep`,
|
|
`test_object.beep`,
|
|
`module.foo.test_object.beep`,
|
|
true,
|
|
`test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[0].test_object.beep`,
|
|
`test_object.beep`,
|
|
`module.foo[0].test_object.beep`,
|
|
true,
|
|
`test_object.beep`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`test_object.beep`,
|
|
`test_object.boop`,
|
|
`module.foo[0].test_object.beep`,
|
|
true,
|
|
`module.foo[0].test_object.boop`,
|
|
},
|
|
{
|
|
``,
|
|
`test_object.beep`,
|
|
`test_object.boop`,
|
|
`test_object.boop`,
|
|
false, // the reciever is already the "to" address
|
|
``,
|
|
},
|
|
{
|
|
`foo`,
|
|
`test_object.beep`,
|
|
`test_object.boop`,
|
|
`test_object.beep`,
|
|
false, // the receiver is not inside an instance of module "foo"
|
|
``,
|
|
},
|
|
{
|
|
`foo.bar`,
|
|
`test_object.beep`,
|
|
`test_object.boop`,
|
|
`test_object.beep`,
|
|
false, // the receiver is not inside an instance of module "foo.bar"
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[0].test_object.beep`,
|
|
`test_object.beep`,
|
|
`module.foo[1].test_object.beep`,
|
|
false, // receiver is in a different instance of module.foo
|
|
``,
|
|
},
|
|
|
|
// Moving a module also moves all of the resources declared within it.
|
|
// The following tests all cover variations of that rule.
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar`,
|
|
`module.foo.test_object.beep`,
|
|
true,
|
|
`module.bar.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar`,
|
|
`module.foo[1].test_object.beep`,
|
|
true,
|
|
`module.bar[1].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar`,
|
|
`module.foo["a"].test_object.beep`,
|
|
true,
|
|
`module.bar["a"].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar.module.foo`,
|
|
`module.foo.test_object.beep`,
|
|
true,
|
|
`module.bar.module.foo.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo.module.bar`,
|
|
`module.bar`,
|
|
`module.foo.module.bar.test_object.beep`,
|
|
true,
|
|
`module.bar.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo[2]`,
|
|
`module.foo[1].test_object.beep`,
|
|
true,
|
|
`module.foo[2].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo`,
|
|
`module.foo[1].test_object.beep`,
|
|
true,
|
|
`module.foo.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.foo[1]`,
|
|
`module.foo.test_object.beep`,
|
|
true,
|
|
`module.foo[1].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.foo[1]`,
|
|
`module.foo.module.bar.test_object.beep`,
|
|
true,
|
|
`module.foo[1].module.bar.test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.foo[1]`,
|
|
`module.foo.module.bar[0].test_object.beep`,
|
|
true,
|
|
`module.foo[1].module.bar[0].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo`,
|
|
`module.bar.module.foo`,
|
|
`module.foo[0].test_object.beep`,
|
|
true,
|
|
`module.bar.module.foo[0].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo.module.bar`,
|
|
`module.bar`,
|
|
`module.foo.module.bar[0].test_object.beep`,
|
|
true,
|
|
`module.bar[0].test_object.beep`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.baz`,
|
|
`module.foo.module.bar.test_object.beep`,
|
|
true,
|
|
`module.foo.module.baz.test_object.beep`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.baz`,
|
|
`module.foo[1].module.bar.test_object.beep`,
|
|
true,
|
|
`module.foo[1].module.baz.test_object.beep`,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.bar[1]`,
|
|
`module.foo[1].module.bar.test_object.beep`,
|
|
true,
|
|
`module.foo[1].module.bar[1].test_object.beep`,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo[2]`,
|
|
`module.foo.test_object.beep`,
|
|
false, // the receiver module has a non-matching instance key (NoKey)
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.foo[1]`,
|
|
`module.foo[2]`,
|
|
`module.foo[2].test_object.beep`,
|
|
false, // the receiver is already at the "to" address
|
|
``,
|
|
},
|
|
{
|
|
`foo`,
|
|
`module.bar`,
|
|
`module.bar[1]`,
|
|
`module.boz.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
`foo.bar`,
|
|
`module.bar`,
|
|
`module.bar[1]`,
|
|
`module.boz.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
`foo.bar`,
|
|
`module.a`,
|
|
`module.b`,
|
|
`module.boz.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2`,
|
|
`module.b1.module.b2`,
|
|
`module.c.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2[0]`,
|
|
`module.b1.module.b2[1]`,
|
|
`module.c.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2`,
|
|
`module.b1.module.b2`,
|
|
`module.a1.module.b2.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2`,
|
|
`module.b1.module.b2`,
|
|
`module.b1.module.a2.test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`module.a1.module.a2[0]`,
|
|
`module.b1.module.b2[1]`,
|
|
`module.a1.module.b2[0].test_object.beep`,
|
|
false, // the receiver module is outside the declaration module
|
|
``,
|
|
},
|
|
{
|
|
``,
|
|
`foo_instance.bar`,
|
|
`foo_instance.baz`,
|
|
`module.foo.test_object.beep`,
|
|
false, // the resource address is unrelated to the move statements
|
|
``,
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(
|
|
fmt.Sprintf(
|
|
"[%02d] %s: %s to %s with %s",
|
|
i,
|
|
test.DeclModule,
|
|
test.StmtFrom, test.StmtTo,
|
|
test.Receiver,
|
|
),
|
|
func(t *testing.T) {
|
|
|
|
parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
|
|
t.Helper()
|
|
|
|
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
|
|
if hclDiags.HasErrors() {
|
|
// We're not trying to test the HCL parser here, so any
|
|
// failures at this point are likely to be bugs in the
|
|
// test case itself.
|
|
t.Fatalf("syntax error: %s", hclDiags.Error())
|
|
}
|
|
|
|
moveEp, diags := ParseMoveEndpoint(traversal)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected error: %s", diags.Err().Error())
|
|
}
|
|
return moveEp
|
|
}
|
|
|
|
fromEPLocal := parseStmtEP(t, test.StmtFrom)
|
|
toEPLocal := parseStmtEP(t, test.StmtTo)
|
|
|
|
declModule := RootModule
|
|
if test.DeclModule != "" {
|
|
declModule = strings.Split(test.DeclModule, ".")
|
|
}
|
|
fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
|
|
if fromEP == nil || toEP == nil {
|
|
t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal)
|
|
}
|
|
|
|
// We only have an AbsResourceInstance parser, not an
|
|
// AbsResourceParser, and so we'll just cheat and parse this
|
|
// as a resource instance but fail if it includes an instance
|
|
// key.
|
|
receiverInstanceAddr, diags := ParseAbsResourceInstanceStr(test.Receiver)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("invalid reciever address: %s", diags.Err().Error())
|
|
}
|
|
if receiverInstanceAddr.Resource.Key != NoKey {
|
|
t.Fatalf("invalid reciever address: must be a resource, not a resource instance")
|
|
}
|
|
receiverAddr := receiverInstanceAddr.ContainingResource()
|
|
gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
|
|
if !test.WantMatch {
|
|
if gotMatch {
|
|
t.Errorf("unexpected match\nreceiver: %s (%T)\nfrom: %s\nto: %s\nresult: %s", test.Receiver, receiverAddr, fromEP, toEP, gotAddr)
|
|
}
|
|
return
|
|
}
|
|
|
|
if !gotMatch {
|
|
t.Fatalf("unexpected non-match\nreceiver: %s (%T)\nfrom: %s\nto: %s\ngot: no match\nwant: %s", test.Receiver, receiverAddr, fromEP, toEP, test.WantResult)
|
|
}
|
|
|
|
if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestMoveEndpointChainAndNested(t *testing.T) {
|
|
tests := []struct {
|
|
Endpoint, Other AbsMoveable
|
|
EndpointMod, OtherMod Module
|
|
CanChainFrom, NestedWithin bool
|
|
}{
|
|
{
|
|
Endpoint: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
Other: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
CanChainFrom: true,
|
|
NestedWithin: false,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Other: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
CanChainFrom: false,
|
|
NestedWithin: false,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseModuleInstanceStr("module.foo[2].module.bar[2]"),
|
|
Other: AbsModuleCall{
|
|
Module: RootModuleInstance,
|
|
Call: ModuleCall{Name: "foo"},
|
|
},
|
|
CanChainFrom: false,
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz").ContainingResource(),
|
|
Other: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
CanChainFrom: false,
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar[3].resource.baz[2]"),
|
|
Other: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
CanChainFrom: false,
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
Other: mustParseModuleInstanceStr("module.foo[2]"),
|
|
CanChainFrom: false,
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Other: mustParseModuleInstanceStr("module.foo[2]"),
|
|
CanChainFrom: true,
|
|
NestedWithin: false,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
|
|
Other: mustParseModuleInstanceStr("module.foo[2]"),
|
|
CanChainFrom: false,
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz"),
|
|
Other: mustParseModuleInstanceStr("module.foo[2]"),
|
|
CanChainFrom: false,
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
|
|
CanChainFrom: false,
|
|
NestedWithin: false,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
|
|
CanChainFrom: false,
|
|
NestedWithin: false,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
|
|
CanChainFrom: true,
|
|
NestedWithin: false,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz[2]").ContainingResource(),
|
|
CanChainFrom: false,
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
|
|
CanChainFrom: false,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
|
|
CanChainFrom: false,
|
|
},
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
|
|
CanChainFrom: false,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
|
|
CanChainFrom: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("resource.baz"),
|
|
EndpointMod: Module{"foo"},
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
|
|
CanChainFrom: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
|
|
Other: mustParseAbsResourceInstanceStr("resource.baz"),
|
|
OtherMod: Module{"foo"},
|
|
CanChainFrom: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("resource.baz"),
|
|
EndpointMod: Module{"foo"},
|
|
Other: mustParseAbsResourceInstanceStr("resource.baz"),
|
|
OtherMod: Module{"foo"},
|
|
CanChainFrom: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(),
|
|
EndpointMod: Module{"foo"},
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
|
|
CanChainFrom: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseModuleInstanceStr("module.foo[2].module.baz"),
|
|
Other: mustParseModuleInstanceStr("module.baz"),
|
|
OtherMod: Module{"foo"},
|
|
CanChainFrom: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: AbsModuleCall{
|
|
Call: ModuleCall{Name: "bing"},
|
|
},
|
|
EndpointMod: Module{"foo", "baz"},
|
|
Other: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.baz"),
|
|
Call: ModuleCall{Name: "bing"},
|
|
},
|
|
OtherMod: Module{"foo"},
|
|
CanChainFrom: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("resource.baz"),
|
|
EndpointMod: Module{"foo"},
|
|
Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
|
|
Other: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(),
|
|
OtherMod: Module{"foo"},
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("resource.baz"),
|
|
EndpointMod: Module{"foo"},
|
|
Other: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(),
|
|
OtherMod: Module{"foo"},
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: mustParseAbsResourceInstanceStr("ressurce.baz").ContainingResource(),
|
|
EndpointMod: Module{"foo"},
|
|
Other: mustParseModuleInstanceStr("module.foo[2]"),
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: AbsModuleCall{
|
|
Call: ModuleCall{Name: "bang"},
|
|
},
|
|
EndpointMod: Module{"foo", "baz", "bing"},
|
|
Other: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.baz"),
|
|
Call: ModuleCall{Name: "bing"},
|
|
},
|
|
OtherMod: Module{"foo"},
|
|
NestedWithin: true,
|
|
},
|
|
|
|
{
|
|
Endpoint: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.bing"),
|
|
Call: ModuleCall{Name: "bang"},
|
|
},
|
|
EndpointMod: Module{"foo", "baz"},
|
|
Other: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo.module.baz"),
|
|
Call: ModuleCall{Name: "bing"},
|
|
},
|
|
NestedWithin: true,
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("[%02d]%s.CanChainFrom(%s)", i, test.Endpoint, test.Other),
|
|
func(t *testing.T) {
|
|
endpoint := &MoveEndpointInModule{
|
|
relSubject: test.Endpoint,
|
|
module: test.EndpointMod,
|
|
}
|
|
|
|
other := &MoveEndpointInModule{
|
|
relSubject: test.Other,
|
|
module: test.OtherMod,
|
|
}
|
|
|
|
if endpoint.CanChainFrom(other) != test.CanChainFrom {
|
|
t.Errorf("expected %s CanChainFrom %s == %t", endpoint, other, test.CanChainFrom)
|
|
}
|
|
|
|
if endpoint.NestedWithin(other) != test.NestedWithin {
|
|
t.Errorf("expected %s NestedWithin %s == %t", endpoint, other, test.NestedWithin)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestSelectsModule(t *testing.T) {
|
|
tests := []struct {
|
|
Endpoint *MoveEndpointInModule
|
|
Addr ModuleInstance
|
|
Selects bool
|
|
}{
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
},
|
|
Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[1]"),
|
|
Selects: true,
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
module: mustParseModuleInstanceStr("module.foo").Module(),
|
|
relSubject: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.bar[2]"),
|
|
Call: ModuleCall{Name: "baz"},
|
|
},
|
|
},
|
|
Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[2].module.baz"),
|
|
Selects: true,
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
module: mustParseModuleInstanceStr("module.foo").Module(),
|
|
relSubject: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.bar[2]"),
|
|
Call: ModuleCall{Name: "baz"},
|
|
},
|
|
},
|
|
Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[1].module.baz"),
|
|
Selects: false,
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.bar"),
|
|
Call: ModuleCall{Name: "baz"},
|
|
},
|
|
},
|
|
Addr: mustParseModuleInstanceStr("module.bar[1].module.baz"),
|
|
Selects: false,
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
module: mustParseModuleInstanceStr("module.foo").Module(),
|
|
relSubject: mustParseAbsResourceInstanceStr(`module.bar.resource.name["key"]`),
|
|
},
|
|
Addr: mustParseModuleInstanceStr(`module.foo[1].module.bar`),
|
|
Selects: true,
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
|
|
},
|
|
Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
|
|
Selects: true,
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: mustParseAbsResourceInstanceStr(`module.bar.module.baz["key"].resource.name`).ContainingResource(),
|
|
},
|
|
Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
|
|
Selects: true,
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
module: mustParseModuleInstanceStr("module.nope").Module(),
|
|
relSubject: mustParseAbsResourceInstanceStr(`module.bar.resource.name["key"]`),
|
|
},
|
|
Addr: mustParseModuleInstanceStr(`module.foo[1].module.bar`),
|
|
Selects: false,
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
|
|
},
|
|
Addr: mustParseModuleInstanceStr(`module.bar.module.baz["nope"]`),
|
|
Selects: false,
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: mustParseAbsResourceInstanceStr(`module.nope.module.baz["key"].resource.name`).ContainingResource(),
|
|
},
|
|
Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
|
|
Selects: false,
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("[%02d]%s.SelectsModule(%s)", i, test.Endpoint, test.Addr),
|
|
func(t *testing.T) {
|
|
if test.Endpoint.SelectsModule(test.Addr) != test.Selects {
|
|
t.Errorf("expected %s SelectsModule %s == %t", test.Endpoint, test.Addr, test.Selects)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestSelectsResource(t *testing.T) {
|
|
matchingResource := Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "matching",
|
|
}
|
|
unmatchingResource := Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "unmatching",
|
|
}
|
|
childMod := Module{
|
|
"child",
|
|
}
|
|
childModMatchingInst := ModuleInstance{
|
|
ModuleInstanceStep{Name: "child", InstanceKey: StringKey("matching")},
|
|
}
|
|
childModUnmatchingInst := ModuleInstance{
|
|
ModuleInstanceStep{Name: "child", InstanceKey: StringKey("unmatching")},
|
|
}
|
|
|
|
tests := []struct {
|
|
Endpoint *MoveEndpointInModule
|
|
Addr AbsResource
|
|
Selects bool
|
|
}{
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: matchingResource.Absolute(nil),
|
|
},
|
|
Addr: matchingResource.Absolute(nil),
|
|
Selects: true, // exact match
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: unmatchingResource.Absolute(nil),
|
|
},
|
|
Addr: matchingResource.Absolute(nil),
|
|
Selects: false, // wrong resource name
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: unmatchingResource.Instance(IntKey(1)).Absolute(nil),
|
|
},
|
|
Addr: matchingResource.Absolute(nil),
|
|
Selects: false, // wrong resource name
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: matchingResource.Instance(NoKey).Absolute(nil),
|
|
},
|
|
Addr: matchingResource.Absolute(nil),
|
|
Selects: true, // matches one instance
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: matchingResource.Instance(IntKey(0)).Absolute(nil),
|
|
},
|
|
Addr: matchingResource.Absolute(nil),
|
|
Selects: true, // matches one instance
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: matchingResource.Instance(StringKey("a")).Absolute(nil),
|
|
},
|
|
Addr: matchingResource.Absolute(nil),
|
|
Selects: true, // matches one instance
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
module: childMod,
|
|
relSubject: matchingResource.Absolute(nil),
|
|
},
|
|
Addr: matchingResource.Absolute(childModMatchingInst),
|
|
Selects: true, // in one of the instances of the module where the statement was written
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: matchingResource.Absolute(childModMatchingInst),
|
|
},
|
|
Addr: matchingResource.Absolute(childModMatchingInst),
|
|
Selects: true, // exact match
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: matchingResource.Instance(IntKey(2)).Absolute(childModMatchingInst),
|
|
},
|
|
Addr: matchingResource.Absolute(childModMatchingInst),
|
|
Selects: true, // matches one instance
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: matchingResource.Absolute(childModMatchingInst),
|
|
},
|
|
Addr: matchingResource.Absolute(childModUnmatchingInst),
|
|
Selects: false, // the containing module instance doesn't match
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr("module.foo[2]"),
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
},
|
|
Addr: matchingResource.Absolute(mustParseModuleInstanceStr("module.foo[2]")),
|
|
Selects: false, // a module call can't match a resource
|
|
},
|
|
{
|
|
Endpoint: &MoveEndpointInModule{
|
|
relSubject: mustParseModuleInstanceStr("module.foo[2]"),
|
|
},
|
|
Addr: matchingResource.Absolute(mustParseModuleInstanceStr("module.foo[2]")),
|
|
Selects: false, // a module instance can't match a resource
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("[%02d]%s SelectsResource(%s)", i, test.Endpoint, test.Addr),
|
|
func(t *testing.T) {
|
|
if got, want := test.Endpoint.SelectsResource(test.Addr), test.Selects; got != want {
|
|
t.Errorf("wrong result\nReceiver: %s\nArgument: %s\ngot: %t\nwant: %t", test.Endpoint, test.Addr, got, want)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestIsModuleMoveReIndex(t *testing.T) {
|
|
tests := []struct {
|
|
from, to AbsMoveable
|
|
expect bool
|
|
}{
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar`),
|
|
to: mustParseModuleInstanceStr(`module.bar`),
|
|
expect: true,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar`),
|
|
to: mustParseModuleInstanceStr(`module.bar[0]`),
|
|
expect: true,
|
|
},
|
|
{
|
|
from: AbsModuleCall{
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
to: mustParseModuleInstanceStr(`module.bar[0]`),
|
|
expect: true,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar["a"]`),
|
|
to: AbsModuleCall{
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
expect: true,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.foo`),
|
|
to: mustParseModuleInstanceStr(`module.bar`),
|
|
expect: false,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar`),
|
|
to: mustParseModuleInstanceStr(`module.foo[0]`),
|
|
expect: false,
|
|
},
|
|
{
|
|
from: AbsModuleCall{
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
to: mustParseModuleInstanceStr(`module.foo[0]`),
|
|
expect: false,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar["a"]`),
|
|
to: AbsModuleCall{
|
|
Call: ModuleCall{Name: "foo"},
|
|
},
|
|
expect: false,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
|
|
to: mustParseModuleInstanceStr(`module.bar.module.baz`),
|
|
expect: true,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
|
|
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
|
|
expect: true,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
|
|
to: mustParseModuleInstanceStr(`module.baz.module.baz`),
|
|
expect: false,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
|
|
to: mustParseModuleInstanceStr(`module.baz.module.baz[0]`),
|
|
expect: false,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar.module.baz`),
|
|
to: mustParseModuleInstanceStr(`module.bar[0].module.baz`),
|
|
expect: true,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar[0].module.baz`),
|
|
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
|
|
expect: true,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar[0].module.baz`),
|
|
to: mustParseModuleInstanceStr(`module.bar[1].module.baz[0]`),
|
|
expect: true,
|
|
},
|
|
{
|
|
from: AbsModuleCall{
|
|
Call: ModuleCall{Name: "baz"},
|
|
},
|
|
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
|
|
expect: false,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
|
|
to: AbsModuleCall{
|
|
Call: ModuleCall{Name: "baz"},
|
|
},
|
|
expect: false,
|
|
},
|
|
|
|
{
|
|
from: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr(`module.bar[0]`),
|
|
Call: ModuleCall{Name: "baz"},
|
|
},
|
|
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
|
|
expect: true,
|
|
},
|
|
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
|
|
to: AbsModuleCall{
|
|
Module: mustParseModuleInstanceStr(`module.bar[0]`),
|
|
Call: ModuleCall{Name: "baz"},
|
|
},
|
|
expect: true,
|
|
},
|
|
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.baz`),
|
|
to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
|
|
expect: false,
|
|
},
|
|
{
|
|
from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
|
|
to: mustParseModuleInstanceStr(`module.baz`),
|
|
expect: false,
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("[%02d]IsModuleMoveReIndex(%s, %s)", i, test.from, test.to),
|
|
func(t *testing.T) {
|
|
from := &MoveEndpointInModule{
|
|
relSubject: test.from,
|
|
}
|
|
|
|
to := &MoveEndpointInModule{
|
|
relSubject: test.to,
|
|
}
|
|
|
|
if got := from.IsModuleReIndex(to); got != test.expect {
|
|
t.Errorf("expected %t, got %t", test.expect, got)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func mustParseAbsResourceInstanceStr(s string) AbsResourceInstance {
|
|
r, diags := ParseAbsResourceInstanceStr(s)
|
|
if diags.HasErrors() {
|
|
panic(diags.ErrWithWarnings().Error())
|
|
}
|
|
return r
|
|
}
|