github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/hclext/structure_test.go (about)

     1  package hclext
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/google/go-cmp/cmp"
     7  	"github.com/google/go-cmp/cmp/cmpopts"
     8  	"github.com/hashicorp/hcl/v2"
     9  	"github.com/hashicorp/hcl/v2/hclsyntax"
    10  	"github.com/zclconf/go-cty/cty"
    11  )
    12  
    13  func TestContent_PartialContent(t *testing.T) {
    14  	tests := []struct {
    15  		Name      string
    16  		Body      *hclsyntax.Body
    17  		Schema    *BodySchema
    18  		Partial   bool
    19  		Want      *BodyContent
    20  		DiagCount int
    21  	}{
    22  		{
    23  			Name:      "nil body with nil schema",
    24  			Body:      nil,
    25  			Schema:    nil,
    26  			Partial:   false,
    27  			Want:      &BodyContent{},
    28  			DiagCount: 0,
    29  		},
    30  		{
    31  			Name:      "nil body with empty schema",
    32  			Body:      nil,
    33  			Schema:    &BodySchema{},
    34  			Partial:   false,
    35  			Want:      &BodyContent{},
    36  			DiagCount: 0,
    37  		},
    38  		{
    39  			Name:      "empty body with nil schema",
    40  			Body:      &hclsyntax.Body{},
    41  			Schema:    nil,
    42  			Partial:   false,
    43  			Want:      &BodyContent{Attributes: Attributes{}, Blocks: Blocks{}},
    44  			DiagCount: 0,
    45  		},
    46  		{
    47  			Name:      "empty body with empty schema",
    48  			Body:      &hclsyntax.Body{},
    49  			Schema:    &BodySchema{},
    50  			Partial:   false,
    51  			Want:      &BodyContent{Attributes: Attributes{}, Blocks: Blocks{}},
    52  			DiagCount: 0,
    53  		},
    54  		{
    55  			Name: "attributes",
    56  			Body: &hclsyntax.Body{
    57  				Attributes: hclsyntax.Attributes{
    58  					"foo": &hclsyntax.Attribute{
    59  						Name: "foo",
    60  					},
    61  				},
    62  			},
    63  			Schema: &BodySchema{
    64  				Attributes: []AttributeSchema{
    65  					{
    66  						Name: "foo",
    67  					},
    68  				},
    69  			},
    70  			Partial: false,
    71  			Want: &BodyContent{
    72  				Attributes: Attributes{"foo": &Attribute{Name: "foo"}},
    73  				Blocks:     Blocks{},
    74  			},
    75  			DiagCount: 0,
    76  		},
    77  		{
    78  			Name: "attributes with empty schema",
    79  			Body: &hclsyntax.Body{
    80  				Attributes: hclsyntax.Attributes{
    81  					"foo": &hclsyntax.Attribute{
    82  						Name: "foo",
    83  					},
    84  				},
    85  			},
    86  			Schema:  &BodySchema{},
    87  			Partial: false,
    88  			Want: &BodyContent{
    89  				Attributes: Attributes{},
    90  				Blocks:     Blocks{},
    91  			},
    92  			DiagCount: 1, // extra attribute is not allowed
    93  		},
    94  		{
    95  			Name: "attributes with partial empty schema",
    96  			Body: &hclsyntax.Body{
    97  				Attributes: hclsyntax.Attributes{
    98  					"foo": &hclsyntax.Attribute{
    99  						Name: "foo",
   100  					},
   101  				},
   102  			},
   103  			Schema:  &BodySchema{},
   104  			Partial: true,
   105  			Want: &BodyContent{
   106  				Attributes: Attributes{},
   107  				Blocks:     Blocks{},
   108  			},
   109  			DiagCount: 0, // extra attribute is allowed in partial schema
   110  		},
   111  		{
   112  			Name: "empty body with attribute schema",
   113  			Body: &hclsyntax.Body{
   114  				Attributes: hclsyntax.Attributes{},
   115  			},
   116  			Schema: &BodySchema{
   117  				Attributes: []AttributeSchema{
   118  					{
   119  						Name: "foo",
   120  					},
   121  				},
   122  			},
   123  			Partial: false,
   124  			Want: &BodyContent{
   125  				Attributes: Attributes{},
   126  				Blocks:     Blocks{},
   127  			},
   128  			DiagCount: 0, // attribute is not required by default
   129  		},
   130  		{
   131  			Name: "empty body with required attribute schema",
   132  			Body: &hclsyntax.Body{
   133  				Attributes: hclsyntax.Attributes{},
   134  			},
   135  			Schema: &BodySchema{
   136  				Attributes: []AttributeSchema{
   137  					{
   138  						Name:     "foo",
   139  						Required: true,
   140  					},
   141  				},
   142  			},
   143  			Partial: false,
   144  			Want: &BodyContent{
   145  				Attributes: Attributes{},
   146  				Blocks:     Blocks{},
   147  			},
   148  			DiagCount: 1, // attribute is required
   149  		},
   150  		{
   151  			Name: "attributes with block schema",
   152  			Body: &hclsyntax.Body{
   153  				Attributes: hclsyntax.Attributes{
   154  					"foo": &hclsyntax.Attribute{
   155  						Name: "foo",
   156  					},
   157  				},
   158  			},
   159  			Schema: &BodySchema{
   160  				Blocks: []BlockSchema{
   161  					{
   162  						Type: "foo",
   163  					},
   164  				},
   165  			},
   166  			Partial: false,
   167  			Want: &BodyContent{
   168  				Attributes: Attributes{},
   169  				Blocks:     Blocks{},
   170  			},
   171  			DiagCount: 1, // "foo" is defined as attribute, but should be defined as block
   172  		},
   173  		{
   174  			Name: "blocks",
   175  			Body: &hclsyntax.Body{
   176  				Blocks: hclsyntax.Blocks{
   177  					&hclsyntax.Block{
   178  						Type: "foo",
   179  					},
   180  				},
   181  			},
   182  			Schema: &BodySchema{
   183  				Blocks: []BlockSchema{
   184  					{
   185  						Type: "foo",
   186  					},
   187  				},
   188  			},
   189  			Partial: false,
   190  			Want: &BodyContent{
   191  				Attributes: Attributes{},
   192  				Blocks: Blocks{
   193  					{
   194  						Type: "foo",
   195  						Body: &BodyContent{},
   196  					},
   197  				},
   198  			},
   199  			DiagCount: 0,
   200  		},
   201  		{
   202  			Name: "multiple blocks",
   203  			Body: &hclsyntax.Body{
   204  				Blocks: hclsyntax.Blocks{
   205  					&hclsyntax.Block{
   206  						Type: "foo",
   207  					},
   208  					&hclsyntax.Block{
   209  						Type: "foo",
   210  					},
   211  				},
   212  			},
   213  			Schema: &BodySchema{
   214  				Blocks: []BlockSchema{
   215  					{
   216  						Type: "foo",
   217  					},
   218  				},
   219  			},
   220  			Partial: false,
   221  			Want: &BodyContent{
   222  				Attributes: Attributes{},
   223  				Blocks: Blocks{
   224  					{
   225  						Type: "foo",
   226  						Body: &BodyContent{},
   227  					},
   228  					{
   229  						Type: "foo",
   230  						Body: &BodyContent{},
   231  					},
   232  				},
   233  			},
   234  			DiagCount: 0,
   235  		},
   236  		{
   237  			Name: "multiple blocks which including unexpected schema",
   238  			Body: &hclsyntax.Body{
   239  				Blocks: hclsyntax.Blocks{
   240  					&hclsyntax.Block{
   241  						Type: "foo",
   242  					},
   243  					&hclsyntax.Block{
   244  						Type: "bar",
   245  					},
   246  				},
   247  			},
   248  			Schema: &BodySchema{
   249  				Blocks: []BlockSchema{
   250  					{
   251  						Type: "foo",
   252  					},
   253  				},
   254  			},
   255  			Partial: false,
   256  			Want: &BodyContent{
   257  				Attributes: Attributes{},
   258  				Blocks: Blocks{
   259  					{
   260  						Type: "foo",
   261  						Body: &BodyContent{},
   262  					},
   263  				},
   264  			},
   265  			DiagCount: 1, // "bar" is not expected
   266  		},
   267  		{
   268  			Name: "multiple blocks which including unexpected schema with partial schema",
   269  			Body: &hclsyntax.Body{
   270  				Blocks: hclsyntax.Blocks{
   271  					&hclsyntax.Block{
   272  						Type: "foo",
   273  					},
   274  					&hclsyntax.Block{
   275  						Type: "bar",
   276  					},
   277  				},
   278  			},
   279  			Schema: &BodySchema{
   280  				Blocks: []BlockSchema{
   281  					{
   282  						Type: "foo",
   283  					},
   284  				},
   285  			},
   286  			Partial: true,
   287  			Want: &BodyContent{
   288  				Attributes: Attributes{},
   289  				Blocks: Blocks{
   290  					{
   291  						Type: "foo",
   292  						Body: &BodyContent{},
   293  					},
   294  				},
   295  			},
   296  			DiagCount: 0, // extra schema block is allowed in partial schema
   297  		},
   298  		{
   299  			Name: "labeled block",
   300  			Body: &hclsyntax.Body{
   301  				Blocks: hclsyntax.Blocks{
   302  					&hclsyntax.Block{
   303  						Type:   "foo",
   304  						Labels: []string{"bar"},
   305  					},
   306  				},
   307  			},
   308  			Schema: &BodySchema{
   309  				Blocks: []BlockSchema{
   310  					{
   311  						Type:       "foo",
   312  						LabelNames: []string{"name"},
   313  					},
   314  				},
   315  			},
   316  			Partial: false,
   317  			Want: &BodyContent{
   318  				Attributes: Attributes{},
   319  				Blocks: Blocks{
   320  					{
   321  						Type:   "foo",
   322  						Labels: []string{"bar"},
   323  						Body:   &BodyContent{},
   324  					},
   325  				},
   326  			},
   327  			DiagCount: 0,
   328  		},
   329  		{
   330  			Name: "non-labeled block with labeled block schema",
   331  			Body: &hclsyntax.Body{
   332  				Blocks: hclsyntax.Blocks{
   333  					&hclsyntax.Block{
   334  						Type: "foo",
   335  					},
   336  				},
   337  			},
   338  			Schema: &BodySchema{
   339  				Blocks: []BlockSchema{
   340  					{
   341  						Type:       "foo",
   342  						LabelNames: []string{"name"},
   343  					},
   344  				},
   345  			},
   346  			Partial: false,
   347  			Want: &BodyContent{
   348  				Attributes: Attributes{},
   349  				Blocks:     Blocks{},
   350  			},
   351  			DiagCount: 1, // missing label is not allowed
   352  		},
   353  		{
   354  			Name: "labeled block with non-labeled block schema",
   355  			Body: &hclsyntax.Body{
   356  				Blocks: hclsyntax.Blocks{
   357  					&hclsyntax.Block{
   358  						Type:        "foo",
   359  						Labels:      []string{"bar"},
   360  						LabelRanges: []hcl.Range{{}},
   361  					},
   362  				},
   363  			},
   364  			Schema: &BodySchema{
   365  				Blocks: []BlockSchema{
   366  					{
   367  						Type: "foo",
   368  					},
   369  				},
   370  			},
   371  			Partial: false,
   372  			Want: &BodyContent{
   373  				Attributes: Attributes{},
   374  				Blocks:     Blocks{},
   375  			},
   376  			DiagCount: 1, // extraneous label is not allowed
   377  		},
   378  		{
   379  			Name: "multi-labeled block with single-labeled block schema",
   380  			Body: &hclsyntax.Body{
   381  				Blocks: hclsyntax.Blocks{
   382  					&hclsyntax.Block{
   383  						Type:        "foo",
   384  						Labels:      []string{"bar", "baz"},
   385  						LabelRanges: []hcl.Range{{}, {}},
   386  					},
   387  				},
   388  			},
   389  			Schema: &BodySchema{
   390  				Blocks: []BlockSchema{
   391  					{
   392  						Type:       "foo",
   393  						LabelNames: []string{"name"},
   394  					},
   395  				},
   396  			},
   397  			Partial: false,
   398  			Want: &BodyContent{
   399  				Attributes: Attributes{},
   400  				Blocks:     Blocks{},
   401  			},
   402  			DiagCount: 1, // extraneous label is not allowed
   403  		},
   404  		{
   405  			Name: "block with attribute schema",
   406  			Body: &hclsyntax.Body{
   407  				Blocks: hclsyntax.Blocks{
   408  					&hclsyntax.Block{
   409  						Type: "foo",
   410  					},
   411  				},
   412  			},
   413  			Schema: &BodySchema{
   414  				Attributes: []AttributeSchema{
   415  					{
   416  						Name: "foo",
   417  					},
   418  				},
   419  			},
   420  			Partial: false,
   421  			Want: &BodyContent{
   422  				Attributes: Attributes{},
   423  				Blocks:     Blocks{},
   424  			},
   425  			DiagCount: 1, // "foo" is defined as block, but should be defined as attribute
   426  		},
   427  		{
   428  			Name: "nested blocks",
   429  			Body: &hclsyntax.Body{
   430  				Attributes: hclsyntax.Attributes{
   431  					"foo": &hclsyntax.Attribute{Name: "foo"},
   432  				},
   433  				Blocks: hclsyntax.Blocks{
   434  					&hclsyntax.Block{
   435  						Type: "bar",
   436  						Body: &hclsyntax.Body{
   437  							Attributes: hclsyntax.Attributes{
   438  								"baz": &hclsyntax.Attribute{Name: "baz"},
   439  							},
   440  						},
   441  					},
   442  				},
   443  			},
   444  			Schema: &BodySchema{
   445  				Attributes: []AttributeSchema{
   446  					{
   447  						Name: "foo",
   448  					},
   449  				},
   450  				Blocks: []BlockSchema{
   451  					{
   452  						Type: "bar",
   453  						Body: &BodySchema{
   454  							Attributes: []AttributeSchema{
   455  								{
   456  									Name: "baz",
   457  								},
   458  							},
   459  						},
   460  					},
   461  				},
   462  			},
   463  			Partial: false,
   464  			Want: &BodyContent{
   465  				Attributes: Attributes{
   466  					"foo": &Attribute{Name: "foo"},
   467  				},
   468  				Blocks: Blocks{
   469  					{
   470  						Type: "bar",
   471  						Body: &BodyContent{
   472  							Attributes: Attributes{
   473  								"baz": &Attribute{Name: "baz"},
   474  							},
   475  							Blocks: Blocks{},
   476  						},
   477  					},
   478  				},
   479  			},
   480  			DiagCount: 0,
   481  		},
   482  		{
   483  			Name: "attributes with empty schema in nested blocks",
   484  			Body: &hclsyntax.Body{
   485  				Attributes: hclsyntax.Attributes{
   486  					"foo": &hclsyntax.Attribute{Name: "foo"},
   487  				},
   488  				Blocks: hclsyntax.Blocks{
   489  					&hclsyntax.Block{
   490  						Type: "bar",
   491  						Body: &hclsyntax.Body{
   492  							Attributes: hclsyntax.Attributes{
   493  								"baz": &hclsyntax.Attribute{Name: "baz"},
   494  							},
   495  						},
   496  					},
   497  				},
   498  			},
   499  			Schema: &BodySchema{
   500  				Attributes: []AttributeSchema{
   501  					{
   502  						Name: "foo",
   503  					},
   504  				},
   505  				Blocks: []BlockSchema{
   506  					{
   507  						Type: "bar",
   508  						Body: &BodySchema{},
   509  					},
   510  				},
   511  			},
   512  			Partial: false,
   513  			Want: &BodyContent{
   514  				Attributes: Attributes{
   515  					"foo": &Attribute{Name: "foo"},
   516  				},
   517  				Blocks: Blocks{
   518  					{
   519  						Type: "bar",
   520  						Body: &BodyContent{
   521  							Attributes: Attributes{},
   522  							Blocks:     Blocks{},
   523  						},
   524  					},
   525  				},
   526  			},
   527  			DiagCount: 1, // extra attribute in nested blocks is not allowed
   528  		},
   529  		{
   530  			Name: "attributes with partial empty schema in nested blocks",
   531  			Body: &hclsyntax.Body{
   532  				Attributes: hclsyntax.Attributes{
   533  					"foo": &hclsyntax.Attribute{Name: "foo"},
   534  				},
   535  				Blocks: hclsyntax.Blocks{
   536  					&hclsyntax.Block{
   537  						Type: "bar",
   538  						Body: &hclsyntax.Body{
   539  							Attributes: hclsyntax.Attributes{
   540  								"baz": &hclsyntax.Attribute{Name: "baz"},
   541  							},
   542  						},
   543  					},
   544  				},
   545  			},
   546  			Schema: &BodySchema{
   547  				Attributes: []AttributeSchema{
   548  					{
   549  						Name: "foo",
   550  					},
   551  				},
   552  				Blocks: []BlockSchema{
   553  					{
   554  						Type: "bar",
   555  						Body: &BodySchema{},
   556  					},
   557  				},
   558  			},
   559  			Partial: true,
   560  			Want: &BodyContent{
   561  				Attributes: Attributes{
   562  					"foo": &Attribute{Name: "foo"},
   563  				},
   564  				Blocks: Blocks{
   565  					{
   566  						Type: "bar",
   567  						Body: &BodyContent{
   568  							Attributes: Attributes{},
   569  							Blocks:     Blocks{},
   570  						},
   571  					},
   572  				},
   573  			},
   574  			DiagCount: 0, // extra attribute in nested blocks is allowed in partial schema
   575  		},
   576  		{
   577  			Name: "empty body with attribute schema in nested blocks",
   578  			Body: &hclsyntax.Body{
   579  				Attributes: hclsyntax.Attributes{
   580  					"foo": &hclsyntax.Attribute{Name: "foo"},
   581  				},
   582  				Blocks: hclsyntax.Blocks{
   583  					&hclsyntax.Block{
   584  						Type: "bar",
   585  						Body: &hclsyntax.Body{},
   586  					},
   587  				},
   588  			},
   589  			Schema: &BodySchema{
   590  				Attributes: []AttributeSchema{
   591  					{
   592  						Name: "foo",
   593  					},
   594  				},
   595  				Blocks: []BlockSchema{
   596  					{
   597  						Type: "bar",
   598  						Body: &BodySchema{
   599  							Attributes: []AttributeSchema{
   600  								{
   601  									Name: "baz",
   602  								},
   603  							},
   604  						},
   605  					},
   606  				},
   607  			},
   608  			Partial: true,
   609  			Want: &BodyContent{
   610  				Attributes: Attributes{
   611  					"foo": &Attribute{Name: "foo"},
   612  				},
   613  				Blocks: Blocks{
   614  					{
   615  						Type: "bar",
   616  						Body: &BodyContent{
   617  							Attributes: Attributes{},
   618  							Blocks:     Blocks{},
   619  						},
   620  					},
   621  				},
   622  			},
   623  			DiagCount: 0, // attribute in nested blocks is not required by default
   624  		},
   625  		{
   626  			Name: "empty body with required attribute schema in nested blocks",
   627  			Body: &hclsyntax.Body{
   628  				Attributes: hclsyntax.Attributes{
   629  					"foo": &hclsyntax.Attribute{Name: "foo"},
   630  				},
   631  				Blocks: hclsyntax.Blocks{
   632  					&hclsyntax.Block{
   633  						Type: "bar",
   634  						Body: &hclsyntax.Body{},
   635  					},
   636  				},
   637  			},
   638  			Schema: &BodySchema{
   639  				Attributes: []AttributeSchema{
   640  					{
   641  						Name: "foo",
   642  					},
   643  				},
   644  				Blocks: []BlockSchema{
   645  					{
   646  						Type: "bar",
   647  						Body: &BodySchema{
   648  							Attributes: []AttributeSchema{
   649  								{
   650  									Name:     "baz",
   651  									Required: true,
   652  								},
   653  							},
   654  						},
   655  					},
   656  				},
   657  			},
   658  			Partial: true,
   659  			Want: &BodyContent{
   660  				Attributes: Attributes{
   661  					"foo": &Attribute{Name: "foo"},
   662  				},
   663  				Blocks: Blocks{
   664  					{
   665  						Type: "bar",
   666  						Body: &BodyContent{
   667  							Attributes: Attributes{},
   668  							Blocks:     Blocks{},
   669  						},
   670  					},
   671  				},
   672  			},
   673  			DiagCount: 1, // attribute in nested blocks is required
   674  		},
   675  	}
   676  
   677  	for _, test := range tests {
   678  		t.Run(test.Name, func(t *testing.T) {
   679  			var got *BodyContent
   680  			var diags hcl.Diagnostics
   681  			if test.Partial {
   682  				got, diags = PartialContent(test.Body, test.Schema)
   683  			} else {
   684  				got, diags = Content(test.Body, test.Schema)
   685  			}
   686  
   687  			if len(diags) != test.DiagCount {
   688  				t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
   689  				for _, diag := range diags {
   690  					t.Logf(" - %s", diag.Error())
   691  				}
   692  			}
   693  
   694  			if diff := cmp.Diff(test.Want, got); diff != "" {
   695  				t.Errorf("wrong result\ndiff: %s", diff)
   696  			}
   697  		})
   698  	}
   699  }
   700  
   701  func TestContent_JustAttributes(t *testing.T) {
   702  	tests := []struct {
   703  		Name      string
   704  		Body      *hclsyntax.Body
   705  		Schema    *BodySchema
   706  		Partial   bool
   707  		Want      *BodyContent
   708  		DiagCount int
   709  	}{
   710  		{
   711  			Name: "just attributes in the top level",
   712  			Body: &hclsyntax.Body{
   713  				Attributes: hclsyntax.Attributes{
   714  					"foo": &hclsyntax.Attribute{Name: "foo"},
   715  					"bar": &hclsyntax.Attribute{Name: "bar"},
   716  					"baz": &hclsyntax.Attribute{Name: "baz"},
   717  				},
   718  			},
   719  			Schema: &BodySchema{Mode: SchemaJustAttributesMode},
   720  			Want: &BodyContent{
   721  				Attributes: Attributes{
   722  					"foo": &Attribute{Name: "foo"},
   723  					"bar": &Attribute{Name: "bar"},
   724  					"baz": &Attribute{Name: "baz"},
   725  				},
   726  				Blocks: Blocks{},
   727  			},
   728  		},
   729  		{
   730  			Name: "just attributes in nested blocks",
   731  			Body: &hclsyntax.Body{
   732  				Blocks: hclsyntax.Blocks{
   733  					&hclsyntax.Block{
   734  						Type: "bar",
   735  						Body: &hclsyntax.Body{
   736  							Attributes: hclsyntax.Attributes{
   737  								"foo": &hclsyntax.Attribute{Name: "foo"},
   738  								"bar": &hclsyntax.Attribute{Name: "bar"},
   739  								"baz": &hclsyntax.Attribute{Name: "baz"},
   740  							},
   741  						},
   742  					},
   743  				},
   744  			},
   745  			Schema: &BodySchema{
   746  				Blocks: []BlockSchema{
   747  					{
   748  						Type: "bar",
   749  						Body: &BodySchema{Mode: SchemaJustAttributesMode},
   750  					},
   751  				},
   752  			},
   753  			Want: &BodyContent{
   754  				Attributes: Attributes{},
   755  				Blocks: Blocks{
   756  					{
   757  						Type: "bar",
   758  						Body: &BodyContent{
   759  							Attributes: Attributes{
   760  								"foo": &Attribute{Name: "foo"},
   761  								"bar": &Attribute{Name: "bar"},
   762  								"baz": &Attribute{Name: "baz"},
   763  							},
   764  							Blocks: Blocks{},
   765  						},
   766  					},
   767  				},
   768  			},
   769  		},
   770  		{
   771  			Name: "just attributes in body with blocks",
   772  			Body: &hclsyntax.Body{
   773  				Attributes: hclsyntax.Attributes{
   774  					"foo": &hclsyntax.Attribute{Name: "foo"},
   775  					"bar": &hclsyntax.Attribute{Name: "bar"},
   776  					"baz": &hclsyntax.Attribute{Name: "baz"},
   777  				},
   778  				Blocks: hclsyntax.Blocks{
   779  					&hclsyntax.Block{
   780  						Type: "bar",
   781  						Body: &hclsyntax.Body{},
   782  					},
   783  				},
   784  			},
   785  			Schema: &BodySchema{Mode: SchemaJustAttributesMode},
   786  			Want: &BodyContent{
   787  				Attributes: Attributes{
   788  					"foo": &Attribute{Name: "foo"},
   789  					"bar": &Attribute{Name: "bar"},
   790  					"baz": &Attribute{Name: "baz"},
   791  				},
   792  				Blocks: Blocks{},
   793  			},
   794  			DiagCount: 1, // Unexpected "bar" block; Blocks are not allowed here.
   795  		},
   796  	}
   797  
   798  	for _, test := range tests {
   799  		t.Run(test.Name, func(t *testing.T) {
   800  			var got *BodyContent
   801  			var diags hcl.Diagnostics
   802  			if test.Partial {
   803  				got, diags = PartialContent(test.Body, test.Schema)
   804  			} else {
   805  				got, diags = Content(test.Body, test.Schema)
   806  			}
   807  
   808  			if len(diags) != test.DiagCount {
   809  				t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
   810  				for _, diag := range diags {
   811  					t.Logf(" - %s", diag.Error())
   812  				}
   813  			}
   814  
   815  			if diff := cmp.Diff(test.Want, got); diff != "" {
   816  				t.Errorf("wrong result\ndiff: %s", diff)
   817  			}
   818  		})
   819  	}
   820  }
   821  
   822  func Test_IsEmpty(t *testing.T) {
   823  	tests := []struct {
   824  		name string
   825  		body *BodyContent
   826  		want bool
   827  	}{
   828  		{
   829  			name: "body is not empty",
   830  			body: &BodyContent{
   831  				Attributes: Attributes{
   832  					"foo": &Attribute{Name: "foo"},
   833  				},
   834  			},
   835  			want: false,
   836  		},
   837  		{
   838  			name: "body has empty attributes and empty blocks",
   839  			body: &BodyContent{Attributes: Attributes{}, Blocks: Blocks{}},
   840  			want: true,
   841  		},
   842  		{
   843  			name: "body has nil attributes and nil blocks",
   844  			body: &BodyContent{},
   845  			want: true,
   846  		},
   847  		{
   848  			name: "body is nil",
   849  			body: nil,
   850  			want: true,
   851  		},
   852  	}
   853  
   854  	for _, test := range tests {
   855  		t.Run(test.name, func(t *testing.T) {
   856  			if test.body.IsEmpty() != test.want {
   857  				t.Errorf("%t is expected, but got %t", test.want, test.body.IsEmpty())
   858  			}
   859  		})
   860  	}
   861  }
   862  
   863  func TestCopy_BodyContent(t *testing.T) {
   864  	body := &BodyContent{
   865  		Attributes: Attributes{
   866  			"foo": {Name: "foo"},
   867  		},
   868  		Blocks: Blocks{
   869  			{
   870  				Body: &BodyContent{
   871  					Attributes: Attributes{
   872  						"bar": {Name: "bar"},
   873  					},
   874  					Blocks: Blocks{
   875  						{
   876  							Body: &BodyContent{
   877  								Attributes: Attributes{
   878  									"baz": {Name: "baz"},
   879  								},
   880  							},
   881  						},
   882  					},
   883  				},
   884  			},
   885  			{
   886  				Body: &BodyContent{
   887  					Attributes: Attributes{
   888  						"aaa": {Name: "aaa"},
   889  						"bbb": {Name: "bbb"},
   890  					},
   891  				},
   892  			},
   893  		},
   894  	}
   895  
   896  	if diff := cmp.Diff(body.Copy(), body); diff != "" {
   897  		t.Error(diff)
   898  	}
   899  }
   900  
   901  func TestWalkAttributes(t *testing.T) {
   902  	body := &BodyContent{
   903  		Attributes: Attributes{
   904  			"foo": {Name: "foo"},
   905  		},
   906  		Blocks: Blocks{
   907  			{
   908  				Body: &BodyContent{
   909  					Attributes: Attributes{
   910  						"bar": {Name: "bar"},
   911  					},
   912  					Blocks: Blocks{
   913  						{
   914  							Body: &BodyContent{
   915  								Attributes: Attributes{
   916  									"baz": {Name: "baz"},
   917  								},
   918  							},
   919  						},
   920  					},
   921  				},
   922  			},
   923  			{
   924  				Body: &BodyContent{
   925  					Attributes: Attributes{
   926  						"aaa": {Name: "aaa"},
   927  						"bbb": {Name: "bbb"},
   928  					},
   929  				},
   930  			},
   931  		},
   932  	}
   933  
   934  	got := []string{}
   935  	diags := body.WalkAttributes(func(a *Attribute) hcl.Diagnostics {
   936  		got = append(got, a.Name)
   937  		return nil
   938  	})
   939  	if diags.HasErrors() {
   940  		t.Fatal(diags)
   941  	}
   942  
   943  	want := []string{"foo", "bar", "baz", "aaa", "bbb"}
   944  
   945  	opt := cmpopts.SortSlices(func(x, y string) bool { return x < y })
   946  	if diff := cmp.Diff(got, want, opt); diff != "" {
   947  		t.Error(diff)
   948  	}
   949  }
   950  
   951  func TestCopy_Attribute(t *testing.T) {
   952  	attribute := &Attribute{
   953  		Name:      "foo",
   954  		Expr:      hcl.StaticExpr(cty.StringVal("foo"), hcl.Range{}),
   955  		Range:     hcl.Range{Start: hcl.Pos{Line: 2}},
   956  		NameRange: hcl.Range{Start: hcl.Pos{Line: 1}},
   957  	}
   958  
   959  	must := func(v cty.Value, diags hcl.Diagnostics) cty.Value {
   960  		if diags.HasErrors() {
   961  			t.Fatal(diags)
   962  		}
   963  		return v
   964  	}
   965  	opts := cmp.Options{
   966  		cmp.Comparer(func(x, y hcl.Expression) bool {
   967  			return must(x.Value(nil)) == must(y.Value(nil))
   968  		}),
   969  	}
   970  	if diff := cmp.Diff(attribute.Copy(), attribute, opts); diff != "" {
   971  		t.Error(diff)
   972  	}
   973  }
   974  
   975  func TestCopy_Block(t *testing.T) {
   976  	block := &Block{
   977  		Type:   "foo",
   978  		Labels: []string{"bar", "baz"},
   979  		Body: &BodyContent{
   980  			Attributes: Attributes{
   981  				"foo": {Name: "foo"},
   982  			},
   983  			Blocks: Blocks{},
   984  		},
   985  		DefRange:  hcl.Range{Start: hcl.Pos{Line: 1}},
   986  		TypeRange: hcl.Range{Start: hcl.Pos{Line: 2}},
   987  		LabelRanges: []hcl.Range{
   988  			{Start: hcl.Pos{Line: 3}},
   989  			{Start: hcl.Pos{Line: 4}},
   990  		},
   991  	}
   992  
   993  	if diff := cmp.Diff(block.Copy(), block); diff != "" {
   994  		t.Error(diff)
   995  	}
   996  }