github.com/hashicorp/hcl/v2@v2.20.0/json/structure_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package json
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/davecgh/go-spew/spew"
    13  	"github.com/go-test/deep"
    14  	"github.com/hashicorp/hcl/v2"
    15  	"github.com/zclconf/go-cty/cty"
    16  )
    17  
    18  func TestBodyPartialContent(t *testing.T) {
    19  	tests := []struct {
    20  		src       string
    21  		schema    *hcl.BodySchema
    22  		want      *hcl.BodyContent
    23  		diagCount int
    24  	}{
    25  		{
    26  			`{}`,
    27  			&hcl.BodySchema{},
    28  			&hcl.BodyContent{
    29  				Attributes: map[string]*hcl.Attribute{},
    30  				MissingItemRange: hcl.Range{
    31  					Filename: "test.json",
    32  					Start:    hcl.Pos{Line: 1, Column: 2, Byte: 1},
    33  					End:      hcl.Pos{Line: 1, Column: 3, Byte: 2},
    34  				},
    35  			},
    36  			0,
    37  		},
    38  		{
    39  			`[]`,
    40  			&hcl.BodySchema{},
    41  			&hcl.BodyContent{
    42  				Attributes: map[string]*hcl.Attribute{},
    43  				MissingItemRange: hcl.Range{
    44  					Filename: "test.json",
    45  					Start:    hcl.Pos{Line: 1, Column: 1, Byte: 0},
    46  					End:      hcl.Pos{Line: 1, Column: 2, Byte: 1},
    47  				},
    48  			},
    49  			0,
    50  		},
    51  		{
    52  			`[{}]`,
    53  			&hcl.BodySchema{},
    54  			&hcl.BodyContent{
    55  				Attributes: map[string]*hcl.Attribute{},
    56  				MissingItemRange: hcl.Range{
    57  					Filename: "test.json",
    58  					Start:    hcl.Pos{Line: 1, Column: 1, Byte: 0},
    59  					End:      hcl.Pos{Line: 1, Column: 2, Byte: 1},
    60  				},
    61  			},
    62  			0,
    63  		},
    64  		{
    65  			`[[]]`,
    66  			&hcl.BodySchema{},
    67  			&hcl.BodyContent{
    68  				Attributes: map[string]*hcl.Attribute{},
    69  				MissingItemRange: hcl.Range{
    70  					Filename: "test.json",
    71  					Start:    hcl.Pos{Line: 1, Column: 1, Byte: 0},
    72  					End:      hcl.Pos{Line: 1, Column: 2, Byte: 1},
    73  				},
    74  			},
    75  			1, // elements of root array must be objects
    76  		},
    77  		{
    78  			`{"//": "comment that should be ignored"}`,
    79  			&hcl.BodySchema{},
    80  			&hcl.BodyContent{
    81  				Attributes: map[string]*hcl.Attribute{},
    82  				MissingItemRange: hcl.Range{
    83  					Filename: "test.json",
    84  					Start:    hcl.Pos{Line: 1, Column: 40, Byte: 39},
    85  					End:      hcl.Pos{Line: 1, Column: 41, Byte: 40},
    86  				},
    87  			},
    88  			0,
    89  		},
    90  		{
    91  			`{"//": "comment that should be ignored", "//": "another comment"}`,
    92  			&hcl.BodySchema{},
    93  			&hcl.BodyContent{
    94  				Attributes: map[string]*hcl.Attribute{},
    95  				MissingItemRange: hcl.Range{
    96  					Filename: "test.json",
    97  					Start:    hcl.Pos{Line: 1, Column: 65, Byte: 64},
    98  					End:      hcl.Pos{Line: 1, Column: 66, Byte: 65},
    99  				},
   100  			},
   101  			0,
   102  		},
   103  		{
   104  			`{"name":"Ermintrude"}`,
   105  			&hcl.BodySchema{
   106  				Attributes: []hcl.AttributeSchema{
   107  					{
   108  						Name: "name",
   109  					},
   110  				},
   111  			},
   112  			&hcl.BodyContent{
   113  				Attributes: map[string]*hcl.Attribute{
   114  					"name": &hcl.Attribute{
   115  						Name: "name",
   116  						Expr: &expression{
   117  							src: &stringVal{
   118  								Value: "Ermintrude",
   119  								SrcRange: hcl.Range{
   120  									Filename: "test.json",
   121  									Start: hcl.Pos{
   122  										Byte:   8,
   123  										Line:   1,
   124  										Column: 9,
   125  									},
   126  									End: hcl.Pos{
   127  										Byte:   20,
   128  										Line:   1,
   129  										Column: 21,
   130  									},
   131  								},
   132  							},
   133  						},
   134  						Range: hcl.Range{
   135  							Filename: "test.json",
   136  							Start: hcl.Pos{
   137  								Byte:   1,
   138  								Line:   1,
   139  								Column: 2,
   140  							},
   141  							End: hcl.Pos{
   142  								Byte:   20,
   143  								Line:   1,
   144  								Column: 21,
   145  							},
   146  						},
   147  						NameRange: hcl.Range{
   148  							Filename: "test.json",
   149  							Start: hcl.Pos{
   150  								Byte:   1,
   151  								Line:   1,
   152  								Column: 2,
   153  							},
   154  							End: hcl.Pos{
   155  								Byte:   7,
   156  								Line:   1,
   157  								Column: 8,
   158  							},
   159  						},
   160  					},
   161  				},
   162  				MissingItemRange: hcl.Range{
   163  					Filename: "test.json",
   164  					Start:    hcl.Pos{Line: 1, Column: 21, Byte: 20},
   165  					End:      hcl.Pos{Line: 1, Column: 22, Byte: 21},
   166  				},
   167  			},
   168  			0,
   169  		},
   170  		{
   171  			`[{"name":"Ermintrude"}]`,
   172  			&hcl.BodySchema{
   173  				Attributes: []hcl.AttributeSchema{
   174  					{
   175  						Name: "name",
   176  					},
   177  				},
   178  			},
   179  			&hcl.BodyContent{
   180  				Attributes: map[string]*hcl.Attribute{
   181  					"name": &hcl.Attribute{
   182  						Name: "name",
   183  						Expr: &expression{
   184  							src: &stringVal{
   185  								Value: "Ermintrude",
   186  								SrcRange: hcl.Range{
   187  									Filename: "test.json",
   188  									Start: hcl.Pos{
   189  										Byte:   9,
   190  										Line:   1,
   191  										Column: 10,
   192  									},
   193  									End: hcl.Pos{
   194  										Byte:   21,
   195  										Line:   1,
   196  										Column: 22,
   197  									},
   198  								},
   199  							},
   200  						},
   201  						Range: hcl.Range{
   202  							Filename: "test.json",
   203  							Start: hcl.Pos{
   204  								Byte:   2,
   205  								Line:   1,
   206  								Column: 3,
   207  							},
   208  							End: hcl.Pos{
   209  								Byte:   21,
   210  								Line:   1,
   211  								Column: 22,
   212  							},
   213  						},
   214  						NameRange: hcl.Range{
   215  							Filename: "test.json",
   216  							Start: hcl.Pos{
   217  								Byte:   2,
   218  								Line:   1,
   219  								Column: 3,
   220  							},
   221  							End: hcl.Pos{
   222  								Byte:   8,
   223  								Line:   1,
   224  								Column: 9,
   225  							},
   226  						},
   227  					},
   228  				},
   229  				MissingItemRange: hcl.Range{
   230  					Filename: "test.json",
   231  					Start:    hcl.Pos{Line: 1, Column: 1, Byte: 0},
   232  					End:      hcl.Pos{Line: 1, Column: 2, Byte: 1},
   233  				},
   234  			},
   235  			0,
   236  		},
   237  		{
   238  			`{"name":"Ermintrude"}`,
   239  			&hcl.BodySchema{
   240  				Attributes: []hcl.AttributeSchema{
   241  					{
   242  						Name:     "name",
   243  						Required: true,
   244  					},
   245  					{
   246  						Name:     "age",
   247  						Required: true,
   248  					},
   249  				},
   250  			},
   251  			&hcl.BodyContent{
   252  				Attributes: map[string]*hcl.Attribute{
   253  					"name": &hcl.Attribute{
   254  						Name: "name",
   255  						Expr: &expression{
   256  							src: &stringVal{
   257  								Value: "Ermintrude",
   258  								SrcRange: hcl.Range{
   259  									Filename: "test.json",
   260  									Start: hcl.Pos{
   261  										Byte:   8,
   262  										Line:   1,
   263  										Column: 9,
   264  									},
   265  									End: hcl.Pos{
   266  										Byte:   20,
   267  										Line:   1,
   268  										Column: 21,
   269  									},
   270  								},
   271  							},
   272  						},
   273  						Range: hcl.Range{
   274  							Filename: "test.json",
   275  							Start: hcl.Pos{
   276  								Byte:   1,
   277  								Line:   1,
   278  								Column: 2,
   279  							},
   280  							End: hcl.Pos{
   281  								Byte:   20,
   282  								Line:   1,
   283  								Column: 21,
   284  							},
   285  						},
   286  						NameRange: hcl.Range{
   287  							Filename: "test.json",
   288  							Start: hcl.Pos{
   289  								Byte:   1,
   290  								Line:   1,
   291  								Column: 2,
   292  							},
   293  							End: hcl.Pos{
   294  								Byte:   7,
   295  								Line:   1,
   296  								Column: 8,
   297  							},
   298  						},
   299  					},
   300  				},
   301  				MissingItemRange: hcl.Range{
   302  					Filename: "test.json",
   303  					Start:    hcl.Pos{Line: 1, Column: 21, Byte: 20},
   304  					End:      hcl.Pos{Line: 1, Column: 22, Byte: 21},
   305  				},
   306  			},
   307  			1,
   308  		},
   309  		{
   310  			`{"resource": null}`,
   311  			&hcl.BodySchema{
   312  				Blocks: []hcl.BlockHeaderSchema{
   313  					{
   314  						Type: "resource",
   315  					},
   316  				},
   317  			},
   318  			&hcl.BodyContent{
   319  				Attributes: map[string]*hcl.Attribute{},
   320  				// We don't find any blocks if the value is json null.
   321  				Blocks: nil,
   322  				MissingItemRange: hcl.Range{
   323  					Filename: "test.json",
   324  					Start:    hcl.Pos{Line: 1, Column: 18, Byte: 17},
   325  					End:      hcl.Pos{Line: 1, Column: 19, Byte: 18},
   326  				},
   327  			},
   328  			0,
   329  		},
   330  		{
   331  			`{"resource": { "nested": null }}`,
   332  			&hcl.BodySchema{
   333  				Blocks: []hcl.BlockHeaderSchema{
   334  					{
   335  						Type:       "resource",
   336  						LabelNames: []string{"name"},
   337  					},
   338  				},
   339  			},
   340  			&hcl.BodyContent{
   341  				Attributes: map[string]*hcl.Attribute{},
   342  				Blocks:     nil,
   343  				MissingItemRange: hcl.Range{
   344  					Filename: "test.json",
   345  					Start:    hcl.Pos{Line: 1, Column: 32, Byte: 31},
   346  					End:      hcl.Pos{Line: 1, Column: 33, Byte: 32},
   347  				},
   348  			},
   349  			0,
   350  		},
   351  		{
   352  			`{"resource":{}}`,
   353  			&hcl.BodySchema{
   354  				Blocks: []hcl.BlockHeaderSchema{
   355  					{
   356  						Type: "resource",
   357  					},
   358  				},
   359  			},
   360  			&hcl.BodyContent{
   361  				Attributes: map[string]*hcl.Attribute{},
   362  				Blocks: hcl.Blocks{
   363  					{
   364  						Type:   "resource",
   365  						Labels: []string{},
   366  						Body: &body{
   367  							val: &objectVal{
   368  								Attrs: []*objectAttr{},
   369  								SrcRange: hcl.Range{
   370  									Filename: "test.json",
   371  									Start: hcl.Pos{
   372  										Byte:   12,
   373  										Line:   1,
   374  										Column: 13,
   375  									},
   376  									End: hcl.Pos{
   377  										Byte:   14,
   378  										Line:   1,
   379  										Column: 15,
   380  									},
   381  								},
   382  								OpenRange: hcl.Range{
   383  									Filename: "test.json",
   384  									Start: hcl.Pos{
   385  										Byte:   12,
   386  										Line:   1,
   387  										Column: 13,
   388  									},
   389  									End: hcl.Pos{
   390  										Byte:   13,
   391  										Line:   1,
   392  										Column: 14,
   393  									},
   394  								},
   395  								CloseRange: hcl.Range{
   396  									Filename: "test.json",
   397  									Start: hcl.Pos{
   398  										Byte:   13,
   399  										Line:   1,
   400  										Column: 14,
   401  									},
   402  									End: hcl.Pos{
   403  										Byte:   14,
   404  										Line:   1,
   405  										Column: 15,
   406  									},
   407  								},
   408  							},
   409  						},
   410  
   411  						DefRange: hcl.Range{
   412  							Filename: "test.json",
   413  							Start: hcl.Pos{
   414  								Byte:   12,
   415  								Line:   1,
   416  								Column: 13,
   417  							},
   418  							End: hcl.Pos{
   419  								Byte:   13,
   420  								Line:   1,
   421  								Column: 14,
   422  							},
   423  						},
   424  						TypeRange: hcl.Range{
   425  							Filename: "test.json",
   426  							Start: hcl.Pos{
   427  								Byte:   1,
   428  								Line:   1,
   429  								Column: 2,
   430  							},
   431  							End: hcl.Pos{
   432  								Byte:   11,
   433  								Line:   1,
   434  								Column: 12,
   435  							},
   436  						},
   437  						LabelRanges: []hcl.Range{},
   438  					},
   439  				},
   440  				MissingItemRange: hcl.Range{
   441  					Filename: "test.json",
   442  					Start:    hcl.Pos{Line: 1, Column: 15, Byte: 14},
   443  					End:      hcl.Pos{Line: 1, Column: 16, Byte: 15},
   444  				},
   445  			},
   446  			0,
   447  		},
   448  		{
   449  			`{"resource":[{},{}]}`,
   450  			&hcl.BodySchema{
   451  				Blocks: []hcl.BlockHeaderSchema{
   452  					{
   453  						Type: "resource",
   454  					},
   455  				},
   456  			},
   457  			&hcl.BodyContent{
   458  				Attributes: map[string]*hcl.Attribute{},
   459  				Blocks: hcl.Blocks{
   460  					{
   461  						Type:   "resource",
   462  						Labels: []string{},
   463  						Body: &body{
   464  							val: &objectVal{
   465  								Attrs: []*objectAttr{},
   466  								SrcRange: hcl.Range{
   467  									Filename: "test.json",
   468  									Start: hcl.Pos{
   469  										Byte:   13,
   470  										Line:   1,
   471  										Column: 14,
   472  									},
   473  									End: hcl.Pos{
   474  										Byte:   15,
   475  										Line:   1,
   476  										Column: 16,
   477  									},
   478  								},
   479  								OpenRange: hcl.Range{
   480  									Filename: "test.json",
   481  									Start: hcl.Pos{
   482  										Byte:   13,
   483  										Line:   1,
   484  										Column: 14,
   485  									},
   486  									End: hcl.Pos{
   487  										Byte:   14,
   488  										Line:   1,
   489  										Column: 15,
   490  									},
   491  								},
   492  								CloseRange: hcl.Range{
   493  									Filename: "test.json",
   494  									Start: hcl.Pos{
   495  										Byte:   14,
   496  										Line:   1,
   497  										Column: 15,
   498  									},
   499  									End: hcl.Pos{
   500  										Byte:   15,
   501  										Line:   1,
   502  										Column: 16,
   503  									},
   504  								},
   505  							},
   506  						},
   507  
   508  						DefRange: hcl.Range{
   509  							Filename: "test.json",
   510  							Start: hcl.Pos{
   511  								Byte:   12,
   512  								Line:   1,
   513  								Column: 13,
   514  							},
   515  							End: hcl.Pos{
   516  								Byte:   13,
   517  								Line:   1,
   518  								Column: 14,
   519  							},
   520  						},
   521  						TypeRange: hcl.Range{
   522  							Filename: "test.json",
   523  							Start: hcl.Pos{
   524  								Byte:   1,
   525  								Line:   1,
   526  								Column: 2,
   527  							},
   528  							End: hcl.Pos{
   529  								Byte:   11,
   530  								Line:   1,
   531  								Column: 12,
   532  							},
   533  						},
   534  						LabelRanges: []hcl.Range{},
   535  					},
   536  					{
   537  						Type:   "resource",
   538  						Labels: []string{},
   539  						Body: &body{
   540  							val: &objectVal{
   541  								Attrs: []*objectAttr{},
   542  								SrcRange: hcl.Range{
   543  									Filename: "test.json",
   544  									Start: hcl.Pos{
   545  										Byte:   16,
   546  										Line:   1,
   547  										Column: 17,
   548  									},
   549  									End: hcl.Pos{
   550  										Byte:   18,
   551  										Line:   1,
   552  										Column: 19,
   553  									},
   554  								},
   555  								OpenRange: hcl.Range{
   556  									Filename: "test.json",
   557  									Start: hcl.Pos{
   558  										Byte:   16,
   559  										Line:   1,
   560  										Column: 17,
   561  									},
   562  									End: hcl.Pos{
   563  										Byte:   17,
   564  										Line:   1,
   565  										Column: 18,
   566  									},
   567  								},
   568  								CloseRange: hcl.Range{
   569  									Filename: "test.json",
   570  									Start: hcl.Pos{
   571  										Byte:   17,
   572  										Line:   1,
   573  										Column: 18,
   574  									},
   575  									End: hcl.Pos{
   576  										Byte:   18,
   577  										Line:   1,
   578  										Column: 19,
   579  									},
   580  								},
   581  							},
   582  						},
   583  
   584  						DefRange: hcl.Range{
   585  							Filename: "test.json",
   586  							Start: hcl.Pos{
   587  								Byte:   12,
   588  								Line:   1,
   589  								Column: 13,
   590  							},
   591  							End: hcl.Pos{
   592  								Byte:   13,
   593  								Line:   1,
   594  								Column: 14,
   595  							},
   596  						},
   597  						TypeRange: hcl.Range{
   598  							Filename: "test.json",
   599  							Start: hcl.Pos{
   600  								Byte:   1,
   601  								Line:   1,
   602  								Column: 2,
   603  							},
   604  							End: hcl.Pos{
   605  								Byte:   11,
   606  								Line:   1,
   607  								Column: 12,
   608  							},
   609  						},
   610  						LabelRanges: []hcl.Range{},
   611  					},
   612  				},
   613  				MissingItemRange: hcl.Range{
   614  					Filename: "test.json",
   615  					Start:    hcl.Pos{Line: 1, Column: 20, Byte: 19},
   616  					End:      hcl.Pos{Line: 1, Column: 21, Byte: 20},
   617  				},
   618  			},
   619  			0,
   620  		},
   621  		{
   622  			`{"resource":{"foo_instance":{"bar":{}}}}`,
   623  			&hcl.BodySchema{
   624  				Blocks: []hcl.BlockHeaderSchema{
   625  					{
   626  						Type:       "resource",
   627  						LabelNames: []string{"type", "name"},
   628  					},
   629  				},
   630  			},
   631  			&hcl.BodyContent{
   632  				Attributes: map[string]*hcl.Attribute{},
   633  				Blocks: hcl.Blocks{
   634  					{
   635  						Type:   "resource",
   636  						Labels: []string{"foo_instance", "bar"},
   637  						Body: &body{
   638  							val: &objectVal{
   639  								Attrs: []*objectAttr{},
   640  								SrcRange: hcl.Range{
   641  									Filename: "test.json",
   642  									Start: hcl.Pos{
   643  										Byte:   35,
   644  										Line:   1,
   645  										Column: 36,
   646  									},
   647  									End: hcl.Pos{
   648  										Byte:   37,
   649  										Line:   1,
   650  										Column: 38,
   651  									},
   652  								},
   653  								OpenRange: hcl.Range{
   654  									Filename: "test.json",
   655  									Start: hcl.Pos{
   656  										Byte:   35,
   657  										Line:   1,
   658  										Column: 36,
   659  									},
   660  									End: hcl.Pos{
   661  										Byte:   36,
   662  										Line:   1,
   663  										Column: 37,
   664  									},
   665  								},
   666  								CloseRange: hcl.Range{
   667  									Filename: "test.json",
   668  									Start: hcl.Pos{
   669  										Byte:   36,
   670  										Line:   1,
   671  										Column: 37,
   672  									},
   673  									End: hcl.Pos{
   674  										Byte:   37,
   675  										Line:   1,
   676  										Column: 38,
   677  									},
   678  								},
   679  							},
   680  						},
   681  
   682  						DefRange: hcl.Range{
   683  							Filename: "test.json",
   684  							Start: hcl.Pos{
   685  								Byte:   35,
   686  								Line:   1,
   687  								Column: 36,
   688  							},
   689  							End: hcl.Pos{
   690  								Byte:   36,
   691  								Line:   1,
   692  								Column: 37,
   693  							},
   694  						},
   695  						TypeRange: hcl.Range{
   696  							Filename: "test.json",
   697  							Start: hcl.Pos{
   698  								Byte:   1,
   699  								Line:   1,
   700  								Column: 2,
   701  							},
   702  							End: hcl.Pos{
   703  								Byte:   11,
   704  								Line:   1,
   705  								Column: 12,
   706  							},
   707  						},
   708  						LabelRanges: []hcl.Range{
   709  							{
   710  								Filename: "test.json",
   711  								Start: hcl.Pos{
   712  									Byte:   13,
   713  									Line:   1,
   714  									Column: 14,
   715  								},
   716  								End: hcl.Pos{
   717  									Byte:   27,
   718  									Line:   1,
   719  									Column: 28,
   720  								},
   721  							},
   722  							{
   723  								Filename: "test.json",
   724  								Start: hcl.Pos{
   725  									Byte:   29,
   726  									Line:   1,
   727  									Column: 30,
   728  								},
   729  								End: hcl.Pos{
   730  									Byte:   34,
   731  									Line:   1,
   732  									Column: 35,
   733  								},
   734  							},
   735  						},
   736  					},
   737  				},
   738  				MissingItemRange: hcl.Range{
   739  					Filename: "test.json",
   740  					Start:    hcl.Pos{Line: 1, Column: 40, Byte: 39},
   741  					End:      hcl.Pos{Line: 1, Column: 41, Byte: 40},
   742  				},
   743  			},
   744  			0,
   745  		},
   746  		{
   747  			`{"resource":{"foo_instance":[{"bar":{}}, {"bar":{}}]}}`,
   748  			&hcl.BodySchema{
   749  				Blocks: []hcl.BlockHeaderSchema{
   750  					{
   751  						Type:       "resource",
   752  						LabelNames: []string{"type", "name"},
   753  					},
   754  				},
   755  			},
   756  			&hcl.BodyContent{
   757  				Attributes: map[string]*hcl.Attribute{},
   758  				Blocks: hcl.Blocks{
   759  					{
   760  						Type:   "resource",
   761  						Labels: []string{"foo_instance", "bar"},
   762  						Body: &body{
   763  							val: &objectVal{
   764  								Attrs: []*objectAttr{},
   765  								SrcRange: hcl.Range{
   766  									Filename: "test.json",
   767  									Start: hcl.Pos{
   768  										Byte:   36,
   769  										Line:   1,
   770  										Column: 37,
   771  									},
   772  									End: hcl.Pos{
   773  										Byte:   38,
   774  										Line:   1,
   775  										Column: 39,
   776  									},
   777  								},
   778  								OpenRange: hcl.Range{
   779  									Filename: "test.json",
   780  									Start: hcl.Pos{
   781  										Byte:   36,
   782  										Line:   1,
   783  										Column: 37,
   784  									},
   785  									End: hcl.Pos{
   786  										Byte:   37,
   787  										Line:   1,
   788  										Column: 38,
   789  									},
   790  								},
   791  								CloseRange: hcl.Range{
   792  									Filename: "test.json",
   793  									Start: hcl.Pos{
   794  										Byte:   37,
   795  										Line:   1,
   796  										Column: 38,
   797  									},
   798  									End: hcl.Pos{
   799  										Byte:   38,
   800  										Line:   1,
   801  										Column: 39,
   802  									},
   803  								},
   804  							},
   805  						},
   806  
   807  						DefRange: hcl.Range{
   808  							Filename: "test.json",
   809  							Start: hcl.Pos{
   810  								Byte:   36,
   811  								Line:   1,
   812  								Column: 37,
   813  							},
   814  							End: hcl.Pos{
   815  								Byte:   37,
   816  								Line:   1,
   817  								Column: 38,
   818  							},
   819  						},
   820  						TypeRange: hcl.Range{
   821  							Filename: "test.json",
   822  							Start: hcl.Pos{
   823  								Byte:   1,
   824  								Line:   1,
   825  								Column: 2,
   826  							},
   827  							End: hcl.Pos{
   828  								Byte:   11,
   829  								Line:   1,
   830  								Column: 12,
   831  							},
   832  						},
   833  						LabelRanges: []hcl.Range{
   834  							{
   835  								Filename: "test.json",
   836  								Start: hcl.Pos{
   837  									Byte:   13,
   838  									Line:   1,
   839  									Column: 14,
   840  								},
   841  								End: hcl.Pos{
   842  									Byte:   27,
   843  									Line:   1,
   844  									Column: 28,
   845  								},
   846  							},
   847  							{
   848  								Filename: "test.json",
   849  								Start: hcl.Pos{
   850  									Byte:   30,
   851  									Line:   1,
   852  									Column: 31,
   853  								},
   854  								End: hcl.Pos{
   855  									Byte:   35,
   856  									Line:   1,
   857  									Column: 36,
   858  								},
   859  							},
   860  						},
   861  					},
   862  					{
   863  						Type:   "resource",
   864  						Labels: []string{"foo_instance", "bar"},
   865  						Body: &body{
   866  							val: &objectVal{
   867  								Attrs: []*objectAttr{},
   868  								SrcRange: hcl.Range{
   869  									Filename: "test.json",
   870  									Start: hcl.Pos{
   871  										Byte:   36,
   872  										Line:   1,
   873  										Column: 37,
   874  									},
   875  									End: hcl.Pos{
   876  										Byte:   38,
   877  										Line:   1,
   878  										Column: 39,
   879  									},
   880  								},
   881  								OpenRange: hcl.Range{
   882  									Filename: "test.json",
   883  									Start: hcl.Pos{
   884  										Byte:   36,
   885  										Line:   1,
   886  										Column: 37,
   887  									},
   888  									End: hcl.Pos{
   889  										Byte:   37,
   890  										Line:   1,
   891  										Column: 38,
   892  									},
   893  								},
   894  								CloseRange: hcl.Range{
   895  									Filename: "test.json",
   896  									Start: hcl.Pos{
   897  										Byte:   37,
   898  										Line:   1,
   899  										Column: 38,
   900  									},
   901  									End: hcl.Pos{
   902  										Byte:   38,
   903  										Line:   1,
   904  										Column: 39,
   905  									},
   906  								},
   907  							},
   908  						},
   909  
   910  						DefRange: hcl.Range{
   911  							Filename: "test.json",
   912  							Start: hcl.Pos{
   913  								Byte:   48,
   914  								Line:   1,
   915  								Column: 49,
   916  							},
   917  							End: hcl.Pos{
   918  								Byte:   49,
   919  								Line:   1,
   920  								Column: 50,
   921  							},
   922  						},
   923  						TypeRange: hcl.Range{
   924  							Filename: "test.json",
   925  							Start: hcl.Pos{
   926  								Byte:   1,
   927  								Line:   1,
   928  								Column: 2,
   929  							},
   930  							End: hcl.Pos{
   931  								Byte:   11,
   932  								Line:   1,
   933  								Column: 12,
   934  							},
   935  						},
   936  						LabelRanges: []hcl.Range{
   937  							{
   938  								Filename: "test.json",
   939  								Start: hcl.Pos{
   940  									Byte:   13,
   941  									Line:   1,
   942  									Column: 14,
   943  								},
   944  								End: hcl.Pos{
   945  									Byte:   27,
   946  									Line:   1,
   947  									Column: 28,
   948  								},
   949  							},
   950  							{
   951  								Filename: "test.json",
   952  								Start: hcl.Pos{
   953  									Byte:   42,
   954  									Line:   1,
   955  									Column: 43,
   956  								},
   957  								End: hcl.Pos{
   958  									Byte:   47,
   959  									Line:   1,
   960  									Column: 48,
   961  								},
   962  							},
   963  						},
   964  					},
   965  				},
   966  				MissingItemRange: hcl.Range{
   967  					Filename: "test.json",
   968  					Start:    hcl.Pos{Line: 1, Column: 54, Byte: 53},
   969  					End:      hcl.Pos{Line: 1, Column: 55, Byte: 54},
   970  				},
   971  			},
   972  			0,
   973  		},
   974  		{
   975  			`{"name":"Ermintrude"}`,
   976  			&hcl.BodySchema{
   977  				Blocks: []hcl.BlockHeaderSchema{
   978  					{
   979  						Type: "name",
   980  					},
   981  				},
   982  			},
   983  			&hcl.BodyContent{
   984  				Attributes: map[string]*hcl.Attribute{},
   985  				MissingItemRange: hcl.Range{
   986  					Filename: "test.json",
   987  					Start:    hcl.Pos{Line: 1, Column: 21, Byte: 20},
   988  					End:      hcl.Pos{Line: 1, Column: 22, Byte: 21},
   989  				},
   990  			},
   991  			1, // name is supposed to be a block
   992  		},
   993  		{
   994  			`[{"name":"Ermintrude"},{"name":"Ermintrude"}]`,
   995  			&hcl.BodySchema{
   996  				Attributes: []hcl.AttributeSchema{
   997  					{
   998  						Name: "name",
   999  					},
  1000  				},
  1001  			},
  1002  			&hcl.BodyContent{
  1003  				Attributes: map[string]*hcl.Attribute{
  1004  					"name": {
  1005  						Name: "name",
  1006  						Expr: &expression{
  1007  							src: &stringVal{
  1008  								Value: "Ermintrude",
  1009  								SrcRange: hcl.Range{
  1010  									Filename: "test.json",
  1011  									Start: hcl.Pos{
  1012  										Byte:   8,
  1013  										Line:   1,
  1014  										Column: 9,
  1015  									},
  1016  									End: hcl.Pos{
  1017  										Byte:   20,
  1018  										Line:   1,
  1019  										Column: 21,
  1020  									},
  1021  								},
  1022  							},
  1023  						},
  1024  						Range: hcl.Range{
  1025  							Filename: "test.json",
  1026  							Start: hcl.Pos{
  1027  								Byte:   2,
  1028  								Line:   1,
  1029  								Column: 3,
  1030  							},
  1031  							End: hcl.Pos{
  1032  								Byte:   21,
  1033  								Line:   1,
  1034  								Column: 22,
  1035  							},
  1036  						},
  1037  						NameRange: hcl.Range{
  1038  							Filename: "test.json",
  1039  							Start: hcl.Pos{
  1040  								Byte:   2,
  1041  								Line:   1,
  1042  								Column: 3,
  1043  							},
  1044  							End: hcl.Pos{
  1045  								Byte:   8,
  1046  								Line:   1,
  1047  								Column: 9,
  1048  							},
  1049  						},
  1050  					},
  1051  				},
  1052  				MissingItemRange: hcl.Range{
  1053  					Filename: "test.json",
  1054  					Start:    hcl.Pos{Line: 1, Column: 1, Byte: 0},
  1055  					End:      hcl.Pos{Line: 1, Column: 2, Byte: 1},
  1056  				},
  1057  			},
  1058  			1, // "name" attribute is defined twice
  1059  		},
  1060  	}
  1061  
  1062  	for i, test := range tests {
  1063  		t.Run(fmt.Sprintf("%02d-%s", i, test.src), func(t *testing.T) {
  1064  			file, diags := Parse([]byte(test.src), "test.json")
  1065  			if len(diags) != 0 {
  1066  				t.Fatalf("Parse produced diagnostics: %s", diags)
  1067  			}
  1068  			got, _, diags := file.Body.PartialContent(test.schema)
  1069  			if len(diags) != test.diagCount {
  1070  				t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
  1071  				for _, diag := range diags {
  1072  					t.Logf(" - %s", diag)
  1073  				}
  1074  			}
  1075  
  1076  			for _, problem := range deep.Equal(got, test.want) {
  1077  				t.Error(problem)
  1078  			}
  1079  		})
  1080  	}
  1081  }
  1082  
  1083  func TestBodyContent(t *testing.T) {
  1084  	// We test most of the functionality already in TestBodyPartialContent, so
  1085  	// this test focuses on the handling of extraneous attributes.
  1086  	tests := []struct {
  1087  		src       string
  1088  		schema    *hcl.BodySchema
  1089  		diagCount int
  1090  	}{
  1091  		{
  1092  			`{"unknown": true}`,
  1093  			&hcl.BodySchema{},
  1094  			1,
  1095  		},
  1096  		{
  1097  			`{"//": "comment that should be ignored"}`,
  1098  			&hcl.BodySchema{},
  1099  			0,
  1100  		},
  1101  		{
  1102  			`{"unknow": true}`,
  1103  			&hcl.BodySchema{
  1104  				Attributes: []hcl.AttributeSchema{
  1105  					{
  1106  						Name: "unknown",
  1107  					},
  1108  				},
  1109  			},
  1110  			1,
  1111  		},
  1112  		{
  1113  			`{"unknow": true, "unnown": true}`,
  1114  			&hcl.BodySchema{
  1115  				Attributes: []hcl.AttributeSchema{
  1116  					{
  1117  						Name: "unknown",
  1118  					},
  1119  				},
  1120  			},
  1121  			2,
  1122  		},
  1123  	}
  1124  
  1125  	for i, test := range tests {
  1126  		t.Run(fmt.Sprintf("%02d-%s", i, test.src), func(t *testing.T) {
  1127  			file, diags := Parse([]byte(test.src), "test.json")
  1128  			if len(diags) != 0 {
  1129  				t.Fatalf("Parse produced diagnostics: %s", diags)
  1130  			}
  1131  			_, diags = file.Body.Content(test.schema)
  1132  			if len(diags) != test.diagCount {
  1133  				t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
  1134  				for _, diag := range diags {
  1135  					t.Logf(" - %s", diag)
  1136  				}
  1137  			}
  1138  		})
  1139  	}
  1140  }
  1141  
  1142  func TestJustAttributes(t *testing.T) {
  1143  	// We test most of the functionality already in TestBodyPartialContent, so
  1144  	// this test focuses on the handling of extraneous attributes.
  1145  	tests := []struct {
  1146  		src       string
  1147  		want      hcl.Attributes
  1148  		diagCount int
  1149  	}{
  1150  		{
  1151  			`{}`,
  1152  			map[string]*hcl.Attribute{},
  1153  			0,
  1154  		},
  1155  		{
  1156  			`{"foo": true}`,
  1157  			map[string]*hcl.Attribute{
  1158  				"foo": {
  1159  					Name: "foo",
  1160  					Expr: &expression{
  1161  						src: &booleanVal{
  1162  							Value: true,
  1163  							SrcRange: hcl.Range{
  1164  								Filename: "test.json",
  1165  								Start:    hcl.Pos{Byte: 8, Line: 1, Column: 9},
  1166  								End:      hcl.Pos{Byte: 12, Line: 1, Column: 13},
  1167  							},
  1168  						},
  1169  					},
  1170  					Range: hcl.Range{
  1171  						Filename: "test.json",
  1172  						Start:    hcl.Pos{Byte: 1, Line: 1, Column: 2},
  1173  						End:      hcl.Pos{Byte: 12, Line: 1, Column: 13},
  1174  					},
  1175  					NameRange: hcl.Range{
  1176  						Filename: "test.json",
  1177  						Start:    hcl.Pos{Byte: 1, Line: 1, Column: 2},
  1178  						End:      hcl.Pos{Byte: 6, Line: 1, Column: 7},
  1179  					},
  1180  				},
  1181  			},
  1182  			0,
  1183  		},
  1184  		{
  1185  			`{"//": "comment that should be ignored"}`,
  1186  			map[string]*hcl.Attribute{},
  1187  			0,
  1188  		},
  1189  		{
  1190  			`{"foo": true, "foo": true}`,
  1191  			map[string]*hcl.Attribute{
  1192  				"foo": {
  1193  					Name: "foo",
  1194  					Expr: &expression{
  1195  						src: &booleanVal{
  1196  							Value: true,
  1197  							SrcRange: hcl.Range{
  1198  								Filename: "test.json",
  1199  								Start:    hcl.Pos{Byte: 8, Line: 1, Column: 9},
  1200  								End:      hcl.Pos{Byte: 12, Line: 1, Column: 13},
  1201  							},
  1202  						},
  1203  					},
  1204  					Range: hcl.Range{
  1205  						Filename: "test.json",
  1206  						Start:    hcl.Pos{Byte: 1, Line: 1, Column: 2},
  1207  						End:      hcl.Pos{Byte: 12, Line: 1, Column: 13},
  1208  					},
  1209  					NameRange: hcl.Range{
  1210  						Filename: "test.json",
  1211  						Start:    hcl.Pos{Byte: 1, Line: 1, Column: 2},
  1212  						End:      hcl.Pos{Byte: 6, Line: 1, Column: 7},
  1213  					},
  1214  				},
  1215  			},
  1216  			1, // attribute foo was already defined
  1217  		},
  1218  	}
  1219  
  1220  	for i, test := range tests {
  1221  		t.Run(fmt.Sprintf("%02d-%s", i, test.src), func(t *testing.T) {
  1222  			file, diags := Parse([]byte(test.src), "test.json")
  1223  			if len(diags) != 0 {
  1224  				t.Fatalf("Parse produced diagnostics: %s", diags)
  1225  			}
  1226  			got, diags := file.Body.JustAttributes()
  1227  			if len(diags) != test.diagCount {
  1228  				t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
  1229  				for _, diag := range diags {
  1230  					t.Logf(" - %s", diag)
  1231  				}
  1232  			}
  1233  			if !reflect.DeepEqual(got, test.want) {
  1234  				t.Errorf("wrong result\ngot:  %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.want))
  1235  			}
  1236  		})
  1237  	}
  1238  }
  1239  
  1240  func TestExpressionVariables(t *testing.T) {
  1241  	tests := []struct {
  1242  		Src  string
  1243  		Want []hcl.Traversal
  1244  	}{
  1245  		{
  1246  			`{"a":true}`,
  1247  			nil,
  1248  		},
  1249  		{
  1250  			`{"a":"${foo}"}`,
  1251  			[]hcl.Traversal{
  1252  				{
  1253  					hcl.TraverseRoot{
  1254  						Name: "foo",
  1255  						SrcRange: hcl.Range{
  1256  							Filename: "test.json",
  1257  							Start:    hcl.Pos{Line: 1, Column: 9, Byte: 8},
  1258  							End:      hcl.Pos{Line: 1, Column: 12, Byte: 11},
  1259  						},
  1260  					},
  1261  				},
  1262  			},
  1263  		},
  1264  		{
  1265  			`{"a":["${foo}"]}`,
  1266  			[]hcl.Traversal{
  1267  				{
  1268  					hcl.TraverseRoot{
  1269  						Name: "foo",
  1270  						SrcRange: hcl.Range{
  1271  							Filename: "test.json",
  1272  							Start:    hcl.Pos{Line: 1, Column: 10, Byte: 9},
  1273  							End:      hcl.Pos{Line: 1, Column: 13, Byte: 12},
  1274  						},
  1275  					},
  1276  				},
  1277  			},
  1278  		},
  1279  		{
  1280  			`{"a":{"b":"${foo}"}}`,
  1281  			[]hcl.Traversal{
  1282  				{
  1283  					hcl.TraverseRoot{
  1284  						Name: "foo",
  1285  						SrcRange: hcl.Range{
  1286  							Filename: "test.json",
  1287  							Start:    hcl.Pos{Line: 1, Column: 14, Byte: 13},
  1288  							End:      hcl.Pos{Line: 1, Column: 17, Byte: 16},
  1289  						},
  1290  					},
  1291  				},
  1292  			},
  1293  		},
  1294  		{
  1295  			`{"a":{"${foo}":"b"}}`,
  1296  			[]hcl.Traversal{
  1297  				{
  1298  					hcl.TraverseRoot{
  1299  						Name: "foo",
  1300  						SrcRange: hcl.Range{
  1301  							Filename: "test.json",
  1302  							Start:    hcl.Pos{Line: 1, Column: 10, Byte: 9},
  1303  							End:      hcl.Pos{Line: 1, Column: 13, Byte: 12},
  1304  						},
  1305  					},
  1306  				},
  1307  			},
  1308  		},
  1309  	}
  1310  
  1311  	for _, test := range tests {
  1312  		t.Run(test.Src, func(t *testing.T) {
  1313  			file, diags := Parse([]byte(test.Src), "test.json")
  1314  			if len(diags) != 0 {
  1315  				t.Fatalf("Parse produced diagnostics: %s", diags)
  1316  			}
  1317  			attrs, diags := file.Body.JustAttributes()
  1318  			if len(diags) != 0 {
  1319  				t.Fatalf("JustAttributes produced diagnostics: %s", diags)
  1320  			}
  1321  			got := attrs["a"].Expr.Variables()
  1322  			if !reflect.DeepEqual(got, test.Want) {
  1323  				t.Errorf("wrong result\ngot:  %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want))
  1324  			}
  1325  		})
  1326  	}
  1327  }
  1328  
  1329  func TestExpressionAsTraversal(t *testing.T) {
  1330  	e := &expression{
  1331  		src: &stringVal{
  1332  			Value: "foo.bar[0]",
  1333  		},
  1334  	}
  1335  	traversal := e.AsTraversal()
  1336  	if len(traversal) != 3 {
  1337  		t.Fatalf("incorrect traversal %#v; want length 3", traversal)
  1338  	}
  1339  }
  1340  
  1341  func TestStaticExpressionList(t *testing.T) {
  1342  	e := &expression{
  1343  		src: &arrayVal{
  1344  			Values: []node{
  1345  				&stringVal{
  1346  					Value: "hello",
  1347  				},
  1348  			},
  1349  		},
  1350  	}
  1351  	exprs := e.ExprList()
  1352  	if len(exprs) != 1 {
  1353  		t.Fatalf("incorrect exprs %#v; want length 1", exprs)
  1354  	}
  1355  	if exprs[0].(*expression).src != e.src.(*arrayVal).Values[0] {
  1356  		t.Fatalf("wrong first expression node")
  1357  	}
  1358  }
  1359  
  1360  func TestExpression_Value(t *testing.T) {
  1361  	src := `{
  1362    "string": "string_val",
  1363    "number": 5,
  1364    "bool_true": true,
  1365    "bool_false": false,
  1366    "array": ["a"],
  1367    "object": {"key": "value"},
  1368    "null": null
  1369  }`
  1370  	expected := map[string]cty.Value{
  1371  		"string":     cty.StringVal("string_val"),
  1372  		"number":     cty.NumberIntVal(5),
  1373  		"bool_true":  cty.BoolVal(true),
  1374  		"bool_false": cty.BoolVal(false),
  1375  		"array":      cty.TupleVal([]cty.Value{cty.StringVal("a")}),
  1376  		"object": cty.ObjectVal(map[string]cty.Value{
  1377  			"key": cty.StringVal("value"),
  1378  		}),
  1379  		"null": cty.NullVal(cty.DynamicPseudoType),
  1380  	}
  1381  
  1382  	file, diags := Parse([]byte(src), "")
  1383  	if len(diags) != 0 {
  1384  		t.Errorf("got %d diagnostics on parse; want 0", len(diags))
  1385  		for _, diag := range diags {
  1386  			t.Logf("- %s", diag.Error())
  1387  		}
  1388  	}
  1389  	if file == nil {
  1390  		t.Errorf("got nil File; want actual file")
  1391  	}
  1392  	if file.Body == nil {
  1393  		t.Fatalf("got nil Body; want actual body")
  1394  	}
  1395  	attrs, diags := file.Body.JustAttributes()
  1396  	if len(diags) != 0 {
  1397  		t.Errorf("got %d diagnostics on decode; want 0", len(diags))
  1398  		for _, diag := range diags {
  1399  			t.Logf("- %s", diag.Error())
  1400  		}
  1401  	}
  1402  
  1403  	for ek, ev := range expected {
  1404  		val, diags := attrs[ek].Expr.Value(&hcl.EvalContext{})
  1405  		if len(diags) != 0 {
  1406  			t.Errorf("got %d diagnostics on eval; want 0", len(diags))
  1407  			for _, diag := range diags {
  1408  				t.Logf("- %s", diag.Error())
  1409  			}
  1410  		}
  1411  
  1412  		if !val.RawEquals(ev) {
  1413  			t.Errorf("wrong result %#v; want %#v", val, ev)
  1414  		}
  1415  	}
  1416  
  1417  }
  1418  
  1419  // TestExpressionValue_Diags asserts that Value() returns diagnostics
  1420  // from nested evaluations for complex objects (e.g. ObjectVal, ArrayVal)
  1421  func TestExpressionValue_Diags(t *testing.T) {
  1422  	cases := []struct {
  1423  		name     string
  1424  		src      string
  1425  		expected cty.Value
  1426  		error    string
  1427  	}{
  1428  		{
  1429  			name:     "string: happy",
  1430  			src:      `{"v": "happy ${VAR1}"}`,
  1431  			expected: cty.StringVal("happy case"),
  1432  		},
  1433  		{
  1434  			name:     "string: unhappy",
  1435  			src:      `{"v": "happy ${UNKNOWN}"}`,
  1436  			expected: cty.UnknownVal(cty.String).RefineNotNull(),
  1437  			error:    "Unknown variable",
  1438  		},
  1439  		{
  1440  			name: "object_val: happy",
  1441  			src:  `{"v": {"key": "happy ${VAR1}"}}`,
  1442  			expected: cty.ObjectVal(map[string]cty.Value{
  1443  				"key": cty.StringVal("happy case"),
  1444  			}),
  1445  		},
  1446  		{
  1447  			name: "object_val: unhappy",
  1448  			src:  `{"v": {"key": "happy ${UNKNOWN}"}}`,
  1449  			expected: cty.ObjectVal(map[string]cty.Value{
  1450  				"key": cty.UnknownVal(cty.String).RefineNotNull(),
  1451  			}),
  1452  			error: "Unknown variable",
  1453  		},
  1454  		{
  1455  			name: "object_key: happy",
  1456  			src:  `{"v": {"happy ${VAR1}": "val"}}`,
  1457  			expected: cty.ObjectVal(map[string]cty.Value{
  1458  				"happy case": cty.StringVal("val"),
  1459  			}),
  1460  		},
  1461  		{
  1462  			name:     "object_key: unhappy",
  1463  			src:      `{"v": {"happy ${UNKNOWN}": "val"}}`,
  1464  			expected: cty.DynamicVal,
  1465  			error:    "Unknown variable",
  1466  		},
  1467  		{
  1468  			name:     "array: happy",
  1469  			src:      `{"v": ["happy ${VAR1}"]}`,
  1470  			expected: cty.TupleVal([]cty.Value{cty.StringVal("happy case")}),
  1471  		},
  1472  		{
  1473  			name:     "array: unhappy",
  1474  			src:      `{"v": ["happy ${UNKNOWN}"]}`,
  1475  			expected: cty.TupleVal([]cty.Value{cty.UnknownVal(cty.String).RefineNotNull()}),
  1476  			error:    "Unknown variable",
  1477  		},
  1478  	}
  1479  
  1480  	ctx := &hcl.EvalContext{
  1481  		Variables: map[string]cty.Value{
  1482  			"VAR1": cty.StringVal("case"),
  1483  		},
  1484  	}
  1485  
  1486  	for _, c := range cases {
  1487  		t.Run(c.name, func(t *testing.T) {
  1488  			file, diags := Parse([]byte(c.src), "")
  1489  
  1490  			if len(diags) != 0 {
  1491  				t.Errorf("got %d diagnostics on parse; want 0", len(diags))
  1492  				for _, diag := range diags {
  1493  					t.Logf("- %s", diag.Error())
  1494  				}
  1495  				t.FailNow()
  1496  			}
  1497  			if file == nil {
  1498  				t.Errorf("got nil File; want actual file")
  1499  			}
  1500  			if file.Body == nil {
  1501  				t.Fatalf("got nil Body; want actual body")
  1502  			}
  1503  
  1504  			attrs, diags := file.Body.JustAttributes()
  1505  			if len(diags) != 0 {
  1506  				t.Errorf("got %d diagnostics on decode; want 0", len(diags))
  1507  				for _, diag := range diags {
  1508  					t.Logf("- %s", diag.Error())
  1509  				}
  1510  				t.FailNow()
  1511  			}
  1512  
  1513  			val, diags := attrs["v"].Expr.Value(ctx)
  1514  			if c.error == "" && len(diags) != 0 {
  1515  				t.Errorf("got %d diagnostics on eval; want 0", len(diags))
  1516  				for _, diag := range diags {
  1517  					t.Logf("- %s", diag.Error())
  1518  				}
  1519  				t.FailNow()
  1520  			} else if c.error != "" && len(diags) == 0 {
  1521  				t.Fatalf("got 0 diagnostics on eval, want 1 with %s", c.error)
  1522  			} else if c.error != "" && len(diags) != 0 {
  1523  				if !strings.Contains(diags[0].Error(), c.error) {
  1524  					t.Fatalf("found error: %s; want %s", diags[0].Error(), c.error)
  1525  				}
  1526  			}
  1527  
  1528  			if !val.RawEquals(c.expected) {
  1529  				t.Errorf("wrong result %#v; want %#v", val, c.expected)
  1530  			}
  1531  		})
  1532  	}
  1533  
  1534  }