1package stdlib 2 3import ( 4 "fmt" 5 "testing" 6 7 "github.com/zclconf/go-cty/cty" 8) 9 10func TestFormat(t *testing.T) { 11 tests := []struct { 12 Format cty.Value 13 Args []cty.Value 14 Want cty.Value 15 WantErr string 16 }{ 17 { 18 cty.StringVal(""), 19 nil, 20 cty.StringVal(""), 21 ``, 22 }, 23 { 24 cty.StringVal("hello"), 25 nil, 26 cty.StringVal("hello"), 27 ``, 28 }, 29 { 30 cty.StringVal("100%% successful"), 31 nil, 32 cty.StringVal("100% successful"), 33 ``, 34 }, 35 { 36 cty.StringVal("100%%"), 37 nil, 38 cty.StringVal("100%"), 39 ``, 40 }, 41 42 // Default formats 43 { 44 cty.StringVal("string %v"), 45 []cty.Value{cty.StringVal("hello")}, 46 cty.StringVal("string hello"), 47 ``, 48 }, 49 { 50 cty.StringVal("string %[2]v"), 51 []cty.Value{cty.True, cty.StringVal("hello")}, 52 cty.StringVal("string hello"), 53 ``, 54 }, 55 { 56 cty.StringVal("string %#v"), 57 []cty.Value{cty.StringVal("hello")}, 58 cty.StringVal(`string "hello"`), 59 ``, 60 }, 61 { 62 cty.StringVal("number %v"), 63 []cty.Value{cty.NumberIntVal(2)}, 64 cty.StringVal("number 2"), 65 ``, 66 }, 67 { 68 cty.StringVal("number %#v"), 69 []cty.Value{cty.NumberIntVal(2)}, 70 cty.StringVal("number 2"), 71 ``, 72 }, 73 { 74 cty.StringVal("bool %v"), 75 []cty.Value{cty.True}, 76 cty.StringVal("bool true"), 77 ``, 78 }, 79 { 80 cty.StringVal("bool %#v"), 81 []cty.Value{cty.True}, 82 cty.StringVal("bool true"), 83 ``, 84 }, 85 { 86 cty.StringVal("object %v"), 87 []cty.Value{cty.EmptyObjectVal}, 88 cty.StringVal("object {}"), 89 ``, 90 }, 91 { 92 cty.StringVal("tuple %v"), 93 []cty.Value{cty.EmptyTupleVal}, 94 cty.StringVal("tuple []"), 95 ``, 96 }, 97 { 98 cty.StringVal("tuple with unknown %v"), 99 []cty.Value{cty.TupleVal([]cty.Value{ 100 cty.UnknownVal(cty.String), 101 })}, 102 cty.UnknownVal(cty.String), 103 ``, 104 }, 105 { 106 cty.StringVal("%%%v"), 107 []cty.Value{cty.False}, 108 cty.StringVal("%false"), 109 ``, 110 }, 111 { 112 cty.StringVal("%v"), 113 []cty.Value{cty.NullVal(cty.Bool)}, 114 cty.StringVal("null"), 115 ``, 116 }, 117 118 // Strings 119 { 120 cty.StringVal("Hello, %s!"), 121 []cty.Value{cty.StringVal("Ermintrude")}, 122 cty.StringVal("Hello, Ermintrude!"), 123 ``, 124 }, 125 { 126 cty.StringVal("Hello, %[2]s!"), 127 []cty.Value{cty.StringVal("Stephen"), cty.StringVal("Ermintrude")}, 128 cty.StringVal("Hello, Ermintrude!"), 129 ``, 130 }, 131 { 132 cty.StringVal("Hello, %q... if that _is_ your real name!"), 133 []cty.Value{cty.StringVal("Ermintrude")}, 134 cty.StringVal(`Hello, "Ermintrude"... if that _is_ your real name!`), 135 ``, 136 }, 137 { 138 cty.StringVal("This statement is %s"), 139 []cty.Value{cty.False}, 140 cty.StringVal("This statement is false"), 141 ``, 142 }, 143 { 144 cty.StringVal("This statement is %q"), 145 []cty.Value{cty.False}, 146 cty.StringVal(`This statement is "false"`), 147 ``, 148 }, 149 { 150 cty.StringVal("%s"), 151 []cty.Value{cty.NullVal(cty.String)}, 152 cty.NilVal, 153 `unsupported value for "%s" at 0: null value cannot be formatted`, 154 }, 155 { 156 cty.StringVal("%10s"), 157 []cty.Value{cty.StringVal("hello")}, 158 cty.StringVal(` hello`), 159 ``, 160 }, 161 { 162 cty.StringVal("%-10s"), 163 []cty.Value{cty.StringVal("hello")}, 164 cty.StringVal(`hello `), 165 ``, 166 }, 167 { 168 cty.StringVal("%4s"), 169 []cty.Value{cty.StringVal("")}, 170 cty.StringVal(` `), // three spaces because this emoji sequence is a single grapheme cluster 171 ``, 172 }, 173 { 174 cty.StringVal("%-4s"), 175 []cty.Value{cty.StringVal("")}, 176 cty.StringVal(` `), // three spaces because this emoji sequence is a single grapheme cluster 177 ``, 178 }, 179 { 180 cty.StringVal("%q"), 181 []cty.Value{cty.StringVal("")}, 182 cty.StringVal(`""`), 183 ``, 184 }, 185 { 186 cty.StringVal("%6q"), 187 []cty.Value{cty.StringVal("")}, 188 cty.StringVal(` ""`), // three spaces because this emoji sequence is a single grapheme cluster 189 ``, 190 }, 191 { 192 cty.StringVal("%-6q"), 193 []cty.Value{cty.StringVal("")}, 194 cty.StringVal(`"" `), // three spaces because this emoji sequence is a single grapheme cluster 195 ``, 196 }, 197 { 198 cty.StringVal("%.2s"), 199 []cty.Value{cty.StringVal("hello")}, 200 cty.StringVal(`he`), 201 ``, 202 }, 203 { 204 cty.StringVal("%.2q"), 205 []cty.Value{cty.StringVal("hello")}, 206 cty.StringVal(`"he"`), 207 ``, 208 }, 209 { 210 cty.StringVal("%.5s"), 211 []cty.Value{cty.StringVal("日本語日本語")}, 212 cty.StringVal(`日本語日本`), 213 ``, 214 }, 215 { 216 cty.StringVal("%.1q"), 217 []cty.Value{cty.StringVal("日本語日本語")}, 218 cty.StringVal(`"日"`), 219 ``, 220 }, 221 { 222 cty.StringVal("%.10s"), 223 []cty.Value{cty.StringVal("hello")}, 224 cty.StringVal(`hello`), 225 ``, 226 }, 227 { 228 cty.StringVal("%4.2s"), 229 []cty.Value{cty.StringVal("hello")}, 230 cty.StringVal(` he`), 231 ``, 232 }, 233 { 234 cty.StringVal("%6.2q"), 235 []cty.Value{cty.StringVal("hello")}, 236 cty.StringVal(` "he"`), 237 ``, 238 }, 239 { 240 cty.StringVal("%-4.2s"), 241 []cty.Value{cty.StringVal("hello")}, 242 cty.StringVal(`he `), 243 ``, 244 }, 245 { 246 cty.StringVal("%q"), 247 []cty.Value{cty.StringVal("Hello\nWorld")}, 248 cty.StringVal(`"Hello\nWorld"`), 249 ``, 250 }, 251 252 // Booleans 253 { 254 cty.StringVal("This statement is %t"), 255 []cty.Value{cty.False}, 256 cty.StringVal("This statement is false"), 257 ``, 258 }, 259 { 260 cty.StringVal("This statement is %[2]t"), 261 []cty.Value{cty.True, cty.False}, 262 cty.StringVal("This statement is false"), 263 ``, 264 }, 265 { 266 cty.StringVal("This statement is %t"), 267 []cty.Value{cty.True}, 268 cty.StringVal("This statement is true"), 269 ``, 270 }, 271 { 272 cty.StringVal("This statement is %t"), 273 []cty.Value{cty.StringVal("false")}, 274 cty.StringVal("This statement is false"), 275 ``, 276 }, 277 278 // Integer Numbers 279 { 280 cty.StringVal("%d green bottles standing on the wall"), 281 []cty.Value{cty.NumberIntVal(10)}, 282 cty.StringVal("10 green bottles standing on the wall"), 283 ``, 284 }, 285 { 286 cty.StringVal("%[2]d things"), 287 []cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(10)}, 288 cty.StringVal("10 things"), 289 ``, 290 }, 291 { 292 cty.StringVal("%+d green bottles standing on the wall"), 293 []cty.Value{cty.NumberIntVal(10)}, 294 cty.StringVal("+10 green bottles standing on the wall"), 295 ``, 296 }, 297 { 298 cty.StringVal("% d green bottles standing on the wall"), 299 []cty.Value{cty.NumberIntVal(10)}, 300 cty.StringVal(" 10 green bottles standing on the wall"), 301 ``, 302 }, 303 { 304 cty.StringVal("%5d green bottles standing on the wall"), 305 []cty.Value{cty.NumberIntVal(10)}, 306 cty.StringVal(" 10 green bottles standing on the wall"), 307 ``, 308 }, 309 { 310 cty.StringVal("%-5d green bottles standing on the wall"), 311 []cty.Value{cty.NumberIntVal(10)}, 312 cty.StringVal("10 green bottles standing on the wall"), 313 ``, 314 }, 315 { 316 cty.StringVal("%d green bottles standing on the wall"), 317 []cty.Value{cty.True}, 318 cty.NilVal, 319 `unsupported value for "%d" at 0: number required`, 320 }, 321 { 322 cty.StringVal("%b"), 323 []cty.Value{cty.NumberIntVal(5)}, 324 cty.StringVal("101"), 325 ``, 326 }, 327 { 328 cty.StringVal("%o"), 329 []cty.Value{cty.NumberIntVal(9)}, 330 cty.StringVal("11"), 331 ``, 332 }, 333 { 334 cty.StringVal("%x"), 335 []cty.Value{cty.NumberIntVal(254)}, 336 cty.StringVal("fe"), 337 ``, 338 }, 339 { 340 cty.StringVal("%X"), 341 []cty.Value{cty.NumberIntVal(254)}, 342 cty.StringVal("FE"), 343 ``, 344 }, 345 346 // Floating-point numbers 347 { 348 cty.StringVal("%f things"), 349 []cty.Value{cty.NumberIntVal(10)}, 350 cty.StringVal("10.000000 things"), 351 ``, 352 }, 353 { 354 cty.StringVal("%[2]f things"), 355 []cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(10)}, 356 cty.StringVal("10.000000 things"), 357 ``, 358 }, 359 { 360 cty.StringVal("%+f things"), 361 []cty.Value{cty.NumberIntVal(10)}, 362 cty.StringVal("+10.000000 things"), 363 ``, 364 }, 365 { 366 cty.StringVal("% f things"), 367 []cty.Value{cty.NumberIntVal(10)}, 368 cty.StringVal(" 10.000000 things"), 369 ``, 370 }, 371 { 372 cty.StringVal("%+f things"), 373 []cty.Value{cty.NumberIntVal(-10)}, 374 cty.StringVal("-10.000000 things"), 375 ``, 376 }, 377 { 378 cty.StringVal("% f things"), 379 []cty.Value{cty.NumberIntVal(-10)}, 380 cty.StringVal("-10.000000 things"), 381 ``, 382 }, 383 { 384 cty.StringVal("%f things"), 385 []cty.Value{cty.StringVal("100000000000000000000000000000000000001")}, 386 cty.StringVal("100000000000000000000000000000000000001.000000 things"), 387 ``, 388 }, 389 { 390 cty.StringVal("%f things"), 391 []cty.Value{cty.StringVal("1.00000000000000000000000000000000000001")}, 392 cty.StringVal("1.000000 things"), 393 ``, 394 }, 395 { 396 cty.StringVal("%.4f things"), 397 []cty.Value{cty.StringVal("1.00000000000000000000000000000000000001")}, 398 cty.StringVal("1.0000 things"), 399 ``, 400 }, 401 { 402 cty.StringVal("%.1f things"), 403 []cty.Value{cty.StringVal("1.06")}, 404 cty.StringVal("1.1 things"), 405 ``, 406 }, 407 { 408 cty.StringVal("%e things"), 409 []cty.Value{cty.NumberIntVal(1000)}, 410 cty.StringVal("1.000000e+03 things"), 411 ``, 412 }, 413 { 414 cty.StringVal("%E things"), 415 []cty.Value{cty.NumberIntVal(1000)}, 416 cty.StringVal("1.000000E+03 things"), 417 ``, 418 }, 419 { 420 cty.StringVal("%g things"), 421 []cty.Value{cty.NumberIntVal(1000)}, 422 cty.StringVal("1000 things"), 423 ``, 424 }, 425 { 426 cty.StringVal("%G things"), 427 []cty.Value{cty.NumberIntVal(1000)}, 428 cty.StringVal("1000 things"), 429 ``, 430 }, 431 { 432 cty.StringVal("%g things"), 433 []cty.Value{cty.StringVal("0.00000000000000000000001")}, 434 cty.StringVal("1e-23 things"), 435 ``, 436 }, 437 { 438 cty.StringVal("%G things"), 439 []cty.Value{cty.StringVal("0.00000000000000000000001")}, 440 cty.StringVal("1E-23 things"), 441 ``, 442 }, 443 444 // Unknowns 445 { 446 cty.UnknownVal(cty.String), 447 []cty.Value{cty.True}, 448 cty.UnknownVal(cty.String), 449 ``, 450 }, 451 { 452 cty.UnknownVal(cty.Bool), 453 []cty.Value{cty.True}, 454 cty.NilVal, 455 `string required, but received bool`, 456 }, 457 { 458 cty.StringVal("Hello, %s!"), 459 []cty.Value{cty.UnknownVal(cty.String)}, 460 cty.UnknownVal(cty.String), 461 ``, 462 }, 463 { 464 cty.StringVal("Hello, %[2]s!"), 465 []cty.Value{cty.UnknownVal(cty.String), cty.StringVal("Ermintrude")}, 466 cty.UnknownVal(cty.String), 467 ``, 468 }, 469 470 // Invalids 471 { 472 cty.StringVal("%s is not in the args list"), 473 nil, 474 cty.NilVal, 475 `not enough arguments for "%s" at 0: need index 1 but have 0 total`, 476 }, 477 { 478 cty.StringVal("%[3]s is not in the args list"), 479 []cty.Value{cty.True, cty.True}, 480 cty.NilVal, 481 `not enough arguments for "%[3]s" at 0: need index 3 but have 2 total`, 482 }, 483 { 484 cty.StringVal("%[0]s is not valid because args are 1-based"), 485 []cty.Value{cty.True, cty.True}, 486 cty.NilVal, 487 `unrecognized format character '0' at offset 2`, 488 }, 489 { 490 cty.StringVal("%v %v %v"), 491 []cty.Value{cty.True, cty.True}, 492 cty.NilVal, 493 `not enough arguments for "%v" at 6: need index 3 but have 2 total`, 494 }, 495 { 496 cty.StringVal("%z is not a valid sequence"), 497 []cty.Value{cty.NumberIntVal(10)}, 498 cty.NilVal, 499 `unsupported format verb 'z' in "%z" at offset 0`, 500 }, 501 { 502 cty.StringVal("%#z is not a valid sequence"), 503 []cty.Value{cty.NumberIntVal(10)}, 504 cty.NilVal, 505 `unsupported format verb 'z' in "%#z" at offset 0`, 506 }, 507 { 508 cty.StringVal("%012z is not a valid sequence"), 509 []cty.Value{cty.NumberIntVal(10)}, 510 cty.NilVal, 511 `unsupported format verb 'z' in "%012z" at offset 0`, 512 }, 513 { 514 cty.StringVal("%☠ is not a valid sequence"), 515 []cty.Value{cty.NumberIntVal(10)}, 516 cty.NilVal, 517 `unrecognized format character '☠' at offset 1`, 518 }, 519 { 520 cty.StringVal("% is not a valid sequence"), 521 []cty.Value{cty.NumberIntVal(10)}, 522 cty.NilVal, 523 `unrecognized format character '' at offset 1`, // since this is a grammar-level error, we don't get the full grapheme cluster 524 }, 525 { 526 cty.NullVal(cty.String), 527 []cty.Value{cty.NumberIntVal(10)}, 528 cty.NilVal, 529 `argument must not be null`, 530 }, 531 { 532 cty.StringVal("no format verbs at all"), 533 []cty.Value{cty.NumberIntVal(10)}, 534 cty.NilVal, 535 `too many arguments; no verbs in format string`, 536 }, 537 { 538 cty.StringVal("only one verb %d"), 539 []cty.Value{cty.NumberIntVal(10), cty.NumberIntVal(11)}, 540 cty.NilVal, 541 `too many arguments; only 1 used by format string`, 542 }, 543 544 { 545 cty.StringVal("hello %s").Mark(1), 546 []cty.Value{cty.StringVal("world")}, 547 cty.StringVal("hello world").Mark(1), 548 ``, 549 }, 550 { 551 cty.StringVal("hello %s"), 552 []cty.Value{cty.StringVal("world").Mark(1)}, 553 cty.StringVal("hello world").Mark(1), 554 ``, 555 }, 556 { 557 cty.StringVal("hello %s").Mark(0), 558 []cty.Value{cty.StringVal("world").Mark(1)}, 559 cty.StringVal("hello world").WithMarks(cty.NewValueMarks(0, 1)), 560 ``, 561 }, 562 } 563 564 for i, test := range tests { 565 t.Run(fmt.Sprintf("%02d-%#v", i, test.Format), func(t *testing.T) { 566 got, err := Format(test.Format, test.Args...) 567 568 if test.WantErr == "" { 569 if err != nil { 570 t.Fatalf("unexpected error: %s", err) 571 } 572 } else { 573 if err == nil { 574 t.Fatalf("no error; want %q", test.WantErr) 575 } 576 errStr := err.Error() 577 if errStr != test.WantErr { 578 t.Fatalf("wrong error\ngot: %s\nwant: %s", errStr, test.WantErr) 579 } 580 return 581 } 582 583 if test.Want != cty.NilVal { 584 if !got.RawEquals(test.Want) { 585 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 586 } 587 } else { 588 t.Errorf("unexpected success %#v; want error", got) 589 } 590 }) 591 } 592} 593func TestFormatList(t *testing.T) { 594 tests := []struct { 595 Format cty.Value 596 Args []cty.Value 597 Want cty.Value 598 WantErr string 599 }{ 600 0: { 601 cty.StringVal(""), 602 nil, 603 cty.ListVal([]cty.Value{ 604 cty.StringVal(""), 605 }), 606 ``, 607 }, 608 1: { 609 cty.StringVal("hello"), 610 nil, 611 cty.ListVal([]cty.Value{ 612 cty.StringVal("hello"), 613 }), 614 ``, 615 }, 616 2: { 617 cty.StringVal("100%% successful"), 618 nil, 619 cty.ListVal([]cty.Value{ 620 cty.StringVal("100% successful"), 621 }), 622 ``, 623 }, 624 3: { 625 cty.StringVal("100%%"), 626 nil, 627 cty.ListVal([]cty.Value{ 628 cty.StringVal("100%"), 629 }), 630 ``, 631 }, 632 633 4: { 634 cty.StringVal("%s"), 635 []cty.Value{cty.StringVal("hello")}, 636 cty.ListVal([]cty.Value{ 637 cty.StringVal("hello"), 638 }), 639 ``, 640 }, 641 5: { 642 cty.StringVal("%s"), 643 []cty.Value{ 644 cty.ListVal([]cty.Value{ 645 cty.StringVal("hello"), 646 }), 647 }, 648 cty.ListVal([]cty.Value{ 649 cty.StringVal("hello"), 650 }), 651 ``, 652 }, 653 6: { 654 cty.StringVal("%s"), 655 []cty.Value{ 656 cty.ListVal([]cty.Value{ 657 cty.StringVal("hello"), 658 cty.StringVal("world"), 659 }), 660 }, 661 cty.ListVal([]cty.Value{ 662 cty.StringVal("hello"), 663 cty.StringVal("world"), 664 }), 665 ``, 666 }, 667 7: { 668 cty.StringVal("%s %s"), 669 []cty.Value{ 670 cty.ListVal([]cty.Value{ 671 cty.StringVal("hello"), 672 cty.StringVal("goodbye"), 673 }), 674 cty.ListVal([]cty.Value{ 675 cty.StringVal("world"), 676 cty.StringVal("universe"), 677 }), 678 }, 679 cty.ListVal([]cty.Value{ 680 cty.StringVal("hello world"), 681 cty.StringVal("goodbye universe"), 682 }), 683 ``, 684 }, 685 8: { 686 cty.StringVal("%s %s"), 687 []cty.Value{ 688 cty.ListVal([]cty.Value{ 689 cty.StringVal("hello"), 690 cty.StringVal("goodbye"), 691 }), 692 cty.StringVal("world"), 693 }, 694 cty.ListVal([]cty.Value{ 695 cty.StringVal("hello world"), 696 cty.StringVal("goodbye world"), 697 }), 698 ``, 699 }, 700 9: { 701 cty.StringVal("%s %s"), 702 []cty.Value{ 703 cty.StringVal("hello"), 704 cty.ListVal([]cty.Value{ 705 cty.StringVal("world"), 706 cty.StringVal("universe"), 707 }), 708 }, 709 cty.ListVal([]cty.Value{ 710 cty.StringVal("hello world"), 711 cty.StringVal("hello universe"), 712 }), 713 ``, 714 }, 715 10: { 716 cty.StringVal("%s %s"), 717 []cty.Value{ 718 cty.ListVal([]cty.Value{ 719 cty.StringVal("hello"), 720 cty.StringVal("goodbye"), 721 }), 722 cty.ListVal([]cty.Value{ 723 cty.StringVal("world"), 724 }), 725 }, 726 cty.ListValEmpty(cty.String), 727 `argument 2 has length 1, which is inconsistent with argument 1 of length 2`, 728 }, 729 11: { 730 cty.StringVal("%s"), 731 []cty.Value{cty.EmptyObjectVal}, 732 cty.ListValEmpty(cty.String), 733 `error on format iteration 0: unsupported value for "%s" at 0: string required`, 734 }, 735 12: { 736 cty.StringVal("%v"), 737 []cty.Value{cty.EmptyTupleVal}, 738 cty.ListValEmpty(cty.String), // no items because our given tuple is empty 739 ``, 740 }, 741 13: { 742 cty.StringVal("%v"), 743 []cty.Value{cty.NullVal(cty.List(cty.String))}, 744 cty.ListVal([]cty.Value{ 745 cty.StringVal("null"), // we treat a null list like a list whose elements are all null 746 }), 747 ``, 748 }, 749 750 14: { 751 cty.UnknownVal(cty.String), 752 []cty.Value{ 753 cty.True, 754 }, 755 cty.UnknownVal(cty.List(cty.String)), 756 ``, 757 }, 758 15: { 759 cty.StringVal("%v"), 760 []cty.Value{ 761 cty.UnknownVal(cty.String), 762 }, 763 cty.ListVal([]cty.Value{ 764 cty.UnknownVal(cty.String), 765 }), 766 ``, 767 }, 768 16: { 769 cty.StringVal("%v"), 770 []cty.Value{ 771 cty.NullVal(cty.String), 772 }, 773 cty.ListVal([]cty.Value{ 774 cty.StringVal("null"), 775 }), 776 ``, 777 }, 778 17: { 779 cty.StringVal("%v"), 780 []cty.Value{ 781 cty.UnknownVal(cty.List(cty.String)), 782 }, 783 cty.UnknownVal(cty.List(cty.String)), 784 ``, 785 }, 786 18: { 787 cty.StringVal("%v"), 788 []cty.Value{ 789 cty.ListVal([]cty.Value{ 790 cty.TupleVal([]cty.Value{cty.StringVal("hello")}), 791 cty.TupleVal([]cty.Value{cty.UnknownVal(cty.String)}), 792 cty.TupleVal([]cty.Value{cty.StringVal("world")}), 793 }), 794 }, 795 cty.ListVal([]cty.Value{ 796 cty.StringVal(`["hello"]`), 797 cty.UnknownVal(cty.String), 798 cty.StringVal(`["world"]`), 799 }), 800 ``, 801 }, 802 19: { 803 cty.StringVal("%v"), 804 []cty.Value{ 805 cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})), 806 }, 807 cty.UnknownVal(cty.List(cty.String)), 808 ``, 809 }, 810 20: { 811 cty.StringVal("%s %s"), 812 []cty.Value{ 813 cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})), 814 cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.String})), 815 }, 816 cty.UnknownVal(cty.List(cty.String)), 817 `argument 2 has length 2, which is inconsistent with argument 1 of length 1`, 818 }, 819 21: { 820 cty.StringVal("%s %s"), 821 []cty.Value{ 822 cty.ListVal([]cty.Value{cty.StringVal("hi")}), 823 cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.String})), 824 }, 825 cty.UnknownVal(cty.List(cty.String)), 826 `argument 2 has length 2, which is inconsistent with argument 1 of length 1`, 827 }, 828 22: { 829 cty.StringVal("%v"), 830 []cty.Value{ 831 cty.SetVal([]cty.Value{ 832 cty.StringVal("hello"), 833 cty.UnknownVal(cty.String), 834 }), 835 }, 836 cty.UnknownVal(cty.List(cty.String)), 837 ``, 838 }, 839 } 840 841 for i, test := range tests { 842 t.Run(fmt.Sprintf("%02d-%#v", i, test.Format), func(t *testing.T) { 843 got, err := FormatList(test.Format, test.Args...) 844 845 if test.WantErr == "" { 846 if err != nil { 847 t.Fatalf("unexpected error: %s", err) 848 } 849 } else { 850 if err == nil { 851 t.Fatalf("no error; want %q", test.WantErr) 852 } 853 errStr := err.Error() 854 if errStr != test.WantErr { 855 t.Fatalf("wrong error\ngot: %s\nwant: %s", errStr, test.WantErr) 856 } 857 return 858 } 859 860 if test.Want != cty.NilVal { 861 if !got.RawEquals(test.Want) { 862 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 863 } 864 } else { 865 t.Errorf("unexpected success %#v; want error", got) 866 } 867 }) 868 } 869} 870