github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/addrs/parse_ref_test.go (about)

     1  package addrs
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/go-test/deep"
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/hashicorp/hcl/v2/hclsyntax"
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    10  	"github.com/zclconf/go-cty/cty"
    11  )
    12  
    13  func TestParseRef(t *testing.T) {
    14  	tests := []struct {
    15  		Input   string
    16  		Want    *Reference
    17  		WantErr string
    18  	}{
    19  
    20  		// count
    21  		{
    22  			`count.index`,
    23  			&Reference{
    24  				Subject: CountAttr{
    25  					Name: "index",
    26  				},
    27  				SourceRange: tfdiags.SourceRange{
    28  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
    29  					End:   tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
    30  				},
    31  			},
    32  			``,
    33  		},
    34  		{
    35  			`count.index.blah`,
    36  			&Reference{
    37  				Subject: CountAttr{
    38  					Name: "index",
    39  				},
    40  				SourceRange: tfdiags.SourceRange{
    41  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
    42  					End:   tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
    43  				},
    44  				Remaining: hcl.Traversal{
    45  					hcl.TraverseAttr{
    46  						Name: "blah",
    47  						SrcRange: hcl.Range{
    48  							Start: hcl.Pos{Line: 1, Column: 12, Byte: 11},
    49  							End:   hcl.Pos{Line: 1, Column: 17, Byte: 16},
    50  						},
    51  					},
    52  				},
    53  			},
    54  			``, // valid at this layer, but will fail during eval because "index" is a number
    55  		},
    56  		{
    57  			`count`,
    58  			nil,
    59  			`The "count" object cannot be accessed directly. Instead, access one of its attributes.`,
    60  		},
    61  		{
    62  			`count["hello"]`,
    63  			nil,
    64  			`The "count" object does not support this operation.`,
    65  		},
    66  
    67  		// each
    68  		{
    69  			`each.key`,
    70  			&Reference{
    71  				Subject: ForEachAttr{
    72  					Name: "key",
    73  				},
    74  				SourceRange: tfdiags.SourceRange{
    75  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
    76  					End:   tfdiags.SourcePos{Line: 1, Column: 9, Byte: 8},
    77  				},
    78  			},
    79  			``,
    80  		},
    81  		{
    82  			`each.value.blah`,
    83  			&Reference{
    84  				Subject: ForEachAttr{
    85  					Name: "value",
    86  				},
    87  				SourceRange: tfdiags.SourceRange{
    88  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
    89  					End:   tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10},
    90  				},
    91  				Remaining: hcl.Traversal{
    92  					hcl.TraverseAttr{
    93  						Name: "blah",
    94  						SrcRange: hcl.Range{
    95  							Start: hcl.Pos{Line: 1, Column: 11, Byte: 10},
    96  							End:   hcl.Pos{Line: 1, Column: 16, Byte: 15},
    97  						},
    98  					},
    99  				},
   100  			},
   101  			``,
   102  		},
   103  		{
   104  			`each`,
   105  			nil,
   106  			`The "each" object cannot be accessed directly. Instead, access one of its attributes.`,
   107  		},
   108  		{
   109  			`each["hello"]`,
   110  			nil,
   111  			`The "each" object does not support this operation.`,
   112  		},
   113  		// data
   114  		{
   115  			`data.external.foo`,
   116  			&Reference{
   117  				Subject: Resource{
   118  					Mode: DataResourceMode,
   119  					Type: "external",
   120  					Name: "foo",
   121  				},
   122  				SourceRange: tfdiags.SourceRange{
   123  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   124  					End:   tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
   125  				},
   126  			},
   127  			``,
   128  		},
   129  		{
   130  			`data.external.foo.bar`,
   131  			&Reference{
   132  				Subject: ResourceInstance{
   133  					Resource: Resource{
   134  						Mode: DataResourceMode,
   135  						Type: "external",
   136  						Name: "foo",
   137  					},
   138  				},
   139  				SourceRange: tfdiags.SourceRange{
   140  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   141  					End:   tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
   142  				},
   143  				Remaining: hcl.Traversal{
   144  					hcl.TraverseAttr{
   145  						Name: "bar",
   146  						SrcRange: hcl.Range{
   147  							Start: hcl.Pos{Line: 1, Column: 18, Byte: 17},
   148  							End:   hcl.Pos{Line: 1, Column: 22, Byte: 21},
   149  						},
   150  					},
   151  				},
   152  			},
   153  			``,
   154  		},
   155  		{
   156  			`data.external.foo["baz"].bar`,
   157  			&Reference{
   158  				Subject: ResourceInstance{
   159  					Resource: Resource{
   160  						Mode: DataResourceMode,
   161  						Type: "external",
   162  						Name: "foo",
   163  					},
   164  					Key: StringKey("baz"),
   165  				},
   166  				SourceRange: tfdiags.SourceRange{
   167  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   168  					End:   tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
   169  				},
   170  				Remaining: hcl.Traversal{
   171  					hcl.TraverseAttr{
   172  						Name: "bar",
   173  						SrcRange: hcl.Range{
   174  							Start: hcl.Pos{Line: 1, Column: 25, Byte: 24},
   175  							End:   hcl.Pos{Line: 1, Column: 29, Byte: 28},
   176  						},
   177  					},
   178  				},
   179  			},
   180  			``,
   181  		},
   182  		{
   183  			`data.external.foo["baz"]`,
   184  			&Reference{
   185  				Subject: ResourceInstance{
   186  					Resource: Resource{
   187  						Mode: DataResourceMode,
   188  						Type: "external",
   189  						Name: "foo",
   190  					},
   191  					Key: StringKey("baz"),
   192  				},
   193  				SourceRange: tfdiags.SourceRange{
   194  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   195  					End:   tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
   196  				},
   197  			},
   198  			``,
   199  		},
   200  		{
   201  			`data`,
   202  			nil,
   203  			`The "data" object must be followed by two attribute names: the data source type and the resource name.`,
   204  		},
   205  		{
   206  			`data.external`,
   207  			nil,
   208  			`The "data" object must be followed by two attribute names: the data source type and the resource name.`,
   209  		},
   210  
   211  		// local
   212  		{
   213  			`local.foo`,
   214  			&Reference{
   215  				Subject: LocalValue{
   216  					Name: "foo",
   217  				},
   218  				SourceRange: tfdiags.SourceRange{
   219  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   220  					End:   tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9},
   221  				},
   222  			},
   223  			``,
   224  		},
   225  		{
   226  			`local.foo.blah`,
   227  			&Reference{
   228  				Subject: LocalValue{
   229  					Name: "foo",
   230  				},
   231  				SourceRange: tfdiags.SourceRange{
   232  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   233  					End:   tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9},
   234  				},
   235  				Remaining: hcl.Traversal{
   236  					hcl.TraverseAttr{
   237  						Name: "blah",
   238  						SrcRange: hcl.Range{
   239  							Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
   240  							End:   hcl.Pos{Line: 1, Column: 15, Byte: 14},
   241  						},
   242  					},
   243  				},
   244  			},
   245  			``,
   246  		},
   247  		{
   248  			`local.foo["blah"]`,
   249  			&Reference{
   250  				Subject: LocalValue{
   251  					Name: "foo",
   252  				},
   253  				SourceRange: tfdiags.SourceRange{
   254  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   255  					End:   tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9},
   256  				},
   257  				Remaining: hcl.Traversal{
   258  					hcl.TraverseIndex{
   259  						Key: cty.StringVal("blah"),
   260  						SrcRange: hcl.Range{
   261  							Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
   262  							End:   hcl.Pos{Line: 1, Column: 18, Byte: 17},
   263  						},
   264  					},
   265  				},
   266  			},
   267  			``,
   268  		},
   269  		{
   270  			`local`,
   271  			nil,
   272  			`The "local" object cannot be accessed directly. Instead, access one of its attributes.`,
   273  		},
   274  		{
   275  			`local["foo"]`,
   276  			nil,
   277  			`The "local" object does not support this operation.`,
   278  		},
   279  
   280  		// module
   281  		{
   282  			`module.foo`,
   283  			&Reference{
   284  				Subject: ModuleCallInstance{
   285  					Call: ModuleCall{
   286  						Name: "foo",
   287  					},
   288  				},
   289  				SourceRange: tfdiags.SourceRange{
   290  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   291  					End:   tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10},
   292  				},
   293  			},
   294  			``,
   295  		},
   296  		{
   297  			`module.foo.bar`,
   298  			&Reference{
   299  				Subject: ModuleCallOutput{
   300  					Call: ModuleCallInstance{
   301  						Call: ModuleCall{
   302  							Name: "foo",
   303  						},
   304  					},
   305  					Name: "bar",
   306  				},
   307  				SourceRange: tfdiags.SourceRange{
   308  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   309  					End:   tfdiags.SourcePos{Line: 1, Column: 15, Byte: 14},
   310  				},
   311  			},
   312  			``,
   313  		},
   314  		{
   315  			`module.foo.bar.baz`,
   316  			&Reference{
   317  				Subject: ModuleCallOutput{
   318  					Call: ModuleCallInstance{
   319  						Call: ModuleCall{
   320  							Name: "foo",
   321  						},
   322  					},
   323  					Name: "bar",
   324  				},
   325  				SourceRange: tfdiags.SourceRange{
   326  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   327  					End:   tfdiags.SourcePos{Line: 1, Column: 15, Byte: 14},
   328  				},
   329  				Remaining: hcl.Traversal{
   330  					hcl.TraverseAttr{
   331  						Name: "baz",
   332  						SrcRange: hcl.Range{
   333  							Start: hcl.Pos{Line: 1, Column: 15, Byte: 14},
   334  							End:   hcl.Pos{Line: 1, Column: 19, Byte: 18},
   335  						},
   336  					},
   337  				},
   338  			},
   339  			``,
   340  		},
   341  		{
   342  			`module.foo["baz"]`,
   343  			&Reference{
   344  				Subject: ModuleCallInstance{
   345  					Call: ModuleCall{
   346  						Name: "foo",
   347  					},
   348  					Key: StringKey("baz"),
   349  				},
   350  				SourceRange: tfdiags.SourceRange{
   351  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   352  					End:   tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
   353  				},
   354  			},
   355  			``,
   356  		},
   357  		{
   358  			`module.foo["baz"].bar`,
   359  			&Reference{
   360  				Subject: ModuleCallOutput{
   361  					Call: ModuleCallInstance{
   362  						Call: ModuleCall{
   363  							Name: "foo",
   364  						},
   365  						Key: StringKey("baz"),
   366  					},
   367  					Name: "bar",
   368  				},
   369  				SourceRange: tfdiags.SourceRange{
   370  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   371  					End:   tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21},
   372  				},
   373  			},
   374  			``,
   375  		},
   376  		{
   377  			`module.foo["baz"].bar.boop`,
   378  			&Reference{
   379  				Subject: ModuleCallOutput{
   380  					Call: ModuleCallInstance{
   381  						Call: ModuleCall{
   382  							Name: "foo",
   383  						},
   384  						Key: StringKey("baz"),
   385  					},
   386  					Name: "bar",
   387  				},
   388  				SourceRange: tfdiags.SourceRange{
   389  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   390  					End:   tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21},
   391  				},
   392  				Remaining: hcl.Traversal{
   393  					hcl.TraverseAttr{
   394  						Name: "boop",
   395  						SrcRange: hcl.Range{
   396  							Start: hcl.Pos{Line: 1, Column: 22, Byte: 21},
   397  							End:   hcl.Pos{Line: 1, Column: 27, Byte: 26},
   398  						},
   399  					},
   400  				},
   401  			},
   402  			``,
   403  		},
   404  		{
   405  			`module`,
   406  			nil,
   407  			`The "module" object cannot be accessed directly. Instead, access one of its attributes.`,
   408  		},
   409  		{
   410  			`module["foo"]`,
   411  			nil,
   412  			`The "module" object does not support this operation.`,
   413  		},
   414  
   415  		// path
   416  		{
   417  			`path.module`,
   418  			&Reference{
   419  				Subject: PathAttr{
   420  					Name: "module",
   421  				},
   422  				SourceRange: tfdiags.SourceRange{
   423  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   424  					End:   tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
   425  				},
   426  			},
   427  			``,
   428  		},
   429  		{
   430  			`path.module.blah`,
   431  			&Reference{
   432  				Subject: PathAttr{
   433  					Name: "module",
   434  				},
   435  				SourceRange: tfdiags.SourceRange{
   436  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   437  					End:   tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
   438  				},
   439  				Remaining: hcl.Traversal{
   440  					hcl.TraverseAttr{
   441  						Name: "blah",
   442  						SrcRange: hcl.Range{
   443  							Start: hcl.Pos{Line: 1, Column: 12, Byte: 11},
   444  							End:   hcl.Pos{Line: 1, Column: 17, Byte: 16},
   445  						},
   446  					},
   447  				},
   448  			},
   449  			``, // valid at this layer, but will fail during eval because "module" is a string
   450  		},
   451  		{
   452  			`path`,
   453  			nil,
   454  			`The "path" object cannot be accessed directly. Instead, access one of its attributes.`,
   455  		},
   456  		{
   457  			`path["module"]`,
   458  			nil,
   459  			`The "path" object does not support this operation.`,
   460  		},
   461  
   462  		// self
   463  		{
   464  			`self`,
   465  			&Reference{
   466  				Subject: Self,
   467  				SourceRange: tfdiags.SourceRange{
   468  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   469  					End:   tfdiags.SourcePos{Line: 1, Column: 5, Byte: 4},
   470  				},
   471  			},
   472  			``,
   473  		},
   474  		{
   475  			`self.blah`,
   476  			&Reference{
   477  				Subject: Self,
   478  				SourceRange: tfdiags.SourceRange{
   479  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   480  					End:   tfdiags.SourcePos{Line: 1, Column: 5, Byte: 4},
   481  				},
   482  				Remaining: hcl.Traversal{
   483  					hcl.TraverseAttr{
   484  						Name: "blah",
   485  						SrcRange: hcl.Range{
   486  							Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
   487  							End:   hcl.Pos{Line: 1, Column: 10, Byte: 9},
   488  						},
   489  					},
   490  				},
   491  			},
   492  			``,
   493  		},
   494  
   495  		// terraform
   496  		{
   497  			`terraform.workspace`,
   498  			&Reference{
   499  				Subject: TerraformAttr{
   500  					Name: "workspace",
   501  				},
   502  				SourceRange: tfdiags.SourceRange{
   503  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   504  					End:   tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19},
   505  				},
   506  			},
   507  			``,
   508  		},
   509  		{
   510  			`terraform.workspace.blah`,
   511  			&Reference{
   512  				Subject: TerraformAttr{
   513  					Name: "workspace",
   514  				},
   515  				SourceRange: tfdiags.SourceRange{
   516  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   517  					End:   tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19},
   518  				},
   519  				Remaining: hcl.Traversal{
   520  					hcl.TraverseAttr{
   521  						Name: "blah",
   522  						SrcRange: hcl.Range{
   523  							Start: hcl.Pos{Line: 1, Column: 20, Byte: 19},
   524  							End:   hcl.Pos{Line: 1, Column: 25, Byte: 24},
   525  						},
   526  					},
   527  				},
   528  			},
   529  			``, // valid at this layer, but will fail during eval because "workspace" is a string
   530  		},
   531  		{
   532  			`terraform`,
   533  			nil,
   534  			`The "terraform" object cannot be accessed directly. Instead, access one of its attributes.`,
   535  		},
   536  		{
   537  			`terraform["workspace"]`,
   538  			nil,
   539  			`The "terraform" object does not support this operation.`,
   540  		},
   541  
   542  		// var
   543  		{
   544  			`var.foo`,
   545  			&Reference{
   546  				Subject: InputVariable{
   547  					Name: "foo",
   548  				},
   549  				SourceRange: tfdiags.SourceRange{
   550  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   551  					End:   tfdiags.SourcePos{Line: 1, Column: 8, Byte: 7},
   552  				},
   553  			},
   554  			``,
   555  		},
   556  		{
   557  			`var.foo.blah`,
   558  			&Reference{
   559  				Subject: InputVariable{
   560  					Name: "foo",
   561  				},
   562  				SourceRange: tfdiags.SourceRange{
   563  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   564  					End:   tfdiags.SourcePos{Line: 1, Column: 8, Byte: 7},
   565  				},
   566  				Remaining: hcl.Traversal{
   567  					hcl.TraverseAttr{
   568  						Name: "blah",
   569  						SrcRange: hcl.Range{
   570  							Start: hcl.Pos{Line: 1, Column: 8, Byte: 7},
   571  							End:   hcl.Pos{Line: 1, Column: 13, Byte: 12},
   572  						},
   573  					},
   574  				},
   575  			},
   576  			``, // valid at this layer, but will fail during eval because "module" is a string
   577  		},
   578  		{
   579  			`var`,
   580  			nil,
   581  			`The "var" object cannot be accessed directly. Instead, access one of its attributes.`,
   582  		},
   583  		{
   584  			`var["foo"]`,
   585  			nil,
   586  			`The "var" object does not support this operation.`,
   587  		},
   588  
   589  		// anything else, interpreted as a managed resource reference
   590  		{
   591  			`boop_instance.foo`,
   592  			&Reference{
   593  				Subject: Resource{
   594  					Mode: ManagedResourceMode,
   595  					Type: "boop_instance",
   596  					Name: "foo",
   597  				},
   598  				SourceRange: tfdiags.SourceRange{
   599  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   600  					End:   tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
   601  				},
   602  			},
   603  			``,
   604  		},
   605  		{
   606  			`boop_instance.foo.bar`,
   607  			&Reference{
   608  				Subject: ResourceInstance{
   609  					Resource: Resource{
   610  						Mode: ManagedResourceMode,
   611  						Type: "boop_instance",
   612  						Name: "foo",
   613  					},
   614  				},
   615  				SourceRange: tfdiags.SourceRange{
   616  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   617  					End:   tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
   618  				},
   619  				Remaining: hcl.Traversal{
   620  					hcl.TraverseAttr{
   621  						Name: "bar",
   622  						SrcRange: hcl.Range{
   623  							Start: hcl.Pos{Line: 1, Column: 18, Byte: 17},
   624  							End:   hcl.Pos{Line: 1, Column: 22, Byte: 21},
   625  						},
   626  					},
   627  				},
   628  			},
   629  			``,
   630  		},
   631  		{
   632  			`boop_instance.foo["baz"].bar`,
   633  			&Reference{
   634  				Subject: ResourceInstance{
   635  					Resource: Resource{
   636  						Mode: ManagedResourceMode,
   637  						Type: "boop_instance",
   638  						Name: "foo",
   639  					},
   640  					Key: StringKey("baz"),
   641  				},
   642  				SourceRange: tfdiags.SourceRange{
   643  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   644  					End:   tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
   645  				},
   646  				Remaining: hcl.Traversal{
   647  					hcl.TraverseAttr{
   648  						Name: "bar",
   649  						SrcRange: hcl.Range{
   650  							Start: hcl.Pos{Line: 1, Column: 25, Byte: 24},
   651  							End:   hcl.Pos{Line: 1, Column: 29, Byte: 28},
   652  						},
   653  					},
   654  				},
   655  			},
   656  			``,
   657  		},
   658  		{
   659  			`boop_instance.foo["baz"]`,
   660  			&Reference{
   661  				Subject: ResourceInstance{
   662  					Resource: Resource{
   663  						Mode: ManagedResourceMode,
   664  						Type: "boop_instance",
   665  						Name: "foo",
   666  					},
   667  					Key: StringKey("baz"),
   668  				},
   669  				SourceRange: tfdiags.SourceRange{
   670  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   671  					End:   tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
   672  				},
   673  			},
   674  			``,
   675  		},
   676  		{
   677  			`boop_instance`,
   678  			nil,
   679  			`A reference to a resource type must be followed by at least one attribute access, specifying the resource name.`,
   680  		},
   681  	}
   682  
   683  	for _, test := range tests {
   684  		t.Run(test.Input, func(t *testing.T) {
   685  			traversal, travDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.Pos{Line: 1, Column: 1})
   686  			if travDiags.HasErrors() {
   687  				t.Fatal(travDiags.Error())
   688  			}
   689  
   690  			got, diags := ParseRef(traversal)
   691  
   692  			switch len(diags) {
   693  			case 0:
   694  				if test.WantErr != "" {
   695  					t.Fatalf("succeeded; want error: %s", test.WantErr)
   696  				}
   697  			case 1:
   698  				if test.WantErr == "" {
   699  					t.Fatalf("unexpected diagnostics: %s", diags.Err())
   700  				}
   701  				if got, want := diags[0].Description().Detail, test.WantErr; got != want {
   702  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
   703  				}
   704  			default:
   705  				t.Fatalf("too many diagnostics: %s", diags.Err())
   706  			}
   707  
   708  			if diags.HasErrors() {
   709  				return
   710  			}
   711  
   712  			for _, problem := range deep.Equal(got, test.Want) {
   713  				t.Errorf(problem)
   714  			}
   715  		})
   716  	}
   717  }