opentofu/internal/refactoring/move_execute_test.go
Martin Atkins 3e5bfa7364 refactoring: Stubbing of the logic for handling moves
This is a whole lot of nothing right now, just stubbing out some control
flow that ultimately just leads to TODOs that cause it to do nothing at
all.

My intent here is to get this cross-cutting skeleton in place and thus
make it easier for us to collaborate on adding the meat to it, so that
it's more likely we can work on different parts separately and still get
a result that tessellates.
2021-07-14 17:37:48 -07:00

214 lines
5.6 KiB
Go

package refactoring
import (
"fmt"
"sort"
"strings"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/states"
)
func TestApplyMoves(t *testing.T) {
// TODO: Renable this once we're ready to implement the intended behaviors
// it is describing.
t.Skip("ApplyMoves is not yet fully implemented")
providerAddr := addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.MustParseProviderSourceString("example.com/foo/bar"),
}
rootNoKeyResourceAddr := [...]addrs.AbsResourceInstance{
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "foo",
Name: "from",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "foo",
Name: "to",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
}
rootIntKeyResourceAddr := [...]addrs.AbsResourceInstance{
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "foo",
Name: "from",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "foo",
Name: "to",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
}
tests := map[string]struct {
Stmts []MoveStatement
State *states.State
WantResults map[addrs.UniqueKey]MoveResult
WantInstanceAddrs []string
}{
"no moves and empty state": {
[]MoveStatement{},
states.NewState(),
nil,
nil,
},
"no moves": {
[]MoveStatement{},
states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
rootNoKeyResourceAddr[0],
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{}`),
},
providerAddr,
)
}),
nil,
[]string{
`foo.from`,
},
},
"single move of whole singleton resource": {
[]MoveStatement{
testMoveStatement(t, "", "foo.from", "foo.to"),
},
states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
rootNoKeyResourceAddr[0],
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{}`),
},
providerAddr,
)
}),
map[addrs.UniqueKey]MoveResult{
rootNoKeyResourceAddr[0].UniqueKey(): {
From: rootNoKeyResourceAddr[0],
To: rootNoKeyResourceAddr[1],
},
rootNoKeyResourceAddr[1].UniqueKey(): {
From: rootNoKeyResourceAddr[1],
To: rootNoKeyResourceAddr[1],
},
},
[]string{
`foo.to`,
},
},
"single move of whole 'count' resource": {
[]MoveStatement{
testMoveStatement(t, "", "foo.from", "foo.to"),
},
states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
rootIntKeyResourceAddr[0],
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{}`),
},
providerAddr,
)
}),
map[addrs.UniqueKey]MoveResult{
rootNoKeyResourceAddr[0].UniqueKey(): {
From: rootIntKeyResourceAddr[0],
To: rootIntKeyResourceAddr[1],
},
rootNoKeyResourceAddr[1].UniqueKey(): {
From: rootIntKeyResourceAddr[0],
To: rootIntKeyResourceAddr[1],
},
},
[]string{
`foo.to[0]`,
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
var stmtsBuf strings.Builder
for _, stmt := range test.Stmts {
fmt.Fprintf(&stmtsBuf, "- from: %s\n to: %s", stmt.From, stmt.To)
}
t.Logf("move statements:\n%s", stmtsBuf.String())
t.Logf("resource instances in prior state:\n%s", spew.Sdump(allResourceInstanceAddrsInState(test.State)))
state := test.State.DeepCopy() // don't modify the test case in-place
gotResults := ApplyMoves(test.Stmts, state)
if diff := cmp.Diff(test.WantResults, gotResults); diff != "" {
t.Errorf("wrong results\n%s", diff)
}
gotInstAddrs := allResourceInstanceAddrsInState(state)
if diff := cmp.Diff(test.WantInstanceAddrs, gotInstAddrs); diff != "" {
t.Errorf("wrong resource instances in final state\n%s", diff)
}
})
}
}
func testMoveStatement(t *testing.T, module string, from string, to string) MoveStatement {
t.Helper()
moduleAddr := addrs.RootModule
if len(module) != 0 {
moduleAddr = addrs.Module(strings.Split(module, "."))
}
fromTraversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(from), "from", hcl.InitialPos)
if hclDiags.HasErrors() {
t.Fatalf("invalid 'from' argument: %s", hclDiags.Error())
}
fromAddr, diags := addrs.ParseMoveEndpoint(fromTraversal)
if diags.HasErrors() {
t.Fatalf("invalid 'from' argument: %s", diags.Err().Error())
}
toTraversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(to), "to", hcl.InitialPos)
if diags.HasErrors() {
t.Fatalf("invalid 'to' argument: %s", hclDiags.Error())
}
toAddr, diags := addrs.ParseMoveEndpoint(toTraversal)
if diags.HasErrors() {
t.Fatalf("invalid 'from' argument: %s", diags.Err().Error())
}
fromInModule, toInModule := addrs.UnifyMoveEndpoints(moduleAddr, fromAddr, toAddr)
if fromInModule == nil || toInModule == nil {
t.Fatalf("incompatible endpoints")
}
return MoveStatement{
From: fromInModule,
To: toInModule,
// DeclRange not populated because it's unimportant for our tests
}
}
func allResourceInstanceAddrsInState(state *states.State) []string {
var ret []string
for _, ms := range state.Modules {
for _, rs := range ms.Resources {
for key := range rs.Instances {
ret = append(ret, rs.Addr.Instance(key).String())
}
}
}
sort.Strings(ret)
return ret
}