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