mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
These three types represent the three different address representations we need to represent different stages of analysis for "moved" blocks in the configuration. The goal here is to encapsulate all of the static address wrangling inside these types so that users of these types elsewhere would have to work pretty hard to use them incorrectly. In particular, the MovableEndpoint type intentionally fully encapsulates the weird relative addresses we use in configuration so that code elsewhere in Terraform can never end up holding an address of a type that suggests absolute when it's actually relative. That situation only occurs in the internals of MoveableEndpoint where we use not-really-absolute AbsMoveable address types to represent the not-yet-resolved relative addresses. This only takes care of the static address wrangling. There's lots of other rules for what makes a "moved" block valid which will need to be checked elsewhere because they require more context than just the content of the address itself.
753 lines
16 KiB
Go
753 lines
16 KiB
Go
package addrs
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
)
|
|
|
|
func TestParseMoveEndpoint(t *testing.T) {
|
|
tests := []struct {
|
|
Input string
|
|
WantRel AbsMoveable // funny intermediate subset of AbsMovable
|
|
WantErr string
|
|
}{
|
|
{
|
|
`foo.bar`,
|
|
AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: NoKey,
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`foo.bar[0]`,
|
|
AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: IntKey(0),
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`foo.bar["a"]`,
|
|
AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: StringKey("a"),
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.boop.foo.bar`,
|
|
AbsResourceInstance{
|
|
Module: ModuleInstance{
|
|
ModuleInstanceStep{Name: "boop"},
|
|
},
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: NoKey,
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.boop.foo.bar[0]`,
|
|
AbsResourceInstance{
|
|
Module: ModuleInstance{
|
|
ModuleInstanceStep{Name: "boop"},
|
|
},
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: IntKey(0),
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.boop.foo.bar["a"]`,
|
|
AbsResourceInstance{
|
|
Module: ModuleInstance{
|
|
ModuleInstanceStep{Name: "boop"},
|
|
},
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: StringKey("a"),
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`data.foo.bar`,
|
|
AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: DataResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: NoKey,
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`data.foo.bar[0]`,
|
|
AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: DataResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: IntKey(0),
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`data.foo.bar["a"]`,
|
|
AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: DataResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: StringKey("a"),
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.boop.data.foo.bar`,
|
|
AbsResourceInstance{
|
|
Module: ModuleInstance{
|
|
ModuleInstanceStep{Name: "boop"},
|
|
},
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: DataResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: NoKey,
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.boop.data.foo.bar[0]`,
|
|
AbsResourceInstance{
|
|
Module: ModuleInstance{
|
|
ModuleInstanceStep{Name: "boop"},
|
|
},
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: DataResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: IntKey(0),
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.boop.data.foo.bar["a"]`,
|
|
AbsResourceInstance{
|
|
Module: ModuleInstance{
|
|
ModuleInstanceStep{Name: "boop"},
|
|
},
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: DataResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: StringKey("a"),
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo`,
|
|
ModuleInstance{
|
|
ModuleInstanceStep{Name: "foo"},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo[0]`,
|
|
ModuleInstance{
|
|
ModuleInstanceStep{Name: "foo", InstanceKey: IntKey(0)},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo["a"]`,
|
|
ModuleInstance{
|
|
ModuleInstanceStep{Name: "foo", InstanceKey: StringKey("a")},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo.module.bar`,
|
|
ModuleInstance{
|
|
ModuleInstanceStep{Name: "foo"},
|
|
ModuleInstanceStep{Name: "bar"},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo[1].module.bar`,
|
|
ModuleInstance{
|
|
ModuleInstanceStep{Name: "foo", InstanceKey: IntKey(1)},
|
|
ModuleInstanceStep{Name: "bar"},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo.module.bar[1]`,
|
|
ModuleInstance{
|
|
ModuleInstanceStep{Name: "foo"},
|
|
ModuleInstanceStep{Name: "bar", InstanceKey: IntKey(1)},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo[0].module.bar[1]`,
|
|
ModuleInstance{
|
|
ModuleInstanceStep{Name: "foo", InstanceKey: IntKey(0)},
|
|
ModuleInstanceStep{Name: "bar", InstanceKey: IntKey(1)},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module`,
|
|
nil,
|
|
`Invalid address operator: Prefix "module." must be followed by a module name.`,
|
|
},
|
|
{
|
|
`module[0]`,
|
|
nil,
|
|
`Invalid address operator: Prefix "module." must be followed by a module name.`,
|
|
},
|
|
{
|
|
`module.foo.data`,
|
|
nil,
|
|
`Invalid address: Resource specification must include a resource type and name.`,
|
|
},
|
|
{
|
|
`module.foo.data.bar`,
|
|
nil,
|
|
`Invalid address: Resource specification must include a resource type and name.`,
|
|
},
|
|
{
|
|
`module.foo.data[0]`,
|
|
nil,
|
|
`Invalid address: Resource specification must include a resource type and name.`,
|
|
},
|
|
{
|
|
`module.foo.data.bar[0]`,
|
|
nil,
|
|
`Invalid address: A resource name is required.`,
|
|
},
|
|
{
|
|
`module.foo.bar`,
|
|
nil,
|
|
`Invalid address: Resource specification must include a resource type and name.`,
|
|
},
|
|
{
|
|
`module.foo.bar[0]`,
|
|
nil,
|
|
`Invalid address: A resource name is required.`,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Input, func(t *testing.T) {
|
|
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(test.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)
|
|
|
|
switch {
|
|
case test.WantErr != "":
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("unexpected success\nwant error: %s", test.WantErr)
|
|
}
|
|
gotErr := diags.Err().Error()
|
|
if gotErr != test.WantErr {
|
|
t.Fatalf("wrong error\ngot: %s\nwant: %s", gotErr, test.WantErr)
|
|
}
|
|
default:
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected error: %s", diags.Err().Error())
|
|
}
|
|
if diff := cmp.Diff(test.WantRel, moveEp.relSubject); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUnifyMoveEndpoints(t *testing.T) {
|
|
tests := []struct {
|
|
InputFrom, InputTo string
|
|
Module ModuleInstance
|
|
WantFrom, WantTo AbsMoveable
|
|
}{
|
|
{
|
|
InputFrom: `foo.bar`,
|
|
InputTo: `foo.baz`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: AbsResource{
|
|
Module: RootModuleInstance,
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
WantTo: AbsResource{
|
|
Module: RootModuleInstance,
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
InputFrom: `foo.bar`,
|
|
InputTo: `foo.baz`,
|
|
Module: RootModuleInstance.Child("a", NoKey),
|
|
WantFrom: AbsResource{
|
|
Module: RootModuleInstance.Child("a", NoKey),
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
WantTo: AbsResource{
|
|
Module: RootModuleInstance.Child("a", NoKey),
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
InputFrom: `foo.bar`,
|
|
InputTo: `module.b[0].foo.baz`,
|
|
Module: RootModuleInstance.Child("a", NoKey),
|
|
WantFrom: AbsResource{
|
|
Module: RootModuleInstance.Child("a", NoKey),
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
WantTo: AbsResource{
|
|
Module: RootModuleInstance.Child("a", NoKey).Child("b", IntKey(0)),
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
InputFrom: `foo.bar`,
|
|
InputTo: `foo.bar["thing"]`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
},
|
|
WantTo: AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: StringKey("thing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
InputFrom: `foo.bar["thing"]`,
|
|
InputTo: `foo.bar`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: StringKey("thing"),
|
|
},
|
|
},
|
|
WantTo: AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
InputFrom: `foo.bar["a"]`,
|
|
InputTo: `foo.bar["b"]`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: StringKey("a"),
|
|
},
|
|
},
|
|
WantTo: AbsResourceInstance{
|
|
Module: RootModuleInstance,
|
|
Resource: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
Key: StringKey("b"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
InputFrom: `module.foo`,
|
|
InputTo: `module.bar`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: AbsModuleCall{
|
|
Module: RootModuleInstance,
|
|
Call: ModuleCall{Name: "foo"},
|
|
},
|
|
WantTo: AbsModuleCall{
|
|
Module: RootModuleInstance,
|
|
Call: ModuleCall{Name: "bar"},
|
|
},
|
|
},
|
|
{
|
|
InputFrom: `module.foo`,
|
|
InputTo: `module.bar.module.baz`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: AbsModuleCall{
|
|
Module: RootModuleInstance,
|
|
Call: ModuleCall{Name: "foo"},
|
|
},
|
|
WantTo: AbsModuleCall{
|
|
Module: RootModuleInstance.Child("bar", NoKey),
|
|
Call: ModuleCall{Name: "baz"},
|
|
},
|
|
},
|
|
{
|
|
InputFrom: `module.foo`,
|
|
InputTo: `module.bar.module.baz`,
|
|
Module: RootModuleInstance.Child("bloop", StringKey("hi")),
|
|
WantFrom: AbsModuleCall{
|
|
Module: RootModuleInstance.Child("bloop", StringKey("hi")),
|
|
Call: ModuleCall{Name: "foo"},
|
|
},
|
|
WantTo: AbsModuleCall{
|
|
Module: RootModuleInstance.Child("bloop", StringKey("hi")).Child("bar", NoKey),
|
|
Call: ModuleCall{Name: "baz"},
|
|
},
|
|
},
|
|
{
|
|
InputFrom: `module.foo[0]`,
|
|
InputTo: `module.foo["a"]`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: RootModuleInstance.Child("foo", IntKey(0)),
|
|
WantTo: RootModuleInstance.Child("foo", StringKey("a")),
|
|
},
|
|
{
|
|
InputFrom: `module.foo`,
|
|
InputTo: `module.foo["a"]`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: RootModuleInstance.Child("foo", NoKey),
|
|
WantTo: RootModuleInstance.Child("foo", StringKey("a")),
|
|
},
|
|
{
|
|
InputFrom: `module.foo[0]`,
|
|
InputTo: `module.foo`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: RootModuleInstance.Child("foo", IntKey(0)),
|
|
WantTo: RootModuleInstance.Child("foo", NoKey),
|
|
},
|
|
{
|
|
InputFrom: `module.foo[0]`,
|
|
InputTo: `module.foo`,
|
|
Module: RootModuleInstance.Child("bloop", NoKey),
|
|
WantFrom: RootModuleInstance.Child("bloop", NoKey).Child("foo", IntKey(0)),
|
|
WantTo: RootModuleInstance.Child("bloop", NoKey).Child("foo", NoKey),
|
|
},
|
|
{
|
|
InputFrom: `module.foo`,
|
|
InputTo: `foo.bar`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: nil, // Can't unify module call with resource
|
|
WantTo: nil,
|
|
},
|
|
{
|
|
InputFrom: `module.foo[0]`,
|
|
InputTo: `foo.bar`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: nil, // Can't unify module instance with resource
|
|
WantTo: nil,
|
|
},
|
|
{
|
|
InputFrom: `module.foo`,
|
|
InputTo: `foo.bar[0]`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: nil, // Can't unify module call with resource instance
|
|
WantTo: nil,
|
|
},
|
|
{
|
|
InputFrom: `module.foo[0]`,
|
|
InputTo: `foo.bar[0]`,
|
|
Module: RootModuleInstance,
|
|
WantFrom: nil, // Can't unify module instance with resource instance
|
|
WantTo: nil,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%s to %s in %s", test.InputFrom, test.InputTo, test.Module), func(t *testing.T) {
|
|
parseInput := func(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
|
|
}
|
|
|
|
fromEp := parseInput(test.InputFrom)
|
|
toEp := parseInput(test.InputTo)
|
|
|
|
diffOpts := cmpopts.IgnoreUnexported(ModuleCall{})
|
|
gotFrom, gotTo := UnifyMoveEndpoints(test.Module, fromEp, toEp)
|
|
if diff := cmp.Diff(test.WantFrom, gotFrom, diffOpts); diff != "" {
|
|
t.Errorf("wrong 'from' address\n%s", diff)
|
|
}
|
|
if diff := cmp.Diff(test.WantTo, gotTo, diffOpts); diff != "" {
|
|
t.Errorf("wrong 'to' address\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMoveEndpointConfigMoveable(t *testing.T) {
|
|
tests := []struct {
|
|
Input string
|
|
Module Module
|
|
Want ConfigMoveable
|
|
}{
|
|
{
|
|
`foo.bar`,
|
|
RootModule,
|
|
ConfigResource{
|
|
Module: RootModule,
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`foo.bar[0]`,
|
|
RootModule,
|
|
ConfigResource{
|
|
Module: RootModule,
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`module.foo.bar.baz`,
|
|
RootModule,
|
|
ConfigResource{
|
|
Module: Module{"foo"},
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "bar",
|
|
Name: "baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`module.foo[0].bar.baz`,
|
|
RootModule,
|
|
ConfigResource{
|
|
Module: Module{"foo"},
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "bar",
|
|
Name: "baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`foo.bar`,
|
|
Module{"boop"},
|
|
ConfigResource{
|
|
Module: Module{"boop"},
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`module.bloop.foo.bar`,
|
|
Module{"bleep"},
|
|
ConfigResource{
|
|
Module: Module{"bleep", "bloop"},
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`module.foo.bar.baz`,
|
|
RootModule,
|
|
ConfigResource{
|
|
Module: Module{"foo"},
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "bar",
|
|
Name: "baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`module.foo`,
|
|
RootModule,
|
|
Module{"foo"},
|
|
},
|
|
{
|
|
`module.foo[0]`,
|
|
RootModule,
|
|
Module{"foo"},
|
|
},
|
|
{
|
|
`module.bloop`,
|
|
Module{"bleep"},
|
|
Module{"bleep", "bloop"},
|
|
},
|
|
{
|
|
`module.bloop[0]`,
|
|
Module{"bleep"},
|
|
Module{"bleep", "bloop"},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%s in %s", test.Input, test.Module), func(t *testing.T) {
|
|
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(test.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())
|
|
}
|
|
|
|
got := moveEp.ConfigMoveable(test.Module)
|
|
if diff := cmp.Diff(test.Want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|