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