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 }