github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/terraform/resource_address_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package terraform
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"testing"
    10  
    11  	"github.com/terramate-io/tf/addrs"
    12  	"github.com/terramate-io/tf/configs"
    13  )
    14  
    15  func TestParseResourceAddressInternal(t *testing.T) {
    16  	cases := map[string]struct {
    17  		Input    string
    18  		Expected *ResourceAddress
    19  		Output   string
    20  	}{
    21  		"basic resource": {
    22  			"aws_instance.foo",
    23  			&ResourceAddress{
    24  				Mode:         ManagedResourceMode,
    25  				Type:         "aws_instance",
    26  				Name:         "foo",
    27  				InstanceType: TypePrimary,
    28  				Index:        -1,
    29  			},
    30  			"aws_instance.foo",
    31  		},
    32  
    33  		"basic resource with count": {
    34  			"aws_instance.foo.1",
    35  			&ResourceAddress{
    36  				Mode:         ManagedResourceMode,
    37  				Type:         "aws_instance",
    38  				Name:         "foo",
    39  				InstanceType: TypePrimary,
    40  				Index:        1,
    41  			},
    42  			"aws_instance.foo[1]",
    43  		},
    44  
    45  		"data resource": {
    46  			"data.aws_ami.foo",
    47  			&ResourceAddress{
    48  				Mode:         DataResourceMode,
    49  				Type:         "aws_ami",
    50  				Name:         "foo",
    51  				InstanceType: TypePrimary,
    52  				Index:        -1,
    53  			},
    54  			"data.aws_ami.foo",
    55  		},
    56  
    57  		"data resource with count": {
    58  			"data.aws_ami.foo.1",
    59  			&ResourceAddress{
    60  				Mode:         DataResourceMode,
    61  				Type:         "aws_ami",
    62  				Name:         "foo",
    63  				InstanceType: TypePrimary,
    64  				Index:        1,
    65  			},
    66  			"data.aws_ami.foo[1]",
    67  		},
    68  
    69  		"non-data resource with 4 elements": {
    70  			"aws_instance.foo.bar.1",
    71  			nil,
    72  			"",
    73  		},
    74  	}
    75  
    76  	for tn, tc := range cases {
    77  		t.Run(tc.Input, func(t *testing.T) {
    78  			out, err := parseResourceAddressInternal(tc.Input)
    79  			if (err != nil) != (tc.Expected == nil) {
    80  				t.Fatalf("%s: unexpected err: %#v", tn, err)
    81  			}
    82  			if err != nil {
    83  				return
    84  			}
    85  
    86  			if !reflect.DeepEqual(out, tc.Expected) {
    87  				t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out)
    88  			}
    89  
    90  			// Compare outputs if those exist
    91  			expected := tc.Input
    92  			if tc.Output != "" {
    93  				expected = tc.Output
    94  			}
    95  			if out.String() != expected {
    96  				t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out)
    97  			}
    98  
    99  			// Compare equality because the internal parse is used
   100  			// to compare equality to equal inputs.
   101  			if !out.Equals(tc.Expected) {
   102  				t.Fatalf("expected equality:\n\n%#v\n\n%#v", out, tc.Expected)
   103  			}
   104  		})
   105  	}
   106  }
   107  
   108  func TestParseResourceAddress(t *testing.T) {
   109  	cases := map[string]struct {
   110  		Input    string
   111  		Expected *ResourceAddress
   112  		Output   string
   113  		Err      bool
   114  	}{
   115  		"implicit primary managed instance, no specific index": {
   116  			"aws_instance.foo",
   117  			&ResourceAddress{
   118  				Mode:         ManagedResourceMode,
   119  				Type:         "aws_instance",
   120  				Name:         "foo",
   121  				InstanceType: TypePrimary,
   122  				Index:        -1,
   123  			},
   124  			"",
   125  			false,
   126  		},
   127  		"implicit primary data instance, no specific index": {
   128  			"data.aws_instance.foo",
   129  			&ResourceAddress{
   130  				Mode:         DataResourceMode,
   131  				Type:         "aws_instance",
   132  				Name:         "foo",
   133  				InstanceType: TypePrimary,
   134  				Index:        -1,
   135  			},
   136  			"",
   137  			false,
   138  		},
   139  		"implicit primary, explicit index": {
   140  			"aws_instance.foo[2]",
   141  			&ResourceAddress{
   142  				Mode:         ManagedResourceMode,
   143  				Type:         "aws_instance",
   144  				Name:         "foo",
   145  				InstanceType: TypePrimary,
   146  				Index:        2,
   147  			},
   148  			"",
   149  			false,
   150  		},
   151  		"implicit primary, explicit index over ten": {
   152  			"aws_instance.foo[12]",
   153  			&ResourceAddress{
   154  				Mode:         ManagedResourceMode,
   155  				Type:         "aws_instance",
   156  				Name:         "foo",
   157  				InstanceType: TypePrimary,
   158  				Index:        12,
   159  			},
   160  			"",
   161  			false,
   162  		},
   163  		"explicit primary, explicit index": {
   164  			"aws_instance.foo.primary[2]",
   165  			&ResourceAddress{
   166  				Mode:            ManagedResourceMode,
   167  				Type:            "aws_instance",
   168  				Name:            "foo",
   169  				InstanceType:    TypePrimary,
   170  				InstanceTypeSet: true,
   171  				Index:           2,
   172  			},
   173  			"",
   174  			false,
   175  		},
   176  		"tainted": {
   177  			"aws_instance.foo.tainted",
   178  			&ResourceAddress{
   179  				Mode:            ManagedResourceMode,
   180  				Type:            "aws_instance",
   181  				Name:            "foo",
   182  				InstanceType:    TypeTainted,
   183  				InstanceTypeSet: true,
   184  				Index:           -1,
   185  			},
   186  			"",
   187  			false,
   188  		},
   189  		"deposed": {
   190  			"aws_instance.foo.deposed",
   191  			&ResourceAddress{
   192  				Mode:            ManagedResourceMode,
   193  				Type:            "aws_instance",
   194  				Name:            "foo",
   195  				InstanceType:    TypeDeposed,
   196  				InstanceTypeSet: true,
   197  				Index:           -1,
   198  			},
   199  			"",
   200  			false,
   201  		},
   202  		"with a hyphen": {
   203  			"aws_instance.foo-bar",
   204  			&ResourceAddress{
   205  				Mode:         ManagedResourceMode,
   206  				Type:         "aws_instance",
   207  				Name:         "foo-bar",
   208  				InstanceType: TypePrimary,
   209  				Index:        -1,
   210  			},
   211  			"",
   212  			false,
   213  		},
   214  		"managed in a module": {
   215  			"module.child.aws_instance.foo",
   216  			&ResourceAddress{
   217  				Path:         []string{"child"},
   218  				Mode:         ManagedResourceMode,
   219  				Type:         "aws_instance",
   220  				Name:         "foo",
   221  				InstanceType: TypePrimary,
   222  				Index:        -1,
   223  			},
   224  			"",
   225  			false,
   226  		},
   227  		"data in a module": {
   228  			"module.child.data.aws_instance.foo",
   229  			&ResourceAddress{
   230  				Path:         []string{"child"},
   231  				Mode:         DataResourceMode,
   232  				Type:         "aws_instance",
   233  				Name:         "foo",
   234  				InstanceType: TypePrimary,
   235  				Index:        -1,
   236  			},
   237  			"",
   238  			false,
   239  		},
   240  		"nested modules": {
   241  			"module.a.module.b.module.forever.aws_instance.foo",
   242  			&ResourceAddress{
   243  				Path:         []string{"a", "b", "forever"},
   244  				Mode:         ManagedResourceMode,
   245  				Type:         "aws_instance",
   246  				Name:         "foo",
   247  				InstanceType: TypePrimary,
   248  				Index:        -1,
   249  			},
   250  			"",
   251  			false,
   252  		},
   253  		"just a module": {
   254  			"module.a",
   255  			&ResourceAddress{
   256  				Path:         []string{"a"},
   257  				Type:         "",
   258  				Name:         "",
   259  				InstanceType: TypePrimary,
   260  				Index:        -1,
   261  			},
   262  			"",
   263  			false,
   264  		},
   265  		"just a nested module": {
   266  			"module.a.module.b",
   267  			&ResourceAddress{
   268  				Path:         []string{"a", "b"},
   269  				Type:         "",
   270  				Name:         "",
   271  				InstanceType: TypePrimary,
   272  				Index:        -1,
   273  			},
   274  			"",
   275  			false,
   276  		},
   277  		"module missing resource type": {
   278  			"module.name.foo",
   279  			nil,
   280  			"",
   281  			true,
   282  		},
   283  	}
   284  
   285  	for tn, tc := range cases {
   286  		t.Run(tn, func(t *testing.T) {
   287  			out, err := ParseResourceAddress(tc.Input)
   288  			if (err != nil) != tc.Err {
   289  				t.Fatalf("%s: unexpected err: %#v", tn, err)
   290  			}
   291  			if tc.Err {
   292  				return
   293  			}
   294  
   295  			if !reflect.DeepEqual(out, tc.Expected) {
   296  				t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out)
   297  			}
   298  
   299  			expected := tc.Input
   300  			if tc.Output != "" {
   301  				expected = tc.Output
   302  			}
   303  			if out.String() != expected {
   304  				t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out)
   305  			}
   306  		})
   307  	}
   308  }
   309  
   310  func TestResourceAddressContains(t *testing.T) {
   311  	tests := []struct {
   312  		Address *ResourceAddress
   313  		Other   *ResourceAddress
   314  		Want    bool
   315  	}{
   316  		{
   317  			&ResourceAddress{
   318  				Mode:            ManagedResourceMode,
   319  				Type:            "aws_instance",
   320  				Name:            "foo",
   321  				InstanceTypeSet: true,
   322  				InstanceType:    TypePrimary,
   323  				Index:           0,
   324  			},
   325  			&ResourceAddress{
   326  				Mode:            ManagedResourceMode,
   327  				Type:            "aws_instance",
   328  				Name:            "foo",
   329  				InstanceTypeSet: true,
   330  				InstanceType:    TypePrimary,
   331  				Index:           0,
   332  			},
   333  			true,
   334  		},
   335  		{
   336  			&ResourceAddress{
   337  				Mode:            ManagedResourceMode,
   338  				Type:            "aws_instance",
   339  				Name:            "foo",
   340  				InstanceTypeSet: false,
   341  				Index:           0,
   342  			},
   343  			&ResourceAddress{
   344  				Mode:            ManagedResourceMode,
   345  				Type:            "aws_instance",
   346  				Name:            "foo",
   347  				InstanceTypeSet: true,
   348  				InstanceType:    TypePrimary,
   349  				Index:           0,
   350  			},
   351  			true,
   352  		},
   353  		{
   354  			&ResourceAddress{
   355  				Mode:            ManagedResourceMode,
   356  				Type:            "aws_instance",
   357  				Name:            "foo",
   358  				InstanceTypeSet: false,
   359  				Index:           -1,
   360  			},
   361  			&ResourceAddress{
   362  				Mode:            ManagedResourceMode,
   363  				Type:            "aws_instance",
   364  				Name:            "foo",
   365  				InstanceTypeSet: true,
   366  				InstanceType:    TypePrimary,
   367  				Index:           0,
   368  			},
   369  			true,
   370  		},
   371  		{
   372  			&ResourceAddress{
   373  				Mode:            ManagedResourceMode,
   374  				Type:            "aws_instance",
   375  				Name:            "foo",
   376  				InstanceTypeSet: false,
   377  				Index:           -1,
   378  			},
   379  			&ResourceAddress{
   380  				Mode:            ManagedResourceMode,
   381  				Type:            "aws_instance",
   382  				Name:            "foo",
   383  				InstanceTypeSet: false,
   384  				Index:           -1,
   385  			},
   386  			true,
   387  		},
   388  		{
   389  			&ResourceAddress{
   390  				InstanceTypeSet: false,
   391  				Index:           -1,
   392  			},
   393  			&ResourceAddress{
   394  				Mode:            ManagedResourceMode,
   395  				Type:            "aws_instance",
   396  				Name:            "foo",
   397  				InstanceTypeSet: false,
   398  				Index:           -1,
   399  			},
   400  			true,
   401  		},
   402  		{
   403  			&ResourceAddress{
   404  				InstanceTypeSet: false,
   405  				Index:           -1,
   406  			},
   407  			&ResourceAddress{
   408  				Path:            []string{"bar"},
   409  				Mode:            ManagedResourceMode,
   410  				Type:            "aws_instance",
   411  				Name:            "foo",
   412  				InstanceTypeSet: false,
   413  				Index:           -1,
   414  			},
   415  			true,
   416  		},
   417  		{
   418  			&ResourceAddress{
   419  				Path:            []string{"bar"},
   420  				InstanceTypeSet: false,
   421  				Index:           -1,
   422  			},
   423  			&ResourceAddress{
   424  				Path:            []string{"bar"},
   425  				Mode:            ManagedResourceMode,
   426  				Type:            "aws_instance",
   427  				Name:            "foo",
   428  				InstanceTypeSet: false,
   429  				Index:           -1,
   430  			},
   431  			true,
   432  		},
   433  		{
   434  			&ResourceAddress{
   435  				Path:            []string{"bar"},
   436  				InstanceTypeSet: false,
   437  				Index:           -1,
   438  			},
   439  			&ResourceAddress{
   440  				Path:            []string{"bar", "baz"},
   441  				Mode:            ManagedResourceMode,
   442  				Type:            "aws_instance",
   443  				Name:            "foo",
   444  				InstanceTypeSet: false,
   445  				Index:           -1,
   446  			},
   447  			true,
   448  		},
   449  		{
   450  			&ResourceAddress{
   451  				Path:            []string{"bar"},
   452  				InstanceTypeSet: false,
   453  				Index:           -1,
   454  			},
   455  			&ResourceAddress{
   456  				Path:            []string{"bar", "baz"},
   457  				InstanceTypeSet: false,
   458  				Index:           -1,
   459  			},
   460  			true,
   461  		},
   462  		{
   463  			&ResourceAddress{
   464  				Path:            []string{"bar"},
   465  				InstanceTypeSet: false,
   466  				Index:           -1,
   467  			},
   468  			&ResourceAddress{
   469  				Path:            []string{"bar", "baz", "foo", "pizza"},
   470  				InstanceTypeSet: false,
   471  				Index:           -1,
   472  			},
   473  			true,
   474  		},
   475  
   476  		{
   477  			&ResourceAddress{
   478  				Mode:            ManagedResourceMode,
   479  				Type:            "aws_instance",
   480  				Name:            "bar",
   481  				InstanceTypeSet: true,
   482  				InstanceType:    TypePrimary,
   483  				Index:           0,
   484  			},
   485  			&ResourceAddress{
   486  				Mode:            ManagedResourceMode,
   487  				Type:            "aws_instance",
   488  				Name:            "foo",
   489  				InstanceTypeSet: true,
   490  				InstanceType:    TypePrimary,
   491  				Index:           0,
   492  			},
   493  			false,
   494  		},
   495  		{
   496  			&ResourceAddress{
   497  				Mode:            ManagedResourceMode,
   498  				Type:            "aws_instance",
   499  				Name:            "foo",
   500  				InstanceTypeSet: true,
   501  				InstanceType:    TypePrimary,
   502  				Index:           0,
   503  			},
   504  			&ResourceAddress{
   505  				Mode:            DataResourceMode,
   506  				Type:            "aws_instance",
   507  				Name:            "foo",
   508  				InstanceTypeSet: true,
   509  				InstanceType:    TypePrimary,
   510  				Index:           0,
   511  			},
   512  			false,
   513  		},
   514  		{
   515  			&ResourceAddress{
   516  				Path:            []string{"bar"},
   517  				InstanceTypeSet: false,
   518  				Index:           -1,
   519  			},
   520  			&ResourceAddress{
   521  				Path:            []string{"baz"},
   522  				Mode:            ManagedResourceMode,
   523  				Type:            "aws_instance",
   524  				Name:            "foo",
   525  				InstanceTypeSet: false,
   526  				Index:           -1,
   527  			},
   528  			false,
   529  		},
   530  		{
   531  			&ResourceAddress{
   532  				Path:            []string{"bar"},
   533  				InstanceTypeSet: false,
   534  				Index:           -1,
   535  			},
   536  			&ResourceAddress{
   537  				Path:            []string{"baz", "bar"},
   538  				Mode:            ManagedResourceMode,
   539  				Type:            "aws_instance",
   540  				Name:            "foo",
   541  				InstanceTypeSet: false,
   542  				Index:           -1,
   543  			},
   544  			false,
   545  		},
   546  		{
   547  			&ResourceAddress{
   548  				Mode:            ManagedResourceMode,
   549  				Type:            "aws_instance",
   550  				Name:            "foo",
   551  				InstanceTypeSet: true,
   552  				InstanceType:    TypePrimary,
   553  				Index:           0,
   554  			},
   555  			&ResourceAddress{
   556  				Mode:            ManagedResourceMode,
   557  				Type:            "aws_instance",
   558  				Name:            "foo",
   559  				InstanceTypeSet: false,
   560  				Index:           0,
   561  			},
   562  			false,
   563  		},
   564  		{
   565  			&ResourceAddress{
   566  				Path:            []string{"bar", "baz"},
   567  				InstanceTypeSet: false,
   568  				Index:           -1,
   569  			},
   570  			&ResourceAddress{
   571  				Path:            []string{"bar"},
   572  				InstanceTypeSet: false,
   573  				Index:           -1,
   574  			},
   575  			false,
   576  		},
   577  		{
   578  			&ResourceAddress{
   579  				Type:         "aws_instance",
   580  				Name:         "foo",
   581  				Index:        1,
   582  				InstanceType: TypePrimary,
   583  				Mode:         ManagedResourceMode,
   584  			},
   585  			&ResourceAddress{
   586  				Type:         "aws_instance",
   587  				Name:         "foo",
   588  				Index:        -1,
   589  				InstanceType: TypePrimary,
   590  				Mode:         ManagedResourceMode,
   591  			},
   592  			false,
   593  		},
   594  	}
   595  
   596  	for _, test := range tests {
   597  		t.Run(fmt.Sprintf("%s contains %s", test.Address, test.Other), func(t *testing.T) {
   598  			got := test.Address.Contains(test.Other)
   599  			if got != test.Want {
   600  				t.Errorf(
   601  					"wrong result\nrecv:  %s\ngiven: %s\ngot:   %#v\nwant:  %#v",
   602  					test.Address, test.Other,
   603  					got, test.Want,
   604  				)
   605  			}
   606  		})
   607  	}
   608  }
   609  
   610  func TestResourceAddressEquals(t *testing.T) {
   611  	cases := map[string]struct {
   612  		Address *ResourceAddress
   613  		Other   interface{}
   614  		Expect  bool
   615  	}{
   616  		"basic match": {
   617  			Address: &ResourceAddress{
   618  				Mode:         ManagedResourceMode,
   619  				Type:         "aws_instance",
   620  				Name:         "foo",
   621  				InstanceType: TypePrimary,
   622  				Index:        0,
   623  			},
   624  			Other: &ResourceAddress{
   625  				Mode:         ManagedResourceMode,
   626  				Type:         "aws_instance",
   627  				Name:         "foo",
   628  				InstanceType: TypePrimary,
   629  				Index:        0,
   630  			},
   631  			Expect: true,
   632  		},
   633  		"address does not set index": {
   634  			Address: &ResourceAddress{
   635  				Mode:         ManagedResourceMode,
   636  				Type:         "aws_instance",
   637  				Name:         "foo",
   638  				InstanceType: TypePrimary,
   639  				Index:        -1,
   640  			},
   641  			Other: &ResourceAddress{
   642  				Mode:         ManagedResourceMode,
   643  				Type:         "aws_instance",
   644  				Name:         "foo",
   645  				InstanceType: TypePrimary,
   646  				Index:        3,
   647  			},
   648  			Expect: true,
   649  		},
   650  		"other does not set index": {
   651  			Address: &ResourceAddress{
   652  				Mode:         ManagedResourceMode,
   653  				Type:         "aws_instance",
   654  				Name:         "foo",
   655  				InstanceType: TypePrimary,
   656  				Index:        3,
   657  			},
   658  			Other: &ResourceAddress{
   659  				Mode:         ManagedResourceMode,
   660  				Type:         "aws_instance",
   661  				Name:         "foo",
   662  				InstanceType: TypePrimary,
   663  				Index:        -1,
   664  			},
   665  			Expect: true,
   666  		},
   667  		"neither sets index": {
   668  			Address: &ResourceAddress{
   669  				Mode:         ManagedResourceMode,
   670  				Type:         "aws_instance",
   671  				Name:         "foo",
   672  				InstanceType: TypePrimary,
   673  				Index:        -1,
   674  			},
   675  			Other: &ResourceAddress{
   676  				Mode:         ManagedResourceMode,
   677  				Type:         "aws_instance",
   678  				Name:         "foo",
   679  				InstanceType: TypePrimary,
   680  				Index:        -1,
   681  			},
   682  			Expect: true,
   683  		},
   684  		"index over ten": {
   685  			Address: &ResourceAddress{
   686  				Mode:         ManagedResourceMode,
   687  				Type:         "aws_instance",
   688  				Name:         "foo",
   689  				InstanceType: TypePrimary,
   690  				Index:        1,
   691  			},
   692  			Other: &ResourceAddress{
   693  				Mode:         ManagedResourceMode,
   694  				Type:         "aws_instance",
   695  				Name:         "foo",
   696  				InstanceType: TypePrimary,
   697  				Index:        13,
   698  			},
   699  			Expect: false,
   700  		},
   701  		"different type": {
   702  			Address: &ResourceAddress{
   703  				Mode:         ManagedResourceMode,
   704  				Type:         "aws_instance",
   705  				Name:         "foo",
   706  				InstanceType: TypePrimary,
   707  				Index:        0,
   708  			},
   709  			Other: &ResourceAddress{
   710  				Mode:         ManagedResourceMode,
   711  				Type:         "aws_vpc",
   712  				Name:         "foo",
   713  				InstanceType: TypePrimary,
   714  				Index:        0,
   715  			},
   716  			Expect: false,
   717  		},
   718  		"different mode": {
   719  			Address: &ResourceAddress{
   720  				Mode:         ManagedResourceMode,
   721  				Type:         "aws_instance",
   722  				Name:         "foo",
   723  				InstanceType: TypePrimary,
   724  				Index:        0,
   725  			},
   726  			Other: &ResourceAddress{
   727  				Mode:         DataResourceMode,
   728  				Type:         "aws_instance",
   729  				Name:         "foo",
   730  				InstanceType: TypePrimary,
   731  				Index:        0,
   732  			},
   733  			Expect: false,
   734  		},
   735  		"different name": {
   736  			Address: &ResourceAddress{
   737  				Mode:         ManagedResourceMode,
   738  				Type:         "aws_instance",
   739  				Name:         "foo",
   740  				InstanceType: TypePrimary,
   741  				Index:        0,
   742  			},
   743  			Other: &ResourceAddress{
   744  				Mode:         ManagedResourceMode,
   745  				Type:         "aws_instance",
   746  				Name:         "bar",
   747  				InstanceType: TypePrimary,
   748  				Index:        0,
   749  			},
   750  			Expect: false,
   751  		},
   752  		"different instance type": {
   753  			Address: &ResourceAddress{
   754  				Mode:         ManagedResourceMode,
   755  				Type:         "aws_instance",
   756  				Name:         "foo",
   757  				InstanceType: TypePrimary,
   758  				Index:        0,
   759  			},
   760  			Other: &ResourceAddress{
   761  				Mode:         ManagedResourceMode,
   762  				Type:         "aws_instance",
   763  				Name:         "foo",
   764  				InstanceType: TypeTainted,
   765  				Index:        0,
   766  			},
   767  			Expect: false,
   768  		},
   769  		"different index": {
   770  			Address: &ResourceAddress{
   771  				Mode:         ManagedResourceMode,
   772  				Type:         "aws_instance",
   773  				Name:         "foo",
   774  				InstanceType: TypePrimary,
   775  				Index:        0,
   776  			},
   777  			Other: &ResourceAddress{
   778  				Mode:         ManagedResourceMode,
   779  				Type:         "aws_instance",
   780  				Name:         "foo",
   781  				InstanceType: TypePrimary,
   782  				Index:        1,
   783  			},
   784  			Expect: false,
   785  		},
   786  		"module address matches address of managed resource inside module": {
   787  			Address: &ResourceAddress{
   788  				Path:         []string{"a", "b"},
   789  				Type:         "",
   790  				Name:         "",
   791  				InstanceType: TypePrimary,
   792  				Index:        -1,
   793  			},
   794  			Other: &ResourceAddress{
   795  				Path:         []string{"a", "b"},
   796  				Mode:         ManagedResourceMode,
   797  				Type:         "aws_instance",
   798  				Name:         "foo",
   799  				InstanceType: TypePrimary,
   800  				Index:        0,
   801  			},
   802  			Expect: true,
   803  		},
   804  		"module address matches address of data resource inside module": {
   805  			Address: &ResourceAddress{
   806  				Path:         []string{"a", "b"},
   807  				Type:         "",
   808  				Name:         "",
   809  				InstanceType: TypePrimary,
   810  				Index:        -1,
   811  			},
   812  			Other: &ResourceAddress{
   813  				Path:         []string{"a", "b"},
   814  				Mode:         DataResourceMode,
   815  				Type:         "aws_instance",
   816  				Name:         "foo",
   817  				InstanceType: TypePrimary,
   818  				Index:        0,
   819  			},
   820  			Expect: true,
   821  		},
   822  		"module address doesn't match managed resource outside module": {
   823  			Address: &ResourceAddress{
   824  				Path:         []string{"a", "b"},
   825  				Type:         "",
   826  				Name:         "",
   827  				InstanceType: TypePrimary,
   828  				Index:        -1,
   829  			},
   830  			Other: &ResourceAddress{
   831  				Path:         []string{"a"},
   832  				Mode:         ManagedResourceMode,
   833  				Type:         "aws_instance",
   834  				Name:         "foo",
   835  				InstanceType: TypePrimary,
   836  				Index:        0,
   837  			},
   838  			Expect: false,
   839  		},
   840  		"module address doesn't match data resource outside module": {
   841  			Address: &ResourceAddress{
   842  				Path:         []string{"a", "b"},
   843  				Type:         "",
   844  				Name:         "",
   845  				InstanceType: TypePrimary,
   846  				Index:        -1,
   847  			},
   848  			Other: &ResourceAddress{
   849  				Path:         []string{"a"},
   850  				Mode:         DataResourceMode,
   851  				Type:         "aws_instance",
   852  				Name:         "foo",
   853  				InstanceType: TypePrimary,
   854  				Index:        0,
   855  			},
   856  			Expect: false,
   857  		},
   858  		"nil path vs empty path should match": {
   859  			Address: &ResourceAddress{
   860  				Path:         []string{},
   861  				Mode:         ManagedResourceMode,
   862  				Type:         "aws_instance",
   863  				Name:         "foo",
   864  				InstanceType: TypePrimary,
   865  				Index:        -1,
   866  			},
   867  			Other: &ResourceAddress{
   868  				Path:         nil,
   869  				Mode:         ManagedResourceMode,
   870  				Type:         "aws_instance",
   871  				Name:         "foo",
   872  				InstanceType: TypePrimary,
   873  				Index:        0,
   874  			},
   875  			Expect: true,
   876  		},
   877  	}
   878  
   879  	for tn, tc := range cases {
   880  		actual := tc.Address.Equals(tc.Other)
   881  		if actual != tc.Expect {
   882  			t.Fatalf("%q: expected equals: %t, got %t for:\n%#v\n%#v",
   883  				tn, tc.Expect, actual, tc.Address, tc.Other)
   884  		}
   885  	}
   886  }
   887  
   888  func TestResourceAddressStateId(t *testing.T) {
   889  	cases := map[string]struct {
   890  		Input    *ResourceAddress
   891  		Expected string
   892  	}{
   893  		"basic resource": {
   894  			&ResourceAddress{
   895  				Mode:         ManagedResourceMode,
   896  				Type:         "aws_instance",
   897  				Name:         "foo",
   898  				InstanceType: TypePrimary,
   899  				Index:        -1,
   900  			},
   901  			"aws_instance.foo",
   902  		},
   903  
   904  		"basic resource with index": {
   905  			&ResourceAddress{
   906  				Mode:         ManagedResourceMode,
   907  				Type:         "aws_instance",
   908  				Name:         "foo",
   909  				InstanceType: TypePrimary,
   910  				Index:        2,
   911  			},
   912  			"aws_instance.foo.2",
   913  		},
   914  
   915  		"data resource": {
   916  			&ResourceAddress{
   917  				Mode:         DataResourceMode,
   918  				Type:         "aws_instance",
   919  				Name:         "foo",
   920  				InstanceType: TypePrimary,
   921  				Index:        -1,
   922  			},
   923  			"data.aws_instance.foo",
   924  		},
   925  	}
   926  
   927  	for tn, tc := range cases {
   928  		t.Run(tn, func(t *testing.T) {
   929  			actual := tc.Input.stateId()
   930  			if actual != tc.Expected {
   931  				t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, tc.Expected, actual)
   932  			}
   933  		})
   934  	}
   935  }
   936  
   937  func TestResourceAddressHasResourceSpec(t *testing.T) {
   938  	cases := []struct {
   939  		Input string
   940  		Want  bool
   941  	}{
   942  		{
   943  			"module.foo",
   944  			false,
   945  		},
   946  		{
   947  			"module.foo.module.bar",
   948  			false,
   949  		},
   950  		{
   951  			"null_resource.baz",
   952  			true,
   953  		},
   954  		{
   955  			"null_resource.baz[0]",
   956  			true,
   957  		},
   958  		{
   959  			"data.null_data_source.baz",
   960  			true,
   961  		},
   962  		{
   963  			"data.null_data_source.baz[0]",
   964  			true,
   965  		},
   966  		{
   967  			"module.foo.null_resource.baz",
   968  			true,
   969  		},
   970  		{
   971  			"module.foo.data.null_data_source.baz",
   972  			true,
   973  		},
   974  		{
   975  			"module.foo.module.bar.null_resource.baz",
   976  			true,
   977  		},
   978  	}
   979  
   980  	for _, test := range cases {
   981  		t.Run(test.Input, func(t *testing.T) {
   982  			addr, err := ParseResourceAddress(test.Input)
   983  			if err != nil {
   984  				t.Fatalf("error parsing address: %s", err)
   985  			}
   986  			got := addr.HasResourceSpec()
   987  			if got != test.Want {
   988  				t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want)
   989  			}
   990  		})
   991  	}
   992  }
   993  
   994  func TestResourceAddressWholeModuleAddress(t *testing.T) {
   995  	cases := []struct {
   996  		Input string
   997  		Want  string
   998  	}{
   999  		{
  1000  			"module.foo",
  1001  			"module.foo",
  1002  		},
  1003  		{
  1004  			"module.foo.module.bar",
  1005  			"module.foo.module.bar",
  1006  		},
  1007  		{
  1008  			"null_resource.baz",
  1009  			"",
  1010  		},
  1011  		{
  1012  			"null_resource.baz[0]",
  1013  			"",
  1014  		},
  1015  		{
  1016  			"data.null_data_source.baz",
  1017  			"",
  1018  		},
  1019  		{
  1020  			"data.null_data_source.baz[0]",
  1021  			"",
  1022  		},
  1023  		{
  1024  			"module.foo.null_resource.baz",
  1025  			"module.foo",
  1026  		},
  1027  		{
  1028  			"module.foo.data.null_data_source.baz",
  1029  			"module.foo",
  1030  		},
  1031  		{
  1032  			"module.foo.module.bar.null_resource.baz",
  1033  			"module.foo.module.bar",
  1034  		},
  1035  	}
  1036  
  1037  	for _, test := range cases {
  1038  		t.Run(test.Input, func(t *testing.T) {
  1039  			addr, err := ParseResourceAddress(test.Input)
  1040  			if err != nil {
  1041  				t.Fatalf("error parsing address: %s", err)
  1042  			}
  1043  			gotAddr := addr.WholeModuleAddress()
  1044  			got := gotAddr.String()
  1045  			if got != test.Want {
  1046  				t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want)
  1047  			}
  1048  		})
  1049  	}
  1050  }
  1051  
  1052  func TestResourceAddressMatchesResourceConfig(t *testing.T) {
  1053  	root := []string(nil)
  1054  	child := []string{"child"}
  1055  	grandchild := []string{"child", "grandchild"}
  1056  	irrelevant := []string{"irrelevant"}
  1057  
  1058  	tests := []struct {
  1059  		Addr       *ResourceAddress
  1060  		ModulePath []string
  1061  		Resource   *configs.Resource
  1062  		Want       bool
  1063  	}{
  1064  		{
  1065  			&ResourceAddress{
  1066  				Mode:  ManagedResourceMode,
  1067  				Type:  "null_resource",
  1068  				Name:  "baz",
  1069  				Index: -1,
  1070  			},
  1071  			root,
  1072  			&configs.Resource{
  1073  				Mode: addrs.ManagedResourceMode,
  1074  				Type: "null_resource",
  1075  				Name: "baz",
  1076  			},
  1077  			true,
  1078  		},
  1079  		{
  1080  			&ResourceAddress{
  1081  				Path:  []string{"child"},
  1082  				Mode:  ManagedResourceMode,
  1083  				Type:  "null_resource",
  1084  				Name:  "baz",
  1085  				Index: -1,
  1086  			},
  1087  			child,
  1088  			&configs.Resource{
  1089  				Mode: addrs.ManagedResourceMode,
  1090  				Type: "null_resource",
  1091  				Name: "baz",
  1092  			},
  1093  			true,
  1094  		},
  1095  		{
  1096  			&ResourceAddress{
  1097  				Path:  []string{"child", "grandchild"},
  1098  				Mode:  ManagedResourceMode,
  1099  				Type:  "null_resource",
  1100  				Name:  "baz",
  1101  				Index: -1,
  1102  			},
  1103  			grandchild,
  1104  			&configs.Resource{
  1105  				Mode: addrs.ManagedResourceMode,
  1106  				Type: "null_resource",
  1107  				Name: "baz",
  1108  			},
  1109  			true,
  1110  		},
  1111  		{
  1112  			&ResourceAddress{
  1113  				Path:  []string{"child"},
  1114  				Index: -1,
  1115  			},
  1116  			child,
  1117  			&configs.Resource{
  1118  				Mode: addrs.ManagedResourceMode,
  1119  				Type: "null_resource",
  1120  				Name: "baz",
  1121  			},
  1122  			true,
  1123  		},
  1124  		{
  1125  			&ResourceAddress{
  1126  				Path:  []string{"child", "grandchild"},
  1127  				Index: -1,
  1128  			},
  1129  			grandchild,
  1130  			&configs.Resource{
  1131  				Mode: addrs.ManagedResourceMode,
  1132  				Type: "null_resource",
  1133  				Name: "baz",
  1134  			},
  1135  			true,
  1136  		},
  1137  		{
  1138  			&ResourceAddress{
  1139  				Mode:  DataResourceMode,
  1140  				Type:  "null_resource",
  1141  				Name:  "baz",
  1142  				Index: -1,
  1143  			},
  1144  			irrelevant,
  1145  			&configs.Resource{
  1146  				Mode: addrs.ManagedResourceMode,
  1147  				Type: "null_resource",
  1148  				Name: "baz",
  1149  			},
  1150  			false,
  1151  		},
  1152  		{
  1153  			&ResourceAddress{
  1154  				Mode:  ManagedResourceMode,
  1155  				Type:  "null_resource",
  1156  				Name:  "baz",
  1157  				Index: -1,
  1158  			},
  1159  			irrelevant,
  1160  			&configs.Resource{
  1161  				Mode: addrs.ManagedResourceMode,
  1162  				Type: "null_resource",
  1163  				Name: "pizza",
  1164  			},
  1165  			false,
  1166  		},
  1167  		{
  1168  			&ResourceAddress{
  1169  				Mode:  ManagedResourceMode,
  1170  				Type:  "null_resource",
  1171  				Name:  "baz",
  1172  				Index: -1,
  1173  			},
  1174  			irrelevant,
  1175  			&configs.Resource{
  1176  				Mode: addrs.ManagedResourceMode,
  1177  				Type: "aws_instance",
  1178  				Name: "baz",
  1179  			},
  1180  			false,
  1181  		},
  1182  		{
  1183  			&ResourceAddress{
  1184  				Path:  []string{"child", "grandchild"},
  1185  				Mode:  ManagedResourceMode,
  1186  				Type:  "null_resource",
  1187  				Name:  "baz",
  1188  				Index: -1,
  1189  			},
  1190  			child,
  1191  			&configs.Resource{
  1192  				Mode: addrs.ManagedResourceMode,
  1193  				Type: "null_resource",
  1194  				Name: "baz",
  1195  			},
  1196  			false,
  1197  		},
  1198  		{
  1199  			&ResourceAddress{
  1200  				Path:  []string{"child"},
  1201  				Mode:  ManagedResourceMode,
  1202  				Type:  "null_resource",
  1203  				Name:  "baz",
  1204  				Index: -1,
  1205  			},
  1206  			grandchild,
  1207  			&configs.Resource{
  1208  				Mode: addrs.ManagedResourceMode,
  1209  				Type: "null_resource",
  1210  				Name: "baz",
  1211  			},
  1212  			false,
  1213  		},
  1214  	}
  1215  
  1216  	for i, test := range tests {
  1217  		t.Run(fmt.Sprintf("%02d-%s", i, test.Addr), func(t *testing.T) {
  1218  			got := test.Addr.MatchesResourceConfig(test.ModulePath, test.Resource)
  1219  			if got != test.Want {
  1220  				t.Errorf(
  1221  					"wrong result\naddr: %s\nmod:  %#v\nrsrc: %#v\ngot:  %#v\nwant: %#v",
  1222  					test.Addr, test.ModulePath, test.Resource, got, test.Want,
  1223  				)
  1224  			}
  1225  		})
  1226  	}
  1227  }
  1228  
  1229  func TestResourceAddressLess(t *testing.T) {
  1230  	tests := []struct {
  1231  		A    string
  1232  		B    string
  1233  		Want bool
  1234  	}{
  1235  		{
  1236  			"foo.bar",
  1237  			"module.baz.foo.bar",
  1238  			true,
  1239  		},
  1240  		{
  1241  			"module.baz.foo.bar",
  1242  			"zzz.bar", // would sort after "module" in lexicographical sort
  1243  			false,
  1244  		},
  1245  		{
  1246  			"module.baz.foo.bar",
  1247  			"module.baz.foo.bar",
  1248  			false,
  1249  		},
  1250  		{
  1251  			"module.baz.foo.bar",
  1252  			"module.boz.foo.bar",
  1253  			true,
  1254  		},
  1255  		{
  1256  			"module.boz.foo.bar",
  1257  			"module.baz.foo.bar",
  1258  			false,
  1259  		},
  1260  		{
  1261  			"a.b",
  1262  			"b.c",
  1263  			true,
  1264  		},
  1265  		{
  1266  			"a.b",
  1267  			"a.c",
  1268  			true,
  1269  		},
  1270  		{
  1271  			"c.b",
  1272  			"b.c",
  1273  			false,
  1274  		},
  1275  		{
  1276  			"a.b[9]",
  1277  			"a.b[10]",
  1278  			true,
  1279  		},
  1280  		{
  1281  			"b.b[9]",
  1282  			"a.b[10]",
  1283  			false,
  1284  		},
  1285  		{
  1286  			"a.b",
  1287  			"a.b.deposed",
  1288  			true,
  1289  		},
  1290  		{
  1291  			"a.b.tainted",
  1292  			"a.b.deposed",
  1293  			true,
  1294  		},
  1295  	}
  1296  
  1297  	for _, test := range tests {
  1298  		t.Run(fmt.Sprintf("%s < %s", test.A, test.B), func(t *testing.T) {
  1299  			addrA, err := ParseResourceAddress(test.A)
  1300  			if err != nil {
  1301  				t.Fatal(err)
  1302  			}
  1303  			addrB, err := ParseResourceAddress(test.B)
  1304  			if err != nil {
  1305  				t.Fatal(err)
  1306  			}
  1307  			got := addrA.Less(addrB)
  1308  			invGot := addrB.Less(addrA)
  1309  			if got != test.Want {
  1310  				t.Errorf(
  1311  					"wrong result\ntest: %s < %s\ngot:  %#v\nwant: %#v",
  1312  					test.A, test.B, got, test.Want,
  1313  				)
  1314  			}
  1315  			if test.A != test.B { // inverse test doesn't apply when equal
  1316  				if invGot != !test.Want {
  1317  					t.Errorf(
  1318  						"wrong inverse result\ntest: %s < %s\ngot:  %#v\nwant: %#v",
  1319  						test.B, test.A, invGot, !test.Want,
  1320  					)
  1321  				}
  1322  			} else {
  1323  				if invGot != test.Want {
  1324  					t.Errorf(
  1325  						"wrong inverse result\ntest: %s < %s\ngot:  %#v\nwant: %#v",
  1326  						test.B, test.A, invGot, test.Want,
  1327  					)
  1328  				}
  1329  			}
  1330  		})
  1331  	}
  1332  }