github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/instances/expander_test.go (about)

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