github.com/xsb/terraform@v0.6.13-0.20160314145438-fe415c2f09d7/config/interpolate_funcs_test.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "reflect" 8 "testing" 9 10 "github.com/hashicorp/hil" 11 "github.com/hashicorp/hil/ast" 12 ) 13 14 func TestInterpolateFuncCompact(t *testing.T) { 15 testFunction(t, testFunctionConfig{ 16 Cases: []testFunctionCase{ 17 // empty string within array 18 { 19 `${compact(split(",", "a,,b"))}`, 20 NewStringList([]string{"a", "b"}).String(), 21 false, 22 }, 23 24 // empty string at the end of array 25 { 26 `${compact(split(",", "a,b,"))}`, 27 NewStringList([]string{"a", "b"}).String(), 28 false, 29 }, 30 31 // single empty string 32 { 33 `${compact(split(",", ""))}`, 34 NewStringList([]string{}).String(), 35 false, 36 }, 37 }, 38 }) 39 } 40 41 func TestInterpolateFuncCidrHost(t *testing.T) { 42 testFunction(t, testFunctionConfig{ 43 Cases: []testFunctionCase{ 44 { 45 `${cidrhost("192.168.1.0/24", 5)}`, 46 "192.168.1.5", 47 false, 48 }, 49 { 50 `${cidrhost("192.168.1.0/30", 255)}`, 51 nil, 52 true, // 255 doesn't fit in two bits 53 }, 54 { 55 `${cidrhost("not-a-cidr", 6)}`, 56 nil, 57 true, // not a valid CIDR mask 58 }, 59 { 60 `${cidrhost("10.256.0.0/8", 6)}`, 61 nil, 62 true, // can't have an octet >255 63 }, 64 }, 65 }) 66 } 67 68 func TestInterpolateFuncCidrNetmask(t *testing.T) { 69 testFunction(t, testFunctionConfig{ 70 Cases: []testFunctionCase{ 71 { 72 `${cidrnetmask("192.168.1.0/24")}`, 73 "255.255.255.0", 74 false, 75 }, 76 { 77 `${cidrnetmask("192.168.1.0/32")}`, 78 "255.255.255.255", 79 false, 80 }, 81 { 82 `${cidrnetmask("0.0.0.0/0")}`, 83 "0.0.0.0", 84 false, 85 }, 86 { 87 // This doesn't really make sense for IPv6 networks 88 // but it ought to do something sensible anyway. 89 `${cidrnetmask("1::/64")}`, 90 "ffff:ffff:ffff:ffff::", 91 false, 92 }, 93 { 94 `${cidrnetmask("not-a-cidr")}`, 95 nil, 96 true, // not a valid CIDR mask 97 }, 98 { 99 `${cidrnetmask("10.256.0.0/8")}`, 100 nil, 101 true, // can't have an octet >255 102 }, 103 }, 104 }) 105 } 106 107 func TestInterpolateFuncCidrSubnet(t *testing.T) { 108 testFunction(t, testFunctionConfig{ 109 Cases: []testFunctionCase{ 110 { 111 `${cidrsubnet("192.168.2.0/20", 4, 6)}`, 112 "192.168.6.0/24", 113 false, 114 }, 115 { 116 `${cidrsubnet("fe80::/48", 16, 6)}`, 117 "fe80:0:0:6::/64", 118 false, 119 }, 120 { 121 // IPv4 address encoded in IPv6 syntax gets normalized 122 `${cidrsubnet("::ffff:192.168.0.0/112", 8, 6)}`, 123 "192.168.6.0/24", 124 false, 125 }, 126 { 127 `${cidrsubnet("192.168.0.0/30", 4, 6)}`, 128 nil, 129 true, // not enough bits left 130 }, 131 { 132 `${cidrsubnet("192.168.0.0/16", 2, 16)}`, 133 nil, 134 true, // can't encode 16 in 2 bits 135 }, 136 { 137 `${cidrsubnet("not-a-cidr", 4, 6)}`, 138 nil, 139 true, // not a valid CIDR mask 140 }, 141 { 142 `${cidrsubnet("10.256.0.0/8", 4, 6)}`, 143 nil, 144 true, // can't have an octet >255 145 }, 146 }, 147 }) 148 } 149 150 func TestInterpolateFuncCoalesce(t *testing.T) { 151 testFunction(t, testFunctionConfig{ 152 Cases: []testFunctionCase{ 153 { 154 `${coalesce("first", "second", "third")}`, 155 "first", 156 false, 157 }, 158 { 159 `${coalesce("", "second", "third")}`, 160 "second", 161 false, 162 }, 163 { 164 `${coalesce("", "", "")}`, 165 "", 166 false, 167 }, 168 { 169 `${coalesce("foo")}`, 170 nil, 171 true, 172 }, 173 }, 174 }) 175 } 176 177 func TestInterpolateFuncDeprecatedConcat(t *testing.T) { 178 testFunction(t, testFunctionConfig{ 179 Cases: []testFunctionCase{ 180 { 181 `${concat("foo", "bar")}`, 182 "foobar", 183 false, 184 }, 185 186 { 187 `${concat("foo")}`, 188 "foo", 189 false, 190 }, 191 192 { 193 `${concat()}`, 194 nil, 195 true, 196 }, 197 }, 198 }) 199 } 200 201 func TestInterpolateFuncConcat(t *testing.T) { 202 testFunction(t, testFunctionConfig{ 203 Cases: []testFunctionCase{ 204 // String + list 205 { 206 `${concat("a", split(",", "b,c"))}`, 207 NewStringList([]string{"a", "b", "c"}).String(), 208 false, 209 }, 210 211 // List + string 212 { 213 `${concat(split(",", "a,b"), "c")}`, 214 NewStringList([]string{"a", "b", "c"}).String(), 215 false, 216 }, 217 218 // Single list 219 { 220 `${concat(split(",", ",foo,"))}`, 221 NewStringList([]string{"", "foo", ""}).String(), 222 false, 223 }, 224 { 225 `${concat(split(",", "a,b,c"))}`, 226 NewStringList([]string{"a", "b", "c"}).String(), 227 false, 228 }, 229 230 // Two lists 231 { 232 `${concat(split(",", "a,b,c"), split(",", "d,e"))}`, 233 NewStringList([]string{"a", "b", "c", "d", "e"}).String(), 234 false, 235 }, 236 // Two lists with different separators 237 { 238 `${concat(split(",", "a,b,c"), split(" ", "d e"))}`, 239 NewStringList([]string{"a", "b", "c", "d", "e"}).String(), 240 false, 241 }, 242 243 // More lists 244 { 245 `${concat(split(",", "a,b"), split(",", "c,d"), split(",", "e,f"), split(",", "0,1"))}`, 246 NewStringList([]string{"a", "b", "c", "d", "e", "f", "0", "1"}).String(), 247 false, 248 }, 249 }, 250 }) 251 } 252 253 func TestInterpolateFuncFile(t *testing.T) { 254 tf, err := ioutil.TempFile("", "tf") 255 if err != nil { 256 t.Fatalf("err: %s", err) 257 } 258 path := tf.Name() 259 tf.Write([]byte("foo")) 260 tf.Close() 261 defer os.Remove(path) 262 263 testFunction(t, testFunctionConfig{ 264 Cases: []testFunctionCase{ 265 { 266 fmt.Sprintf(`${file("%s")}`, path), 267 "foo", 268 false, 269 }, 270 271 // Invalid path 272 { 273 `${file("/i/dont/exist")}`, 274 nil, 275 true, 276 }, 277 278 // Too many args 279 { 280 `${file("foo", "bar")}`, 281 nil, 282 true, 283 }, 284 }, 285 }) 286 } 287 288 func TestInterpolateFuncFormat(t *testing.T) { 289 testFunction(t, testFunctionConfig{ 290 Cases: []testFunctionCase{ 291 { 292 `${format("hello")}`, 293 "hello", 294 false, 295 }, 296 297 { 298 `${format("hello %s", "world")}`, 299 "hello world", 300 false, 301 }, 302 303 { 304 `${format("hello %d", 42)}`, 305 "hello 42", 306 false, 307 }, 308 309 { 310 `${format("hello %05d", 42)}`, 311 "hello 00042", 312 false, 313 }, 314 315 { 316 `${format("hello %05d", 12345)}`, 317 "hello 12345", 318 false, 319 }, 320 }, 321 }) 322 } 323 324 func TestInterpolateFuncFormatList(t *testing.T) { 325 testFunction(t, testFunctionConfig{ 326 Cases: []testFunctionCase{ 327 // formatlist requires at least one list 328 { 329 `${formatlist("hello")}`, 330 nil, 331 true, 332 }, 333 { 334 `${formatlist("hello %s", "world")}`, 335 nil, 336 true, 337 }, 338 // formatlist applies to each list element in turn 339 { 340 `${formatlist("<%s>", split(",", "A,B"))}`, 341 NewStringList([]string{"<A>", "<B>"}).String(), 342 false, 343 }, 344 // formatlist repeats scalar elements 345 { 346 `${join(", ", formatlist("%s=%s", "x", split(",", "A,B,C")))}`, 347 "x=A, x=B, x=C", 348 false, 349 }, 350 // Multiple lists are walked in parallel 351 { 352 `${join(", ", formatlist("%s=%s", split(",", "A,B,C"), split(",", "1,2,3")))}`, 353 "A=1, B=2, C=3", 354 false, 355 }, 356 // Mismatched list lengths generate an error 357 { 358 `${formatlist("%s=%2s", split(",", "A,B,C,D"), split(",", "1,2,3"))}`, 359 nil, 360 true, 361 }, 362 // Works with lists of length 1 [GH-2240] 363 { 364 `${formatlist("%s.id", split(",", "demo-rest-elb"))}`, 365 NewStringList([]string{"demo-rest-elb.id"}).String(), 366 false, 367 }, 368 }, 369 }) 370 } 371 372 func TestInterpolateFuncIndex(t *testing.T) { 373 testFunction(t, testFunctionConfig{ 374 Cases: []testFunctionCase{ 375 { 376 `${index("test", "")}`, 377 nil, 378 true, 379 }, 380 381 { 382 fmt.Sprintf(`${index("%s", "foo")}`, 383 NewStringList([]string{"notfoo", "stillnotfoo", "bar"}).String()), 384 nil, 385 true, 386 }, 387 388 { 389 fmt.Sprintf(`${index("%s", "foo")}`, 390 NewStringList([]string{"foo"}).String()), 391 "0", 392 false, 393 }, 394 395 { 396 fmt.Sprintf(`${index("%s", "bar")}`, 397 NewStringList([]string{"foo", "spam", "bar", "eggs"}).String()), 398 "2", 399 false, 400 }, 401 }, 402 }) 403 } 404 405 func TestInterpolateFuncJoin(t *testing.T) { 406 testFunction(t, testFunctionConfig{ 407 Cases: []testFunctionCase{ 408 { 409 `${join(",")}`, 410 nil, 411 true, 412 }, 413 414 { 415 fmt.Sprintf(`${join(",", "%s")}`, 416 NewStringList([]string{"foo"}).String()), 417 "foo", 418 false, 419 }, 420 421 /* 422 TODO 423 { 424 `${join(",", "foo", "bar")}`, 425 "foo,bar", 426 false, 427 }, 428 */ 429 430 { 431 fmt.Sprintf(`${join(".", "%s")}`, 432 NewStringList([]string{"foo", "bar", "baz"}).String()), 433 "foo.bar.baz", 434 false, 435 }, 436 }, 437 }) 438 } 439 440 func TestInterpolateFuncReplace(t *testing.T) { 441 testFunction(t, testFunctionConfig{ 442 Cases: []testFunctionCase{ 443 // Regular search and replace 444 { 445 `${replace("hello", "hel", "bel")}`, 446 "bello", 447 false, 448 }, 449 450 // Search string doesn't match 451 { 452 `${replace("hello", "nope", "bel")}`, 453 "hello", 454 false, 455 }, 456 457 // Regular expression 458 { 459 `${replace("hello", "/l/", "L")}`, 460 "heLLo", 461 false, 462 }, 463 464 { 465 `${replace("helo", "/(l)/", "$1$1")}`, 466 "hello", 467 false, 468 }, 469 470 // Bad regexp 471 { 472 `${replace("helo", "/(l/", "$1$1")}`, 473 nil, 474 true, 475 }, 476 }, 477 }) 478 } 479 480 func TestInterpolateFuncLength(t *testing.T) { 481 testFunction(t, testFunctionConfig{ 482 Cases: []testFunctionCase{ 483 // Raw strings 484 { 485 `${length("")}`, 486 "0", 487 false, 488 }, 489 { 490 `${length("a")}`, 491 "1", 492 false, 493 }, 494 { 495 `${length(" ")}`, 496 "1", 497 false, 498 }, 499 { 500 `${length(" a ,")}`, 501 "4", 502 false, 503 }, 504 { 505 `${length("aaa")}`, 506 "3", 507 false, 508 }, 509 510 // Lists 511 { 512 `${length(split(",", "a"))}`, 513 "1", 514 false, 515 }, 516 { 517 `${length(split(",", "foo,"))}`, 518 "2", 519 false, 520 }, 521 { 522 `${length(split(",", ",foo,"))}`, 523 "3", 524 false, 525 }, 526 { 527 `${length(split(",", "foo,bar"))}`, 528 "2", 529 false, 530 }, 531 { 532 `${length(split(".", "one.two.three.four.five"))}`, 533 "5", 534 false, 535 }, 536 // Want length 0 if we split an empty string then compact 537 { 538 `${length(compact(split(",", "")))}`, 539 "0", 540 false, 541 }, 542 }, 543 }) 544 } 545 546 func TestInterpolateFuncSignum(t *testing.T) { 547 testFunction(t, testFunctionConfig{ 548 Cases: []testFunctionCase{ 549 { 550 `${signum()}`, 551 nil, 552 true, 553 }, 554 555 { 556 `${signum("")}`, 557 nil, 558 true, 559 }, 560 561 { 562 `${signum(0)}`, 563 "0", 564 false, 565 }, 566 567 { 568 `${signum(15)}`, 569 "1", 570 false, 571 }, 572 573 { 574 `${signum(-29)}`, 575 "-1", 576 false, 577 }, 578 }, 579 }) 580 } 581 582 func TestInterpolateFuncSplit(t *testing.T) { 583 testFunction(t, testFunctionConfig{ 584 Cases: []testFunctionCase{ 585 { 586 `${split(",")}`, 587 nil, 588 true, 589 }, 590 591 { 592 `${split(",", "")}`, 593 NewStringList([]string{""}).String(), 594 false, 595 }, 596 597 { 598 `${split(",", "foo")}`, 599 NewStringList([]string{"foo"}).String(), 600 false, 601 }, 602 603 { 604 `${split(",", ",,,")}`, 605 NewStringList([]string{"", "", "", ""}).String(), 606 false, 607 }, 608 609 { 610 `${split(",", "foo,")}`, 611 NewStringList([]string{"foo", ""}).String(), 612 false, 613 }, 614 615 { 616 `${split(",", ",foo,")}`, 617 NewStringList([]string{"", "foo", ""}).String(), 618 false, 619 }, 620 621 { 622 `${split(".", "foo.bar.baz")}`, 623 NewStringList([]string{"foo", "bar", "baz"}).String(), 624 false, 625 }, 626 }, 627 }) 628 } 629 630 func TestInterpolateFuncLookup(t *testing.T) { 631 testFunction(t, testFunctionConfig{ 632 Vars: map[string]ast.Variable{ 633 "var.foo.bar": ast.Variable{ 634 Value: "baz", 635 Type: ast.TypeString, 636 }, 637 }, 638 Cases: []testFunctionCase{ 639 { 640 `${lookup("foo", "bar")}`, 641 "baz", 642 false, 643 }, 644 645 // Invalid key 646 { 647 `${lookup("foo", "baz")}`, 648 nil, 649 true, 650 }, 651 652 // Too many args 653 { 654 `${lookup("foo", "bar", "baz")}`, 655 nil, 656 true, 657 }, 658 }, 659 }) 660 } 661 662 func TestInterpolateFuncKeys(t *testing.T) { 663 testFunction(t, testFunctionConfig{ 664 Vars: map[string]ast.Variable{ 665 "var.foo.bar": ast.Variable{ 666 Value: "baz", 667 Type: ast.TypeString, 668 }, 669 "var.foo.qux": ast.Variable{ 670 Value: "quack", 671 Type: ast.TypeString, 672 }, 673 "var.str": ast.Variable{ 674 Value: "astring", 675 Type: ast.TypeString, 676 }, 677 }, 678 Cases: []testFunctionCase{ 679 { 680 `${keys("foo")}`, 681 NewStringList([]string{"bar", "qux"}).String(), 682 false, 683 }, 684 685 // Invalid key 686 { 687 `${keys("not")}`, 688 nil, 689 true, 690 }, 691 692 // Too many args 693 { 694 `${keys("foo", "bar")}`, 695 nil, 696 true, 697 }, 698 699 // Not a map 700 { 701 `${keys("str")}`, 702 nil, 703 true, 704 }, 705 }, 706 }) 707 } 708 709 func TestInterpolateFuncValues(t *testing.T) { 710 testFunction(t, testFunctionConfig{ 711 Vars: map[string]ast.Variable{ 712 "var.foo.bar": ast.Variable{ 713 Value: "quack", 714 Type: ast.TypeString, 715 }, 716 "var.foo.qux": ast.Variable{ 717 Value: "baz", 718 Type: ast.TypeString, 719 }, 720 "var.str": ast.Variable{ 721 Value: "astring", 722 Type: ast.TypeString, 723 }, 724 }, 725 Cases: []testFunctionCase{ 726 { 727 `${values("foo")}`, 728 NewStringList([]string{"quack", "baz"}).String(), 729 false, 730 }, 731 732 // Invalid key 733 { 734 `${values("not")}`, 735 nil, 736 true, 737 }, 738 739 // Too many args 740 { 741 `${values("foo", "bar")}`, 742 nil, 743 true, 744 }, 745 746 // Not a map 747 { 748 `${values("str")}`, 749 nil, 750 true, 751 }, 752 }, 753 }) 754 } 755 756 func TestInterpolateFuncElement(t *testing.T) { 757 testFunction(t, testFunctionConfig{ 758 Cases: []testFunctionCase{ 759 { 760 fmt.Sprintf(`${element("%s", "1")}`, 761 NewStringList([]string{"foo", "baz"}).String()), 762 "baz", 763 false, 764 }, 765 766 { 767 fmt.Sprintf(`${element("%s", "0")}`, 768 NewStringList([]string{"foo"}).String()), 769 "foo", 770 false, 771 }, 772 773 // Invalid index should wrap vs. out-of-bounds 774 { 775 fmt.Sprintf(`${element("%s", "2")}`, 776 NewStringList([]string{"foo", "baz"}).String()), 777 "foo", 778 false, 779 }, 780 781 // Too many args 782 { 783 fmt.Sprintf(`${element("%s", "0", "2")}`, 784 NewStringList([]string{"foo", "baz"}).String()), 785 nil, 786 true, 787 }, 788 }, 789 }) 790 } 791 792 func TestInterpolateFuncBase64Encode(t *testing.T) { 793 testFunction(t, testFunctionConfig{ 794 Cases: []testFunctionCase{ 795 // Regular base64 encoding 796 { 797 `${base64encode("abc123!?$*&()'-=@~")}`, 798 "YWJjMTIzIT8kKiYoKSctPUB+", 799 false, 800 }, 801 }, 802 }) 803 } 804 805 func TestInterpolateFuncBase64Decode(t *testing.T) { 806 testFunction(t, testFunctionConfig{ 807 Cases: []testFunctionCase{ 808 // Regular base64 decoding 809 { 810 `${base64decode("YWJjMTIzIT8kKiYoKSctPUB+")}`, 811 "abc123!?$*&()'-=@~", 812 false, 813 }, 814 815 // Invalid base64 data decoding 816 { 817 `${base64decode("this-is-an-invalid-base64-data")}`, 818 nil, 819 true, 820 }, 821 }, 822 }) 823 } 824 825 func TestInterpolateFuncLower(t *testing.T) { 826 testFunction(t, testFunctionConfig{ 827 Cases: []testFunctionCase{ 828 { 829 `${lower("HELLO")}`, 830 "hello", 831 false, 832 }, 833 834 { 835 `${lower("")}`, 836 "", 837 false, 838 }, 839 840 { 841 `${lower()}`, 842 nil, 843 true, 844 }, 845 }, 846 }) 847 } 848 849 func TestInterpolateFuncUpper(t *testing.T) { 850 testFunction(t, testFunctionConfig{ 851 Cases: []testFunctionCase{ 852 { 853 `${upper("hello")}`, 854 "HELLO", 855 false, 856 }, 857 858 { 859 `${upper("")}`, 860 "", 861 false, 862 }, 863 864 { 865 `${upper()}`, 866 nil, 867 true, 868 }, 869 }, 870 }) 871 } 872 873 func TestInterpolateFuncSha1(t *testing.T) { 874 testFunction(t, testFunctionConfig{ 875 Cases: []testFunctionCase{ 876 { 877 `${sha1("test")}`, 878 "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", 879 false, 880 }, 881 }, 882 }) 883 } 884 885 func TestInterpolateFuncSha256(t *testing.T) { 886 testFunction(t, testFunctionConfig{ 887 Cases: []testFunctionCase{ 888 { // hexadecimal representation of sha256 sum 889 `${sha256("test")}`, 890 "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", 891 false, 892 }, 893 }, 894 }) 895 } 896 897 func TestInterpolateFuncTrimSpace(t *testing.T) { 898 testFunction(t, testFunctionConfig{ 899 Cases: []testFunctionCase{ 900 { 901 `${trimspace(" test ")}`, 902 "test", 903 false, 904 }, 905 }, 906 }) 907 } 908 909 func TestInterpolateFuncBase64Sha256(t *testing.T) { 910 testFunction(t, testFunctionConfig{ 911 Cases: []testFunctionCase{ 912 { 913 `${base64sha256("test")}`, 914 "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=", 915 false, 916 }, 917 { // This will differ because we're base64-encoding hex represantiation, not raw bytes 918 `${base64encode(sha256("test"))}`, 919 "OWY4NmQwODE4ODRjN2Q2NTlhMmZlYWEwYzU1YWQwMTVhM2JmNGYxYjJiMGI4MjJjZDE1ZDZjMTViMGYwMGEwOA==", 920 false, 921 }, 922 }, 923 }) 924 } 925 926 func TestInterpolateFuncMd5(t *testing.T) { 927 testFunction(t, testFunctionConfig{ 928 Cases: []testFunctionCase{ 929 { 930 `${md5("tada")}`, 931 "ce47d07243bb6eaf5e1322c81baf9bbf", 932 false, 933 }, 934 { // Confirm that we're not trimming any whitespaces 935 `${md5(" tada ")}`, 936 "aadf191a583e53062de2d02c008141c4", 937 false, 938 }, 939 { // We accept empty string too 940 `${md5("")}`, 941 "d41d8cd98f00b204e9800998ecf8427e", 942 false, 943 }, 944 }, 945 }) 946 } 947 948 func TestInterpolateFuncUUID(t *testing.T) { 949 results := make(map[string]bool) 950 951 for i := 0; i < 100; i++ { 952 ast, err := hil.Parse("${uuid()}") 953 if err != nil { 954 t.Fatalf("err: %s", err) 955 } 956 957 out, _, err := hil.Eval(ast, langEvalConfig(nil)) 958 if err != nil { 959 t.Fatalf("err: %s", err) 960 } 961 962 if results[out.(string)] { 963 t.Fatalf("Got unexpected duplicate uuid: %s", out) 964 } 965 966 results[out.(string)] = true 967 } 968 } 969 970 type testFunctionConfig struct { 971 Cases []testFunctionCase 972 Vars map[string]ast.Variable 973 } 974 975 type testFunctionCase struct { 976 Input string 977 Result interface{} 978 Error bool 979 } 980 981 func testFunction(t *testing.T, config testFunctionConfig) { 982 for i, tc := range config.Cases { 983 ast, err := hil.Parse(tc.Input) 984 if err != nil { 985 t.Fatalf("Case #%d: input: %#v\nerr: %s", i, tc.Input, err) 986 } 987 988 out, _, err := hil.Eval(ast, langEvalConfig(config.Vars)) 989 if err != nil != tc.Error { 990 t.Fatalf("Case #%d:\ninput: %#v\nerr: %s", i, tc.Input, err) 991 } 992 993 if !reflect.DeepEqual(out, tc.Result) { 994 t.Fatalf( 995 "%d: bad output for input: %s\n\nOutput: %#v\nExpected: %#v", 996 i, tc.Input, out, tc.Result) 997 } 998 } 999 }