github.com/hashicorp/hcl/v2@v2.20.0/hclwrite/generate_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hclwrite
     5  
     6  import (
     7  	"bytes"
     8  	"math/big"
     9  	"sort"
    10  	"testing"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/hashicorp/hcl/v2"
    14  	"github.com/hashicorp/hcl/v2/hclsyntax"
    15  	"github.com/zclconf/go-cty/cty"
    16  )
    17  
    18  func TestTokensForValue(t *testing.T) {
    19  	tests := []struct {
    20  		Val  cty.Value
    21  		Want Tokens
    22  	}{
    23  		{
    24  			cty.NullVal(cty.DynamicPseudoType),
    25  			Tokens{
    26  				{
    27  					Type:  hclsyntax.TokenIdent,
    28  					Bytes: []byte(`null`),
    29  				},
    30  			},
    31  		},
    32  		{
    33  			cty.True,
    34  			Tokens{
    35  				{
    36  					Type:  hclsyntax.TokenIdent,
    37  					Bytes: []byte(`true`),
    38  				},
    39  			},
    40  		},
    41  		{
    42  			cty.False,
    43  			Tokens{
    44  				{
    45  					Type:  hclsyntax.TokenIdent,
    46  					Bytes: []byte(`false`),
    47  				},
    48  			},
    49  		},
    50  		{
    51  			cty.NumberIntVal(0),
    52  			Tokens{
    53  				{
    54  					Type:  hclsyntax.TokenNumberLit,
    55  					Bytes: []byte(`0`),
    56  				},
    57  			},
    58  		},
    59  		{
    60  			cty.NumberFloatVal(0.5),
    61  			Tokens{
    62  				{
    63  					Type:  hclsyntax.TokenNumberLit,
    64  					Bytes: []byte(`0.5`),
    65  				},
    66  			},
    67  		},
    68  		{
    69  			cty.NumberVal(big.NewFloat(0).SetPrec(512).Mul(big.NewFloat(40000000), big.NewFloat(2000000))),
    70  			Tokens{
    71  				{
    72  					Type:  hclsyntax.TokenNumberLit,
    73  					Bytes: []byte(`80000000000000`),
    74  				},
    75  			},
    76  		},
    77  		{
    78  			cty.StringVal(""),
    79  			Tokens{
    80  				{
    81  					Type:  hclsyntax.TokenOQuote,
    82  					Bytes: []byte(`"`),
    83  				},
    84  				{
    85  					Type:  hclsyntax.TokenCQuote,
    86  					Bytes: []byte(`"`),
    87  				},
    88  			},
    89  		},
    90  		{
    91  			cty.StringVal("foo"),
    92  			Tokens{
    93  				{
    94  					Type:  hclsyntax.TokenOQuote,
    95  					Bytes: []byte(`"`),
    96  				},
    97  				{
    98  					Type:  hclsyntax.TokenQuotedLit,
    99  					Bytes: []byte(`foo`),
   100  				},
   101  				{
   102  					Type:  hclsyntax.TokenCQuote,
   103  					Bytes: []byte(`"`),
   104  				},
   105  			},
   106  		},
   107  		{
   108  			cty.StringVal(`"foo"`),
   109  			Tokens{
   110  				{
   111  					Type:  hclsyntax.TokenOQuote,
   112  					Bytes: []byte(`"`),
   113  				},
   114  				{
   115  					Type:  hclsyntax.TokenQuotedLit,
   116  					Bytes: []byte(`\"foo\"`),
   117  				},
   118  				{
   119  					Type:  hclsyntax.TokenCQuote,
   120  					Bytes: []byte(`"`),
   121  				},
   122  			},
   123  		},
   124  		{
   125  			cty.StringVal("hello\nworld\n"),
   126  			Tokens{
   127  				{
   128  					Type:  hclsyntax.TokenOQuote,
   129  					Bytes: []byte(`"`),
   130  				},
   131  				{
   132  					Type:  hclsyntax.TokenQuotedLit,
   133  					Bytes: []byte(`hello\nworld\n`),
   134  				},
   135  				{
   136  					Type:  hclsyntax.TokenCQuote,
   137  					Bytes: []byte(`"`),
   138  				},
   139  			},
   140  		},
   141  		{
   142  			cty.StringVal("hello\r\nworld\r\n"),
   143  			Tokens{
   144  				{
   145  					Type:  hclsyntax.TokenOQuote,
   146  					Bytes: []byte(`"`),
   147  				},
   148  				{
   149  					Type:  hclsyntax.TokenQuotedLit,
   150  					Bytes: []byte(`hello\r\nworld\r\n`),
   151  				},
   152  				{
   153  					Type:  hclsyntax.TokenCQuote,
   154  					Bytes: []byte(`"`),
   155  				},
   156  			},
   157  		},
   158  		{
   159  			cty.StringVal(`what\what`),
   160  			Tokens{
   161  				{
   162  					Type:  hclsyntax.TokenOQuote,
   163  					Bytes: []byte(`"`),
   164  				},
   165  				{
   166  					Type:  hclsyntax.TokenQuotedLit,
   167  					Bytes: []byte(`what\\what`),
   168  				},
   169  				{
   170  					Type:  hclsyntax.TokenCQuote,
   171  					Bytes: []byte(`"`),
   172  				},
   173  			},
   174  		},
   175  		{
   176  			cty.StringVal("𝄞"),
   177  			Tokens{
   178  				{
   179  					Type:  hclsyntax.TokenOQuote,
   180  					Bytes: []byte(`"`),
   181  				},
   182  				{
   183  					Type:  hclsyntax.TokenQuotedLit,
   184  					Bytes: []byte("𝄞"),
   185  				},
   186  				{
   187  					Type:  hclsyntax.TokenCQuote,
   188  					Bytes: []byte(`"`),
   189  				},
   190  			},
   191  		},
   192  		{
   193  			cty.StringVal("👩🏾"),
   194  			Tokens{
   195  				{
   196  					Type:  hclsyntax.TokenOQuote,
   197  					Bytes: []byte(`"`),
   198  				},
   199  				{
   200  					Type:  hclsyntax.TokenQuotedLit,
   201  					Bytes: []byte(`👩🏾`),
   202  				},
   203  				{
   204  					Type:  hclsyntax.TokenCQuote,
   205  					Bytes: []byte(`"`),
   206  				},
   207  			},
   208  		},
   209  		{
   210  			cty.EmptyTupleVal,
   211  			Tokens{
   212  				{
   213  					Type:  hclsyntax.TokenOBrack,
   214  					Bytes: []byte(`[`),
   215  				},
   216  				{
   217  					Type:  hclsyntax.TokenCBrack,
   218  					Bytes: []byte(`]`),
   219  				},
   220  			},
   221  		},
   222  		{
   223  			cty.TupleVal([]cty.Value{cty.EmptyTupleVal}),
   224  			Tokens{
   225  				{
   226  					Type:  hclsyntax.TokenOBrack,
   227  					Bytes: []byte(`[`),
   228  				},
   229  				{
   230  					Type:  hclsyntax.TokenOBrack,
   231  					Bytes: []byte(`[`),
   232  				},
   233  				{
   234  					Type:  hclsyntax.TokenCBrack,
   235  					Bytes: []byte(`]`),
   236  				},
   237  				{
   238  					Type:  hclsyntax.TokenCBrack,
   239  					Bytes: []byte(`]`),
   240  				},
   241  			},
   242  		},
   243  		{
   244  			cty.ListValEmpty(cty.String),
   245  			Tokens{
   246  				{
   247  					Type:  hclsyntax.TokenOBrack,
   248  					Bytes: []byte(`[`),
   249  				},
   250  				{
   251  					Type:  hclsyntax.TokenCBrack,
   252  					Bytes: []byte(`]`),
   253  				},
   254  			},
   255  		},
   256  		{
   257  			cty.SetValEmpty(cty.Bool),
   258  			Tokens{
   259  				{
   260  					Type:  hclsyntax.TokenOBrack,
   261  					Bytes: []byte(`[`),
   262  				},
   263  				{
   264  					Type:  hclsyntax.TokenCBrack,
   265  					Bytes: []byte(`]`),
   266  				},
   267  			},
   268  		},
   269  		{
   270  			cty.TupleVal([]cty.Value{cty.True}),
   271  			Tokens{
   272  				{
   273  					Type:  hclsyntax.TokenOBrack,
   274  					Bytes: []byte(`[`),
   275  				},
   276  				{
   277  					Type:  hclsyntax.TokenIdent,
   278  					Bytes: []byte(`true`),
   279  				},
   280  				{
   281  					Type:  hclsyntax.TokenCBrack,
   282  					Bytes: []byte(`]`),
   283  				},
   284  			},
   285  		},
   286  		{
   287  			cty.TupleVal([]cty.Value{cty.True, cty.NumberIntVal(0)}),
   288  			Tokens{
   289  				{
   290  					Type:  hclsyntax.TokenOBrack,
   291  					Bytes: []byte(`[`),
   292  				},
   293  				{
   294  					Type:  hclsyntax.TokenIdent,
   295  					Bytes: []byte(`true`),
   296  				},
   297  				{
   298  					Type:  hclsyntax.TokenComma,
   299  					Bytes: []byte(`,`),
   300  				},
   301  				{
   302  					Type:         hclsyntax.TokenNumberLit,
   303  					Bytes:        []byte(`0`),
   304  					SpacesBefore: 1,
   305  				},
   306  				{
   307  					Type:  hclsyntax.TokenCBrack,
   308  					Bytes: []byte(`]`),
   309  				},
   310  			},
   311  		},
   312  		{
   313  			cty.EmptyObjectVal,
   314  			Tokens{
   315  				{
   316  					Type:  hclsyntax.TokenOBrace,
   317  					Bytes: []byte(`{`),
   318  				},
   319  				{
   320  					Type:  hclsyntax.TokenCBrace,
   321  					Bytes: []byte(`}`),
   322  				},
   323  			},
   324  		},
   325  		{
   326  			cty.MapValEmpty(cty.Bool),
   327  			Tokens{
   328  				{
   329  					Type:  hclsyntax.TokenOBrace,
   330  					Bytes: []byte(`{`),
   331  				},
   332  				{
   333  					Type:  hclsyntax.TokenCBrace,
   334  					Bytes: []byte(`}`),
   335  				},
   336  			},
   337  		},
   338  		{
   339  			cty.ObjectVal(map[string]cty.Value{
   340  				"foo": cty.True,
   341  			}),
   342  			Tokens{
   343  				{
   344  					Type:  hclsyntax.TokenOBrace,
   345  					Bytes: []byte(`{`),
   346  				},
   347  				{
   348  					Type:  hclsyntax.TokenNewline,
   349  					Bytes: []byte("\n"),
   350  				},
   351  				{
   352  					Type:         hclsyntax.TokenIdent,
   353  					Bytes:        []byte(`foo`),
   354  					SpacesBefore: 2,
   355  				},
   356  				{
   357  					Type:         hclsyntax.TokenEqual,
   358  					Bytes:        []byte(`=`),
   359  					SpacesBefore: 1,
   360  				},
   361  				{
   362  					Type:         hclsyntax.TokenIdent,
   363  					Bytes:        []byte(`true`),
   364  					SpacesBefore: 1,
   365  				},
   366  				{
   367  					Type:  hclsyntax.TokenNewline,
   368  					Bytes: []byte("\n"),
   369  				},
   370  				{
   371  					Type:  hclsyntax.TokenCBrace,
   372  					Bytes: []byte(`}`),
   373  				},
   374  			},
   375  		},
   376  		{
   377  			cty.ObjectVal(map[string]cty.Value{
   378  				"foo": cty.True,
   379  				"bar": cty.NumberIntVal(0),
   380  			}),
   381  			Tokens{
   382  				{
   383  					Type:  hclsyntax.TokenOBrace,
   384  					Bytes: []byte(`{`),
   385  				},
   386  				{
   387  					Type:  hclsyntax.TokenNewline,
   388  					Bytes: []byte("\n"),
   389  				},
   390  				{
   391  					Type:         hclsyntax.TokenIdent,
   392  					Bytes:        []byte(`bar`),
   393  					SpacesBefore: 2,
   394  				},
   395  				{
   396  					Type:         hclsyntax.TokenEqual,
   397  					Bytes:        []byte(`=`),
   398  					SpacesBefore: 1,
   399  				},
   400  				{
   401  					Type:         hclsyntax.TokenNumberLit,
   402  					Bytes:        []byte(`0`),
   403  					SpacesBefore: 1,
   404  				},
   405  				{
   406  					Type:  hclsyntax.TokenNewline,
   407  					Bytes: []byte("\n"),
   408  				},
   409  				{
   410  					Type:         hclsyntax.TokenIdent,
   411  					Bytes:        []byte(`foo`),
   412  					SpacesBefore: 2,
   413  				},
   414  				{
   415  					Type:         hclsyntax.TokenEqual,
   416  					Bytes:        []byte(`=`),
   417  					SpacesBefore: 1,
   418  				},
   419  				{
   420  					Type:         hclsyntax.TokenIdent,
   421  					Bytes:        []byte(`true`),
   422  					SpacesBefore: 1,
   423  				},
   424  				{
   425  					Type:  hclsyntax.TokenNewline,
   426  					Bytes: []byte("\n"),
   427  				},
   428  				{
   429  					Type:  hclsyntax.TokenCBrace,
   430  					Bytes: []byte(`}`),
   431  				},
   432  			},
   433  		},
   434  		{
   435  			cty.ObjectVal(map[string]cty.Value{
   436  				"foo bar": cty.True,
   437  			}),
   438  			Tokens{
   439  				{
   440  					Type:  hclsyntax.TokenOBrace,
   441  					Bytes: []byte(`{`),
   442  				},
   443  				{
   444  					Type:  hclsyntax.TokenNewline,
   445  					Bytes: []byte("\n"),
   446  				},
   447  				{
   448  					Type:         hclsyntax.TokenOQuote,
   449  					Bytes:        []byte(`"`),
   450  					SpacesBefore: 2,
   451  				},
   452  				{
   453  					Type:  hclsyntax.TokenQuotedLit,
   454  					Bytes: []byte(`foo bar`),
   455  				},
   456  				{
   457  					Type:  hclsyntax.TokenCQuote,
   458  					Bytes: []byte(`"`),
   459  				},
   460  				{
   461  					Type:         hclsyntax.TokenEqual,
   462  					Bytes:        []byte(`=`),
   463  					SpacesBefore: 1,
   464  				},
   465  				{
   466  					Type:         hclsyntax.TokenIdent,
   467  					Bytes:        []byte(`true`),
   468  					SpacesBefore: 1,
   469  				},
   470  				{
   471  					Type:  hclsyntax.TokenNewline,
   472  					Bytes: []byte("\n"),
   473  				},
   474  				{
   475  					Type:  hclsyntax.TokenCBrace,
   476  					Bytes: []byte(`}`),
   477  				},
   478  			},
   479  		},
   480  	}
   481  
   482  	for _, test := range tests {
   483  		t.Run(test.Val.GoString(), func(t *testing.T) {
   484  			got := TokensForValue(test.Val)
   485  
   486  			if !cmp.Equal(got, test.Want) {
   487  				diff := cmp.Diff(got, test.Want, cmp.Comparer(func(a, b []byte) bool {
   488  					return bytes.Equal(a, b)
   489  				}))
   490  				var gotBuf, wantBuf bytes.Buffer
   491  				got.WriteTo(&gotBuf)
   492  				test.Want.WriteTo(&wantBuf)
   493  				t.Errorf(
   494  					"wrong result\nvalue: %#v\ngot:   %s\nwant:  %s\ndiff:  %s",
   495  					test.Val, gotBuf.String(), wantBuf.String(), diff,
   496  				)
   497  			}
   498  		})
   499  	}
   500  }
   501  
   502  func TestTokensForTraversal(t *testing.T) {
   503  	tests := []struct {
   504  		Val  hcl.Traversal
   505  		Want Tokens
   506  	}{
   507  		{
   508  			hcl.Traversal{
   509  				hcl.TraverseRoot{Name: "root"},
   510  				hcl.TraverseAttr{Name: "attr"},
   511  				hcl.TraverseIndex{Key: cty.StringVal("index")},
   512  			},
   513  			Tokens{
   514  				{Type: hclsyntax.TokenIdent, Bytes: []byte("root")},
   515  				{Type: hclsyntax.TokenDot, Bytes: []byte(".")},
   516  				{Type: hclsyntax.TokenIdent, Bytes: []byte("attr")},
   517  				{Type: hclsyntax.TokenOBrack, Bytes: []byte{'['}},
   518  				{Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`)},
   519  				{Type: hclsyntax.TokenQuotedLit, Bytes: []byte("index")},
   520  				{Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)},
   521  				{Type: hclsyntax.TokenCBrack, Bytes: []byte{']'}},
   522  			},
   523  		},
   524  	}
   525  
   526  	for _, test := range tests {
   527  		got := TokensForTraversal(test.Val)
   528  
   529  		if !cmp.Equal(got, test.Want) {
   530  			diff := cmp.Diff(got, test.Want, cmp.Comparer(func(a, b []byte) bool {
   531  				return bytes.Equal(a, b)
   532  			}))
   533  			var gotBuf, wantBuf bytes.Buffer
   534  			got.WriteTo(&gotBuf)
   535  			test.Want.WriteTo(&wantBuf)
   536  			t.Errorf(
   537  				"wrong result\nvalue: %#v\ngot:   %s\nwant:  %s\ndiff:  %s",
   538  				test.Val, gotBuf.String(), wantBuf.String(), diff,
   539  			)
   540  		}
   541  	}
   542  }
   543  
   544  func TestTokensForTuple(t *testing.T) {
   545  	tests := map[string]struct {
   546  		Val  []Tokens
   547  		Want Tokens
   548  	}{
   549  		"no elements": {
   550  			nil,
   551  			Tokens{
   552  				{Type: hclsyntax.TokenOBrack, Bytes: []byte{'['}},
   553  				{Type: hclsyntax.TokenCBrack, Bytes: []byte{']'}},
   554  			},
   555  		},
   556  		"one element": {
   557  			[]Tokens{
   558  				TokensForValue(cty.StringVal("foo")),
   559  			},
   560  			Tokens{
   561  				{Type: hclsyntax.TokenOBrack, Bytes: []byte{'['}},
   562  				{Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`)},
   563  				{Type: hclsyntax.TokenQuotedLit, Bytes: []byte("foo")},
   564  				{Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)},
   565  				{Type: hclsyntax.TokenCBrack, Bytes: []byte{']'}},
   566  			},
   567  		},
   568  		"two elements": {
   569  			[]Tokens{
   570  				TokensForTraversal(hcl.Traversal{
   571  					hcl.TraverseRoot{Name: "root"},
   572  					hcl.TraverseAttr{Name: "attr"},
   573  				}),
   574  				TokensForValue(cty.StringVal("foo")),
   575  			},
   576  			Tokens{
   577  				{Type: hclsyntax.TokenOBrack, Bytes: []byte{'['}},
   578  				{Type: hclsyntax.TokenIdent, Bytes: []byte("root")},
   579  				{Type: hclsyntax.TokenDot, Bytes: []byte(".")},
   580  				{Type: hclsyntax.TokenIdent, Bytes: []byte("attr")},
   581  				{Type: hclsyntax.TokenComma, Bytes: []byte{','}},
   582  				{Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`), SpacesBefore: 1},
   583  				{Type: hclsyntax.TokenQuotedLit, Bytes: []byte("foo")},
   584  				{Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)},
   585  				{Type: hclsyntax.TokenCBrack, Bytes: []byte{']'}},
   586  			},
   587  		},
   588  	}
   589  
   590  	for name, test := range tests {
   591  		t.Run(name, func(t *testing.T) {
   592  			got := TokensForTuple(test.Val)
   593  
   594  			if !cmp.Equal(got, test.Want) {
   595  				diff := cmp.Diff(got, test.Want, cmp.Comparer(func(a, b []byte) bool {
   596  					return bytes.Equal(a, b)
   597  				}))
   598  				var gotBuf, wantBuf bytes.Buffer
   599  				got.WriteTo(&gotBuf)
   600  				test.Want.WriteTo(&wantBuf)
   601  				t.Errorf(
   602  					"wrong result\nvalue: %#v\ngot:   %s\nwant:  %s\ndiff:  %s",
   603  					test.Val, gotBuf.String(), wantBuf.String(), diff,
   604  				)
   605  			}
   606  		})
   607  	}
   608  }
   609  
   610  func TestTokensForObject(t *testing.T) {
   611  	tests := map[string]struct {
   612  		Val  []ObjectAttrTokens
   613  		Want Tokens
   614  	}{
   615  		"no attributes": {
   616  			nil,
   617  			Tokens{
   618  				{Type: hclsyntax.TokenOBrace, Bytes: []byte{'{'}},
   619  				{Type: hclsyntax.TokenCBrace, Bytes: []byte{'}'}},
   620  			},
   621  		},
   622  		"one attribute": {
   623  			[]ObjectAttrTokens{
   624  				{
   625  					Name: TokensForTraversal(hcl.Traversal{
   626  						hcl.TraverseRoot{Name: "bar"},
   627  					}),
   628  					Value: TokensForValue(cty.StringVal("baz")),
   629  				},
   630  			},
   631  			Tokens{
   632  				{Type: hclsyntax.TokenOBrace, Bytes: []byte{'{'}},
   633  				{Type: hclsyntax.TokenNewline, Bytes: []byte{'\n'}},
   634  				{Type: hclsyntax.TokenIdent, Bytes: []byte("bar"), SpacesBefore: 2},
   635  				{Type: hclsyntax.TokenEqual, Bytes: []byte{'='}, SpacesBefore: 1},
   636  				{Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`), SpacesBefore: 1},
   637  				{Type: hclsyntax.TokenQuotedLit, Bytes: []byte("baz")},
   638  				{Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)},
   639  				{Type: hclsyntax.TokenNewline, Bytes: []byte{'\n'}},
   640  				{Type: hclsyntax.TokenCBrace, Bytes: []byte{'}'}},
   641  			},
   642  		},
   643  		"two attributes": {
   644  			[]ObjectAttrTokens{
   645  				{
   646  					Name: TokensForTraversal(hcl.Traversal{
   647  						hcl.TraverseRoot{Name: "foo"},
   648  					}),
   649  					Value: TokensForTraversal(hcl.Traversal{
   650  						hcl.TraverseRoot{Name: "root"},
   651  						hcl.TraverseAttr{Name: "attr"},
   652  					}),
   653  				},
   654  				{
   655  					Name: TokensForTraversal(hcl.Traversal{
   656  						hcl.TraverseRoot{Name: "bar"},
   657  					}),
   658  					Value: TokensForValue(cty.StringVal("baz")),
   659  				},
   660  			},
   661  			Tokens{
   662  				{Type: hclsyntax.TokenOBrace, Bytes: []byte{'{'}},
   663  				{Type: hclsyntax.TokenNewline, Bytes: []byte{'\n'}},
   664  				{Type: hclsyntax.TokenIdent, Bytes: []byte("foo"), SpacesBefore: 2},
   665  				{Type: hclsyntax.TokenEqual, Bytes: []byte{'='}, SpacesBefore: 1},
   666  				{Type: hclsyntax.TokenIdent, Bytes: []byte("root"), SpacesBefore: 1},
   667  				{Type: hclsyntax.TokenDot, Bytes: []byte(".")},
   668  				{Type: hclsyntax.TokenIdent, Bytes: []byte("attr")},
   669  				{Type: hclsyntax.TokenNewline, Bytes: []byte{'\n'}},
   670  				{Type: hclsyntax.TokenIdent, Bytes: []byte("bar"), SpacesBefore: 2},
   671  				{Type: hclsyntax.TokenEqual, Bytes: []byte{'='}, SpacesBefore: 1},
   672  				{Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`), SpacesBefore: 1},
   673  				{Type: hclsyntax.TokenQuotedLit, Bytes: []byte("baz")},
   674  				{Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)},
   675  				{Type: hclsyntax.TokenNewline, Bytes: []byte{'\n'}},
   676  				{Type: hclsyntax.TokenCBrace, Bytes: []byte{'}'}},
   677  			},
   678  		},
   679  	}
   680  
   681  	for name, test := range tests {
   682  		t.Run(name, func(t *testing.T) {
   683  			got := TokensForObject(test.Val)
   684  
   685  			if !cmp.Equal(got, test.Want) {
   686  				diff := cmp.Diff(got, test.Want, cmp.Comparer(func(a, b []byte) bool {
   687  					return bytes.Equal(a, b)
   688  				}))
   689  				var gotBuf, wantBuf bytes.Buffer
   690  				got.WriteTo(&gotBuf)
   691  				test.Want.WriteTo(&wantBuf)
   692  				t.Errorf(
   693  					"wrong result\nvalue: %#v\ngot:   %s\nwant:  %s\ndiff:  %s",
   694  					test.Val, gotBuf.String(), wantBuf.String(), diff,
   695  				)
   696  			}
   697  		})
   698  	}
   699  }
   700  
   701  func TestTokensForFunctionCall(t *testing.T) {
   702  	tests := map[string]struct {
   703  		FuncName string
   704  		Val      []Tokens
   705  		Want     Tokens
   706  	}{
   707  		"no arguments": {
   708  			"uuid",
   709  			nil,
   710  			Tokens{
   711  				{Type: hclsyntax.TokenIdent, Bytes: []byte("uuid")},
   712  				{Type: hclsyntax.TokenOParen, Bytes: []byte{'('}},
   713  				{Type: hclsyntax.TokenCParen, Bytes: []byte(")")},
   714  			},
   715  		},
   716  		"one argument": {
   717  			"strlen",
   718  			[]Tokens{
   719  				TokensForValue(cty.StringVal("hello")),
   720  			},
   721  			Tokens{
   722  				{Type: hclsyntax.TokenIdent, Bytes: []byte("strlen")},
   723  				{Type: hclsyntax.TokenOParen, Bytes: []byte{'('}},
   724  				{Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`)},
   725  				{Type: hclsyntax.TokenQuotedLit, Bytes: []byte("hello")},
   726  				{Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)},
   727  				{Type: hclsyntax.TokenCParen, Bytes: []byte(")")},
   728  			},
   729  		},
   730  		"two arguments": {
   731  			"list",
   732  			[]Tokens{
   733  				TokensForIdentifier("string"),
   734  				TokensForIdentifier("int"),
   735  			},
   736  			Tokens{
   737  				{Type: hclsyntax.TokenIdent, Bytes: []byte("list")},
   738  				{Type: hclsyntax.TokenOParen, Bytes: []byte{'('}},
   739  				{Type: hclsyntax.TokenIdent, Bytes: []byte("string")},
   740  				{Type: hclsyntax.TokenComma, Bytes: []byte(",")},
   741  				{Type: hclsyntax.TokenIdent, Bytes: []byte("int"), SpacesBefore: 1},
   742  				{Type: hclsyntax.TokenCParen, Bytes: []byte(")")},
   743  			},
   744  		},
   745  	}
   746  
   747  	for name, test := range tests {
   748  		t.Run(name, func(t *testing.T) {
   749  			got := TokensForFunctionCall(test.FuncName, test.Val...)
   750  
   751  			if !cmp.Equal(got, test.Want) {
   752  				diff := cmp.Diff(got, test.Want, cmp.Comparer(func(a, b []byte) bool {
   753  					return bytes.Equal(a, b)
   754  				}))
   755  				var gotBuf, wantBuf bytes.Buffer
   756  				got.WriteTo(&gotBuf)
   757  				test.Want.WriteTo(&wantBuf)
   758  				t.Errorf(
   759  					"wrong result\nvalue: %#v\ngot:   %s\nwant:  %s\ndiff:  %s",
   760  					test.Val, gotBuf.String(), wantBuf.String(), diff,
   761  				)
   762  			}
   763  		})
   764  	}
   765  }
   766  
   767  func TestTokenGenerateConsistency(t *testing.T) {
   768  
   769  	bytesComparer := cmp.Comparer(func(a, b []byte) bool {
   770  		return bytes.Equal(a, b)
   771  	})
   772  
   773  	// This test verifies that different ways of generating equivalent token
   774  	// sequences all generate identical tokens, to help us keep them all in
   775  	// sync under future maintanence.
   776  
   777  	t.Run("tuple constructor", func(t *testing.T) {
   778  		tests := map[string]struct {
   779  			elems []cty.Value
   780  		}{
   781  			"no elements": {
   782  				nil,
   783  			},
   784  			"one element": {
   785  				[]cty.Value{
   786  					cty.StringVal("hello"),
   787  				},
   788  			},
   789  			"two elements": {
   790  				[]cty.Value{
   791  					cty.StringVal("hello"),
   792  					cty.StringVal("world"),
   793  				},
   794  			},
   795  		}
   796  
   797  		for name, test := range tests {
   798  			t.Run(name, func(t *testing.T) {
   799  				var listVal cty.Value
   800  				if len(test.elems) > 0 {
   801  					listVal = cty.ListVal(test.elems)
   802  				} else {
   803  					listVal = cty.ListValEmpty(cty.DynamicPseudoType)
   804  				}
   805  				fromListValue := TokensForValue(listVal)
   806  				fromTupleValue := TokensForValue(cty.TupleVal(test.elems))
   807  				elemTokens := make([]Tokens, len(test.elems))
   808  				for i, v := range test.elems {
   809  					elemTokens[i] = TokensForValue(v)
   810  				}
   811  				fromTupleTokens := TokensForTuple(elemTokens)
   812  
   813  				if diff := cmp.Diff(fromListValue, fromTupleTokens, bytesComparer); diff != "" {
   814  					t.Errorf("inconsistency between TokensForValue(list) and TokensForTuple\n%s", diff)
   815  				}
   816  				if diff := cmp.Diff(fromTupleValue, fromTupleTokens, bytesComparer); diff != "" {
   817  					t.Errorf("inconsistency between TokensForValue(tuple) and TokensForTuple\n%s", diff)
   818  				}
   819  
   820  			})
   821  		}
   822  	})
   823  
   824  	t.Run("object constructor", func(t *testing.T) {
   825  		tests := map[string]struct {
   826  			attrs map[string]cty.Value
   827  		}{
   828  			"no elements": {
   829  				nil,
   830  			},
   831  			"one element": {
   832  				map[string]cty.Value{
   833  					"greeting": cty.StringVal("hello"),
   834  				},
   835  			},
   836  			"two elements": {
   837  				map[string]cty.Value{
   838  					"greeting1": cty.StringVal("hello"),
   839  					"greeting2": cty.StringVal("world"),
   840  				},
   841  			},
   842  		}
   843  
   844  		for name, test := range tests {
   845  			t.Run(name, func(t *testing.T) {
   846  				var mapVal cty.Value
   847  				if len(test.attrs) > 0 {
   848  					mapVal = cty.MapVal(test.attrs)
   849  				} else {
   850  					mapVal = cty.MapValEmpty(cty.DynamicPseudoType)
   851  				}
   852  				fromMapValue := TokensForValue(mapVal)
   853  				fromObjectValue := TokensForValue(cty.ObjectVal(test.attrs))
   854  				attrTokens := make([]ObjectAttrTokens, 0, len(test.attrs))
   855  
   856  				// TokensForValue always writes the keys/attributes in cty's
   857  				// standard iteration order, but TokensForObject gives the
   858  				// caller direct control of the ordering. The result is
   859  				// therefore consistent only if the given attributes are
   860  				// pre-sorted into the same iteration order, which is a lexical
   861  				// sort by attribute name.
   862  				keys := make([]string, 0, len(test.attrs))
   863  				for k := range test.attrs {
   864  					keys = append(keys, k)
   865  				}
   866  				sort.Strings(keys)
   867  				for _, k := range keys {
   868  					v := test.attrs[k]
   869  					attrTokens = append(attrTokens, ObjectAttrTokens{
   870  						Name:  TokensForIdentifier(k),
   871  						Value: TokensForValue(v),
   872  					})
   873  				}
   874  				fromObjectTokens := TokensForObject(attrTokens)
   875  
   876  				if diff := cmp.Diff(fromMapValue, fromObjectTokens, bytesComparer); diff != "" {
   877  					t.Errorf("inconsistency between TokensForValue(map) and TokensForObject\n%s", diff)
   878  				}
   879  				if diff := cmp.Diff(fromObjectValue, fromObjectTokens, bytesComparer); diff != "" {
   880  					t.Errorf("inconsistency between TokensForValue(object) and TokensForObject\n%s", diff)
   881  				}
   882  			})
   883  		}
   884  	})
   885  }