github.com/opentofu/opentofu@v1.7.1/internal/instances/expander_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package instances
     7  
     8  import (
     9  	"fmt"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/google/go-cmp/cmp"
    14  	"github.com/zclconf/go-cty/cty"
    15  
    16  	"github.com/opentofu/opentofu/internal/addrs"
    17  )
    18  
    19  func TestExpander(t *testing.T) {
    20  	// Some module and resource addresses and values we'll use repeatedly below.
    21  	singleModuleAddr := addrs.ModuleCall{Name: "single"}
    22  	count2ModuleAddr := addrs.ModuleCall{Name: "count2"}
    23  	count0ModuleAddr := addrs.ModuleCall{Name: "count0"}
    24  	forEachModuleAddr := addrs.ModuleCall{Name: "for_each"}
    25  	singleResourceAddr := addrs.Resource{
    26  		Mode: addrs.ManagedResourceMode,
    27  		Type: "test",
    28  		Name: "single",
    29  	}
    30  	count2ResourceAddr := addrs.Resource{
    31  		Mode: addrs.ManagedResourceMode,
    32  		Type: "test",
    33  		Name: "count2",
    34  	}
    35  	count0ResourceAddr := addrs.Resource{
    36  		Mode: addrs.ManagedResourceMode,
    37  		Type: "test",
    38  		Name: "count0",
    39  	}
    40  	forEachResourceAddr := addrs.Resource{
    41  		Mode: addrs.ManagedResourceMode,
    42  		Type: "test",
    43  		Name: "for_each",
    44  	}
    45  	eachMap := map[string]cty.Value{
    46  		"a": cty.NumberIntVal(1),
    47  		"b": cty.NumberIntVal(2),
    48  	}
    49  
    50  	// In normal use, Expander would be called in the context of a graph
    51  	// traversal to ensure that information is registered/requested in the
    52  	// correct sequence, but to keep this test self-contained we'll just
    53  	// manually write out the steps here.
    54  	//
    55  	// The steps below are assuming a configuration tree like the following:
    56  	// - root module
    57  	//   - resource test.single with no count or for_each
    58  	//   - resource test.count2 with count = 2
    59  	//   - resource test.count0 with count = 0
    60  	//   - resource test.for_each with for_each = { a = 1, b = 2 }
    61  	//   - child module "single" with no count or for_each
    62  	//     - resource test.single with no count or for_each
    63  	//     - resource test.count2 with count = 2
    64  	//   - child module "count2" with count = 2
    65  	//     - resource test.single with no count or for_each
    66  	//     - resource test.count2 with count = 2
    67  	//     - child module "count2" with count = 2
    68  	//       - resource test.count2 with count = 2
    69  	//   - child module "count0" with count = 0
    70  	//     - resource test.single with no count or for_each
    71  	//   - child module for_each with for_each = { a = 1, b = 2 }
    72  	//     - resource test.single with no count or for_each
    73  	//     - resource test.count2 with count = 2
    74  
    75  	ex := NewExpander()
    76  
    77  	// We don't register the root module, because it's always implied to exist.
    78  	//
    79  	// Below we're going to use braces and indentation just to help visually
    80  	// reflect the tree structure from the tree in the above comment, in the
    81  	// hope that the following is easier to follow.
    82  	//
    83  	// The Expander API requires that we register containing modules before
    84  	// registering anything inside them, so we'll work through the above
    85  	// in a depth-first order in the registration steps that follow.
    86  	{
    87  		ex.SetResourceSingle(addrs.RootModuleInstance, singleResourceAddr)
    88  		ex.SetResourceCount(addrs.RootModuleInstance, count2ResourceAddr, 2)
    89  		ex.SetResourceCount(addrs.RootModuleInstance, count0ResourceAddr, 0)
    90  		ex.SetResourceForEach(addrs.RootModuleInstance, forEachResourceAddr, eachMap)
    91  
    92  		ex.SetModuleSingle(addrs.RootModuleInstance, singleModuleAddr)
    93  		{
    94  			// The single instance of the module
    95  			moduleInstanceAddr := addrs.RootModuleInstance.Child("single", addrs.NoKey)
    96  			ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr)
    97  			ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
    98  		}
    99  
   100  		ex.SetModuleCount(addrs.RootModuleInstance, count2ModuleAddr, 2)
   101  		for i1 := 0; i1 < 2; i1++ {
   102  			moduleInstanceAddr := addrs.RootModuleInstance.Child("count2", addrs.IntKey(i1))
   103  			ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr)
   104  			ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
   105  			ex.SetModuleCount(moduleInstanceAddr, count2ModuleAddr, 2)
   106  			for i2 := 0; i2 < 2; i2++ {
   107  				moduleInstanceAddr := moduleInstanceAddr.Child("count2", addrs.IntKey(i2))
   108  				ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
   109  			}
   110  		}
   111  
   112  		ex.SetModuleCount(addrs.RootModuleInstance, count0ModuleAddr, 0)
   113  		{
   114  			// There are no instances of module "count0", so our nested module
   115  			// would never actually get registered here: the expansion node
   116  			// for the resource would see that its containing module has no
   117  			// instances and so do nothing.
   118  		}
   119  
   120  		ex.SetModuleForEach(addrs.RootModuleInstance, forEachModuleAddr, eachMap)
   121  		for k := range eachMap {
   122  			moduleInstanceAddr := addrs.RootModuleInstance.Child("for_each", addrs.StringKey(k))
   123  			ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr)
   124  			ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
   125  		}
   126  	}
   127  
   128  	t.Run("root module", func(t *testing.T) {
   129  		// Requesting expansion of the root module doesn't really mean anything
   130  		// since it's always a singleton, but for consistency it should work.
   131  		got := ex.ExpandModule(addrs.RootModule)
   132  		want := []addrs.ModuleInstance{addrs.RootModuleInstance}
   133  		if diff := cmp.Diff(want, got); diff != "" {
   134  			t.Errorf("wrong result\n%s", diff)
   135  		}
   136  	})
   137  	t.Run("resource single", func(t *testing.T) {
   138  		got := ex.ExpandModuleResource(
   139  			addrs.RootModule,
   140  			singleResourceAddr,
   141  		)
   142  		want := []addrs.AbsResourceInstance{
   143  			mustAbsResourceInstanceAddr(`test.single`),
   144  		}
   145  		if diff := cmp.Diff(want, got); diff != "" {
   146  			t.Errorf("wrong result\n%s", diff)
   147  		}
   148  	})
   149  	t.Run("resource count2", func(t *testing.T) {
   150  		got := ex.ExpandModuleResource(
   151  			addrs.RootModule,
   152  			count2ResourceAddr,
   153  		)
   154  		want := []addrs.AbsResourceInstance{
   155  			mustAbsResourceInstanceAddr(`test.count2[0]`),
   156  			mustAbsResourceInstanceAddr(`test.count2[1]`),
   157  		}
   158  		if diff := cmp.Diff(want, got); diff != "" {
   159  			t.Errorf("wrong result\n%s", diff)
   160  		}
   161  	})
   162  	t.Run("resource count0", func(t *testing.T) {
   163  		got := ex.ExpandModuleResource(
   164  			addrs.RootModule,
   165  			count0ResourceAddr,
   166  		)
   167  		want := []addrs.AbsResourceInstance(nil)
   168  		if diff := cmp.Diff(want, got); diff != "" {
   169  			t.Errorf("wrong result\n%s", diff)
   170  		}
   171  	})
   172  	t.Run("resource for_each", func(t *testing.T) {
   173  		got := ex.ExpandModuleResource(
   174  			addrs.RootModule,
   175  			forEachResourceAddr,
   176  		)
   177  		want := []addrs.AbsResourceInstance{
   178  			mustAbsResourceInstanceAddr(`test.for_each["a"]`),
   179  			mustAbsResourceInstanceAddr(`test.for_each["b"]`),
   180  		}
   181  		if diff := cmp.Diff(want, got); diff != "" {
   182  			t.Errorf("wrong result\n%s", diff)
   183  		}
   184  	})
   185  	t.Run("module single", func(t *testing.T) {
   186  		got := ex.ExpandModule(addrs.RootModule.Child("single"))
   187  		want := []addrs.ModuleInstance{
   188  			mustModuleInstanceAddr(`module.single`),
   189  		}
   190  		if diff := cmp.Diff(want, got); diff != "" {
   191  			t.Errorf("wrong result\n%s", diff)
   192  		}
   193  	})
   194  	t.Run("module single resource single", func(t *testing.T) {
   195  		got := ex.ExpandModuleResource(
   196  			mustModuleAddr("single"),
   197  			singleResourceAddr,
   198  		)
   199  		want := []addrs.AbsResourceInstance{
   200  			mustAbsResourceInstanceAddr("module.single.test.single"),
   201  		}
   202  		if diff := cmp.Diff(want, got); diff != "" {
   203  			t.Errorf("wrong result\n%s", diff)
   204  		}
   205  	})
   206  	t.Run("module single resource count2", func(t *testing.T) {
   207  		// Two different ways of asking the same question, which should
   208  		// both produce the same result.
   209  		// First: nested expansion of all instances of the resource across
   210  		// all instances of the module, but it's a single-instance module
   211  		// so the first level is a singleton.
   212  		got1 := ex.ExpandModuleResource(
   213  			mustModuleAddr(`single`),
   214  			count2ResourceAddr,
   215  		)
   216  		// Second: expansion of only instances belonging to a specific
   217  		// instance of the module, but again it's a single-instance module
   218  		// so there's only one to ask about.
   219  		got2 := ex.ExpandResource(
   220  			count2ResourceAddr.Absolute(
   221  				addrs.RootModuleInstance.Child("single", addrs.NoKey),
   222  			),
   223  		)
   224  		want := []addrs.AbsResourceInstance{
   225  			mustAbsResourceInstanceAddr(`module.single.test.count2[0]`),
   226  			mustAbsResourceInstanceAddr(`module.single.test.count2[1]`),
   227  		}
   228  		if diff := cmp.Diff(want, got1); diff != "" {
   229  			t.Errorf("wrong ExpandModuleResource result\n%s", diff)
   230  		}
   231  		if diff := cmp.Diff(want, got2); diff != "" {
   232  			t.Errorf("wrong ExpandResource result\n%s", diff)
   233  		}
   234  	})
   235  	t.Run("module single resource count2 with non-existing module instance", func(t *testing.T) {
   236  		got := ex.ExpandResource(
   237  			count2ResourceAddr.Absolute(
   238  				// Note: This is intentionally an invalid instance key,
   239  				// so we're asking about module.single[1].test.count2
   240  				// even though module.single doesn't have count set and
   241  				// therefore there is no module.single[1].
   242  				addrs.RootModuleInstance.Child("single", addrs.IntKey(1)),
   243  			),
   244  		)
   245  		// If the containing module instance doesn't exist then it can't
   246  		// possibly have any resource instances inside it.
   247  		want := ([]addrs.AbsResourceInstance)(nil)
   248  		if diff := cmp.Diff(want, got); diff != "" {
   249  			t.Errorf("wrong result\n%s", diff)
   250  		}
   251  	})
   252  	t.Run("module count2", func(t *testing.T) {
   253  		got := ex.ExpandModule(mustModuleAddr(`count2`))
   254  		want := []addrs.ModuleInstance{
   255  			mustModuleInstanceAddr(`module.count2[0]`),
   256  			mustModuleInstanceAddr(`module.count2[1]`),
   257  		}
   258  		if diff := cmp.Diff(want, got); diff != "" {
   259  			t.Errorf("wrong result\n%s", diff)
   260  		}
   261  	})
   262  	t.Run("module count2 resource single", func(t *testing.T) {
   263  		got := ex.ExpandModuleResource(
   264  			mustModuleAddr(`count2`),
   265  			singleResourceAddr,
   266  		)
   267  		want := []addrs.AbsResourceInstance{
   268  			mustAbsResourceInstanceAddr(`module.count2[0].test.single`),
   269  			mustAbsResourceInstanceAddr(`module.count2[1].test.single`),
   270  		}
   271  		if diff := cmp.Diff(want, got); diff != "" {
   272  			t.Errorf("wrong result\n%s", diff)
   273  		}
   274  	})
   275  	t.Run("module count2 resource count2", func(t *testing.T) {
   276  		got := ex.ExpandModuleResource(
   277  			mustModuleAddr(`count2`),
   278  			count2ResourceAddr,
   279  		)
   280  		want := []addrs.AbsResourceInstance{
   281  			mustAbsResourceInstanceAddr(`module.count2[0].test.count2[0]`),
   282  			mustAbsResourceInstanceAddr(`module.count2[0].test.count2[1]`),
   283  			mustAbsResourceInstanceAddr(`module.count2[1].test.count2[0]`),
   284  			mustAbsResourceInstanceAddr(`module.count2[1].test.count2[1]`),
   285  		}
   286  		if diff := cmp.Diff(want, got); diff != "" {
   287  			t.Errorf("wrong result\n%s", diff)
   288  		}
   289  	})
   290  	t.Run("module count2 module count2", func(t *testing.T) {
   291  		got := ex.ExpandModule(mustModuleAddr(`count2.count2`))
   292  		want := []addrs.ModuleInstance{
   293  			mustModuleInstanceAddr(`module.count2[0].module.count2[0]`),
   294  			mustModuleInstanceAddr(`module.count2[0].module.count2[1]`),
   295  			mustModuleInstanceAddr(`module.count2[1].module.count2[0]`),
   296  			mustModuleInstanceAddr(`module.count2[1].module.count2[1]`),
   297  		}
   298  		if diff := cmp.Diff(want, got); diff != "" {
   299  			t.Errorf("wrong result\n%s", diff)
   300  		}
   301  	})
   302  	t.Run("module count2 module count2 GetDeepestExistingModuleInstance", func(t *testing.T) {
   303  		t.Run("first step invalid", func(t *testing.T) {
   304  			got := ex.GetDeepestExistingModuleInstance(mustModuleInstanceAddr(`module.count2["nope"].module.count2[0]`))
   305  			want := addrs.RootModuleInstance
   306  			if !want.Equal(got) {
   307  				t.Errorf("wrong result\ngot:  %s\nwant: %s", got, want)
   308  			}
   309  		})
   310  		t.Run("second step invalid", func(t *testing.T) {
   311  			got := ex.GetDeepestExistingModuleInstance(mustModuleInstanceAddr(`module.count2[1].module.count2`))
   312  			want := mustModuleInstanceAddr(`module.count2[1]`)
   313  			if !want.Equal(got) {
   314  				t.Errorf("wrong result\ngot:  %s\nwant: %s", got, want)
   315  			}
   316  		})
   317  		t.Run("neither step valid", func(t *testing.T) {
   318  			got := ex.GetDeepestExistingModuleInstance(mustModuleInstanceAddr(`module.count2.module.count2["nope"]`))
   319  			want := addrs.RootModuleInstance
   320  			if !want.Equal(got) {
   321  				t.Errorf("wrong result\ngot:  %s\nwant: %s", got, want)
   322  			}
   323  		})
   324  		t.Run("both steps valid", func(t *testing.T) {
   325  			got := ex.GetDeepestExistingModuleInstance(mustModuleInstanceAddr(`module.count2[1].module.count2[0]`))
   326  			want := mustModuleInstanceAddr(`module.count2[1].module.count2[0]`)
   327  			if !want.Equal(got) {
   328  				t.Errorf("wrong result\ngot:  %s\nwant: %s", got, want)
   329  			}
   330  		})
   331  	})
   332  	t.Run("module count2 resource count2 resource count2", func(t *testing.T) {
   333  		got := ex.ExpandModuleResource(
   334  			mustModuleAddr(`count2.count2`),
   335  			count2ResourceAddr,
   336  		)
   337  		want := []addrs.AbsResourceInstance{
   338  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[0].test.count2[0]`),
   339  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[0].test.count2[1]`),
   340  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[0]`),
   341  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[1]`),
   342  			mustAbsResourceInstanceAddr(`module.count2[1].module.count2[0].test.count2[0]`),
   343  			mustAbsResourceInstanceAddr(`module.count2[1].module.count2[0].test.count2[1]`),
   344  			mustAbsResourceInstanceAddr(`module.count2[1].module.count2[1].test.count2[0]`),
   345  			mustAbsResourceInstanceAddr(`module.count2[1].module.count2[1].test.count2[1]`),
   346  		}
   347  		if diff := cmp.Diff(want, got); diff != "" {
   348  			t.Errorf("wrong result\n%s", diff)
   349  		}
   350  	})
   351  	t.Run("module count2 resource count2 resource count2", func(t *testing.T) {
   352  		got := ex.ExpandResource(
   353  			count2ResourceAddr.Absolute(mustModuleInstanceAddr(`module.count2[0].module.count2[1]`)),
   354  		)
   355  		want := []addrs.AbsResourceInstance{
   356  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[0]`),
   357  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[1]`),
   358  		}
   359  		if diff := cmp.Diff(want, got); diff != "" {
   360  			t.Errorf("wrong result\n%s", diff)
   361  		}
   362  	})
   363  	t.Run("module count0", func(t *testing.T) {
   364  		got := ex.ExpandModule(mustModuleAddr(`count0`))
   365  		want := []addrs.ModuleInstance(nil)
   366  		if diff := cmp.Diff(want, got); diff != "" {
   367  			t.Errorf("wrong result\n%s", diff)
   368  		}
   369  	})
   370  	t.Run("module count0 resource single", func(t *testing.T) {
   371  		got := ex.ExpandModuleResource(
   372  			mustModuleAddr(`count0`),
   373  			singleResourceAddr,
   374  		)
   375  		// The containing module has zero instances, so therefore there
   376  		// are zero instances of this resource even though it doesn't have
   377  		// count = 0 set itself.
   378  		want := []addrs.AbsResourceInstance(nil)
   379  		if diff := cmp.Diff(want, got); diff != "" {
   380  			t.Errorf("wrong result\n%s", diff)
   381  		}
   382  	})
   383  	t.Run("module for_each", func(t *testing.T) {
   384  		got := ex.ExpandModule(mustModuleAddr(`for_each`))
   385  		want := []addrs.ModuleInstance{
   386  			mustModuleInstanceAddr(`module.for_each["a"]`),
   387  			mustModuleInstanceAddr(`module.for_each["b"]`),
   388  		}
   389  		if diff := cmp.Diff(want, got); diff != "" {
   390  			t.Errorf("wrong result\n%s", diff)
   391  		}
   392  	})
   393  	t.Run("module for_each resource single", func(t *testing.T) {
   394  		got := ex.ExpandModuleResource(
   395  			mustModuleAddr(`for_each`),
   396  			singleResourceAddr,
   397  		)
   398  		want := []addrs.AbsResourceInstance{
   399  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.single`),
   400  			mustAbsResourceInstanceAddr(`module.for_each["b"].test.single`),
   401  		}
   402  		if diff := cmp.Diff(want, got); diff != "" {
   403  			t.Errorf("wrong result\n%s", diff)
   404  		}
   405  	})
   406  	t.Run("module for_each resource count2", func(t *testing.T) {
   407  		got := ex.ExpandModuleResource(
   408  			mustModuleAddr(`for_each`),
   409  			count2ResourceAddr,
   410  		)
   411  		want := []addrs.AbsResourceInstance{
   412  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[0]`),
   413  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`),
   414  			mustAbsResourceInstanceAddr(`module.for_each["b"].test.count2[0]`),
   415  			mustAbsResourceInstanceAddr(`module.for_each["b"].test.count2[1]`),
   416  		}
   417  		if diff := cmp.Diff(want, got); diff != "" {
   418  			t.Errorf("wrong result\n%s", diff)
   419  		}
   420  	})
   421  	t.Run("module for_each resource count2", func(t *testing.T) {
   422  		got := ex.ExpandResource(
   423  			count2ResourceAddr.Absolute(mustModuleInstanceAddr(`module.for_each["a"]`)),
   424  		)
   425  		want := []addrs.AbsResourceInstance{
   426  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[0]`),
   427  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`),
   428  		}
   429  		if diff := cmp.Diff(want, got); diff != "" {
   430  			t.Errorf("wrong result\n%s", diff)
   431  		}
   432  	})
   433  
   434  	t.Run(`module.for_each["b"] repetitiondata`, func(t *testing.T) {
   435  		got := ex.GetModuleInstanceRepetitionData(
   436  			mustModuleInstanceAddr(`module.for_each["b"]`),
   437  		)
   438  		want := RepetitionData{
   439  			EachKey:   cty.StringVal("b"),
   440  			EachValue: cty.NumberIntVal(2),
   441  		}
   442  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   443  			t.Errorf("wrong result\n%s", diff)
   444  		}
   445  	})
   446  	t.Run(`module.count2[0].module.count2[1] repetitiondata`, func(t *testing.T) {
   447  		got := ex.GetModuleInstanceRepetitionData(
   448  			mustModuleInstanceAddr(`module.count2[0].module.count2[1]`),
   449  		)
   450  		want := RepetitionData{
   451  			CountIndex: cty.NumberIntVal(1),
   452  		}
   453  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   454  			t.Errorf("wrong result\n%s", diff)
   455  		}
   456  	})
   457  	t.Run(`module.for_each["a"] repetitiondata`, func(t *testing.T) {
   458  		got := ex.GetModuleInstanceRepetitionData(
   459  			mustModuleInstanceAddr(`module.for_each["a"]`),
   460  		)
   461  		want := RepetitionData{
   462  			EachKey:   cty.StringVal("a"),
   463  			EachValue: cty.NumberIntVal(1),
   464  		}
   465  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   466  			t.Errorf("wrong result\n%s", diff)
   467  		}
   468  	})
   469  
   470  	t.Run(`test.for_each["a"] repetitiondata`, func(t *testing.T) {
   471  		got := ex.GetResourceInstanceRepetitionData(
   472  			mustAbsResourceInstanceAddr(`test.for_each["a"]`),
   473  		)
   474  		want := RepetitionData{
   475  			EachKey:   cty.StringVal("a"),
   476  			EachValue: cty.NumberIntVal(1),
   477  		}
   478  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   479  			t.Errorf("wrong result\n%s", diff)
   480  		}
   481  	})
   482  	t.Run(`module.for_each["a"].test.single repetitiondata`, func(t *testing.T) {
   483  		got := ex.GetResourceInstanceRepetitionData(
   484  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.single`),
   485  		)
   486  		want := RepetitionData{}
   487  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   488  			t.Errorf("wrong result\n%s", diff)
   489  		}
   490  	})
   491  	t.Run(`module.for_each["a"].test.count2[1] repetitiondata`, func(t *testing.T) {
   492  		got := ex.GetResourceInstanceRepetitionData(
   493  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`),
   494  		)
   495  		want := RepetitionData{
   496  			CountIndex: cty.NumberIntVal(1),
   497  		}
   498  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   499  			t.Errorf("wrong result\n%s", diff)
   500  		}
   501  	})
   502  }
   503  
   504  func mustAbsResourceInstanceAddr(str string) addrs.AbsResourceInstance {
   505  	addr, diags := addrs.ParseAbsResourceInstanceStr(str)
   506  	if diags.HasErrors() {
   507  		panic(fmt.Sprintf("invalid absolute resource instance address: %s", diags.Err()))
   508  	}
   509  	return addr
   510  }
   511  
   512  func mustModuleAddr(str string) addrs.Module {
   513  	if len(str) == 0 {
   514  		return addrs.RootModule
   515  	}
   516  	// We don't have a real parser for these because they don't appear in the
   517  	// language anywhere, but this interpretation mimics the format we
   518  	// produce from the String method on addrs.Module.
   519  	parts := strings.Split(str, ".")
   520  	return addrs.Module(parts)
   521  }
   522  
   523  func mustModuleInstanceAddr(str string) addrs.ModuleInstance {
   524  	if len(str) == 0 {
   525  		return addrs.RootModuleInstance
   526  	}
   527  	addr, diags := addrs.ParseModuleInstanceStr(str)
   528  	if diags.HasErrors() {
   529  		panic(fmt.Sprintf("invalid module instance address: %s", diags.Err()))
   530  	}
   531  	return addr
   532  }
   533  
   534  func valueEquals(a, b cty.Value) bool {
   535  	if a == cty.NilVal || b == cty.NilVal {
   536  		return a == b
   537  	}
   538  	return a.RawEquals(b)
   539  }