1// Copyright 2009 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package template 6 7import ( 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "os" 14 "strings" 15 "testing" 16) 17 18type Test struct { 19 in, out, err string 20} 21 22type T struct { 23 Item string 24 Value string 25} 26 27type U struct { 28 Mp map[string]int 29} 30 31type S struct { 32 Header string 33 HeaderPtr *string 34 Integer int 35 IntegerPtr *int 36 NilPtr *int 37 InnerT T 38 InnerPointerT *T 39 Data []T 40 Pdata []*T 41 Empty []*T 42 Emptystring string 43 Null []*T 44 Vec []interface{} 45 True bool 46 False bool 47 Mp map[string]string 48 JSON interface{} 49 Innermap U 50 Stringmap map[string]string 51 Ptrmap map[string]*string 52 Iface interface{} 53 Ifaceptr interface{} 54} 55 56func (s *S) PointerMethod() string { return "ptrmethod!" } 57 58func (s S) ValueMethod() string { return "valmethod!" } 59 60var t1 = T{"ItemNumber1", "ValueNumber1"} 61var t2 = T{"ItemNumber2", "ValueNumber2"} 62 63func uppercase(v interface{}) string { 64 s := v.(string) 65 t := "" 66 for i := 0; i < len(s); i++ { 67 c := s[i] 68 if 'a' <= c && c <= 'z' { 69 c = c + 'A' - 'a' 70 } 71 t += string(c) 72 } 73 return t 74} 75 76func plus1(v interface{}) string { 77 i := v.(int) 78 return fmt.Sprint(i + 1) 79} 80 81func writer(f func(interface{}) string) func(io.Writer, string, ...interface{}) { 82 return func(w io.Writer, format string, v ...interface{}) { 83 if len(v) != 1 { 84 panic("test writer expected one arg") 85 } 86 io.WriteString(w, f(v[0])) 87 } 88} 89 90func multiword(w io.Writer, format string, value ...interface{}) { 91 for _, v := range value { 92 fmt.Fprintf(w, "<%v>", v) 93 } 94} 95 96func printf(w io.Writer, format string, v ...interface{}) { 97 io.WriteString(w, fmt.Sprintf(v[0].(string), v[1:]...)) 98} 99 100var formatters = FormatterMap{ 101 "uppercase": writer(uppercase), 102 "+1": writer(plus1), 103 "multiword": multiword, 104 "printf": printf, 105} 106 107var tests = []*Test{ 108 // Simple 109 {"", "", ""}, 110 {"abc", "abc", ""}, 111 {"abc\ndef\n", "abc\ndef\n", ""}, 112 {" {.meta-left} \n", "{", ""}, 113 {" {.meta-right} \n", "}", ""}, 114 {" {.space} \n", " ", ""}, 115 {" {.tab} \n", "\t", ""}, 116 {" {#comment} \n", "", ""}, 117 {"\tSome Text\t\n", "\tSome Text\t\n", ""}, 118 {" {.meta-right} {.meta-right} {.meta-right} \n", " } } } \n", ""}, 119 120 // Variables at top level 121 { 122 in: "{Header}={Integer}\n", 123 124 out: "Header=77\n", 125 }, 126 127 { 128 in: "Pointers: {*HeaderPtr}={*IntegerPtr}\n", 129 130 out: "Pointers: Header=77\n", 131 }, 132 133 { 134 in: "Stars but not pointers: {*Header}={*Integer}\n", 135 136 out: "Stars but not pointers: Header=77\n", 137 }, 138 139 { 140 in: "nil pointer: {*NilPtr}={*Integer}\n", 141 142 out: "nil pointer: <nil>=77\n", 143 }, 144 145 { 146 in: `{"Strings" ":"} {""} {"|"} {"\t\u0123 \x23\\"} {"\"}{\\"}`, 147 148 out: "Strings: | \t\u0123 \x23\\ \"}{\\", 149 }, 150 151 { 152 in: "{`Raw strings` `:`} {``} {`|`} {`\\t\\u0123 \\x23\\`} {`}{\\`}", 153 154 out: "Raw strings: | \\t\\u0123 \\x23\\ }{\\", 155 }, 156 157 { 158 in: "Characters: {'a'} {'\\u0123'} {' '} {'{'} {'|'} {'}'}", 159 160 out: "Characters: 97 291 32 123 124 125", 161 }, 162 163 { 164 in: "Integers: {1} {-2} {+42} {0777} {0x0a}", 165 166 out: "Integers: 1 -2 42 511 10", 167 }, 168 169 { 170 in: "Floats: {.5} {-.5} {1.1} {-2.2} {+42.1} {1e10} {1.2e-3} {1.2e3} {-1.2e3}", 171 172 out: "Floats: 0.5 -0.5 1.1 -2.2 42.1 1e+10 0.0012 1200 -1200", 173 }, 174 175 // Method at top level 176 { 177 in: "ptrmethod={PointerMethod}\n", 178 179 out: "ptrmethod=ptrmethod!\n", 180 }, 181 182 { 183 in: "valmethod={ValueMethod}\n", 184 185 out: "valmethod=valmethod!\n", 186 }, 187 188 // Section 189 { 190 in: "{.section Data }\n" + 191 "some text for the section\n" + 192 "{.end}\n", 193 194 out: "some text for the section\n", 195 }, 196 { 197 in: "{.section Data }\n" + 198 "{Header}={Integer}\n" + 199 "{.end}\n", 200 201 out: "Header=77\n", 202 }, 203 { 204 in: "{.section Pdata }\n" + 205 "{Header}={Integer}\n" + 206 "{.end}\n", 207 208 out: "Header=77\n", 209 }, 210 { 211 in: "{.section Pdata }\n" + 212 "data present\n" + 213 "{.or}\n" + 214 "data not present\n" + 215 "{.end}\n", 216 217 out: "data present\n", 218 }, 219 { 220 in: "{.section Empty }\n" + 221 "data present\n" + 222 "{.or}\n" + 223 "data not present\n" + 224 "{.end}\n", 225 226 out: "data not present\n", 227 }, 228 { 229 in: "{.section Null }\n" + 230 "data present\n" + 231 "{.or}\n" + 232 "data not present\n" + 233 "{.end}\n", 234 235 out: "data not present\n", 236 }, 237 { 238 in: "{.section Pdata }\n" + 239 "{Header}={Integer}\n" + 240 "{.section @ }\n" + 241 "{Header}={Integer}\n" + 242 "{.end}\n" + 243 "{.end}\n", 244 245 out: "Header=77\n" + 246 "Header=77\n", 247 }, 248 249 { 250 in: "{.section Data}{.end} {Header}\n", 251 252 out: " Header\n", 253 }, 254 255 { 256 in: "{.section Integer}{@}{.end}", 257 258 out: "77", 259 }, 260 261 // Repeated 262 { 263 in: "{.section Pdata }\n" + 264 "{.repeated section @ }\n" + 265 "{Item}={Value}\n" + 266 "{.end}\n" + 267 "{.end}\n", 268 269 out: "ItemNumber1=ValueNumber1\n" + 270 "ItemNumber2=ValueNumber2\n", 271 }, 272 { 273 in: "{.section Pdata }\n" + 274 "{.repeated section @ }\n" + 275 "{Item}={Value}\n" + 276 "{.or}\n" + 277 "this should not appear\n" + 278 "{.end}\n" + 279 "{.end}\n", 280 281 out: "ItemNumber1=ValueNumber1\n" + 282 "ItemNumber2=ValueNumber2\n", 283 }, 284 { 285 in: "{.section @ }\n" + 286 "{.repeated section Empty }\n" + 287 "{Item}={Value}\n" + 288 "{.or}\n" + 289 "this should appear: empty field\n" + 290 "{.end}\n" + 291 "{.end}\n", 292 293 out: "this should appear: empty field\n", 294 }, 295 { 296 in: "{.repeated section Pdata }\n" + 297 "{Item}\n" + 298 "{.alternates with}\n" + 299 "is\nover\nmultiple\nlines\n" + 300 "{.end}\n", 301 302 out: "ItemNumber1\n" + 303 "is\nover\nmultiple\nlines\n" + 304 "ItemNumber2\n", 305 }, 306 { 307 in: "{.repeated section Pdata }\n" + 308 "{Item}\n" + 309 "{.alternates with}\n" + 310 "is\nover\nmultiple\nlines\n" + 311 " {.end}\n", 312 313 out: "ItemNumber1\n" + 314 "is\nover\nmultiple\nlines\n" + 315 "ItemNumber2\n", 316 }, 317 { 318 in: "{.section Pdata }\n" + 319 "{.repeated section @ }\n" + 320 "{Item}={Value}\n" + 321 "{.alternates with}DIVIDER\n" + 322 "{.or}\n" + 323 "this should not appear\n" + 324 "{.end}\n" + 325 "{.end}\n", 326 327 out: "ItemNumber1=ValueNumber1\n" + 328 "DIVIDER\n" + 329 "ItemNumber2=ValueNumber2\n", 330 }, 331 { 332 in: "{.repeated section Vec }\n" + 333 "{@}\n" + 334 "{.end}\n", 335 336 out: "elt1\n" + 337 "elt2\n", 338 }, 339 // Same but with a space before {.end}: was a bug. 340 { 341 in: "{.repeated section Vec }\n" + 342 "{@} {.end}\n", 343 344 out: "elt1 elt2 \n", 345 }, 346 { 347 in: "{.repeated section Integer}{.end}", 348 349 err: "line 1: .repeated: cannot repeat Integer (type int)", 350 }, 351 352 // Nested names 353 { 354 in: "{.section @ }\n" + 355 "{InnerT.Item}={InnerT.Value}\n" + 356 "{.end}", 357 358 out: "ItemNumber1=ValueNumber1\n", 359 }, 360 { 361 in: "{.section @ }\n" + 362 "{InnerT.Item}={.section InnerT}{.section Value}{@}{.end}{.end}\n" + 363 "{.end}", 364 365 out: "ItemNumber1=ValueNumber1\n", 366 }, 367 368 { 369 in: "{.section Emptystring}emptystring{.end}\n" + 370 "{.section Header}header{.end}\n", 371 372 out: "\nheader\n", 373 }, 374 375 { 376 in: "{.section True}1{.or}2{.end}\n" + 377 "{.section False}3{.or}4{.end}\n", 378 379 out: "1\n4\n", 380 }, 381 382 // Maps 383 384 { 385 in: "{Mp.mapkey}\n", 386 387 out: "Ahoy!\n", 388 }, 389 { 390 in: "{Innermap.Mp.innerkey}\n", 391 392 out: "55\n", 393 }, 394 { 395 in: "{.section Innermap}{.section Mp}{innerkey}{.end}{.end}\n", 396 397 out: "55\n", 398 }, 399 { 400 in: "{.section JSON}{.repeated section maps}{a}{b}{.end}{.end}\n", 401 402 out: "1234\n", 403 }, 404 { 405 in: "{Stringmap.stringkey1}\n", 406 407 out: "stringresult\n", 408 }, 409 { 410 in: "{.repeated section Stringmap}\n" + 411 "{@}\n" + 412 "{.end}", 413 414 out: "stringresult\n" + 415 "stringresult\n", 416 }, 417 { 418 in: "{.repeated section Stringmap}\n" + 419 "\t{@}\n" + 420 "{.end}", 421 422 out: "\tstringresult\n" + 423 "\tstringresult\n", 424 }, 425 { 426 in: "{*Ptrmap.stringkey1}\n", 427 428 out: "pointedToString\n", 429 }, 430 { 431 in: "{.repeated section Ptrmap}\n" + 432 "{*@}\n" + 433 "{.end}", 434 435 out: "pointedToString\n" + 436 "pointedToString\n", 437 }, 438 439 // Interface values 440 441 { 442 in: "{Iface}", 443 444 out: "[1 2 3]", 445 }, 446 { 447 in: "{.repeated section Iface}{@}{.alternates with} {.end}", 448 449 out: "1 2 3", 450 }, 451 { 452 in: "{.section Iface}{@}{.end}", 453 454 out: "[1 2 3]", 455 }, 456 { 457 in: "{.section Ifaceptr}{Item} {Value}{.end}", 458 459 out: "Item Value", 460 }, 461} 462 463func TestAll(t *testing.T) { 464 // Parse 465 testAll(t, func(test *Test) (*Template, error) { return Parse(test.in, formatters) }) 466 // ParseFile 467 f, err := ioutil.TempFile("", "template-test") 468 if err != nil { 469 t.Fatal(err) 470 } 471 defer func() { 472 name := f.Name() 473 f.Close() 474 os.Remove(name) 475 }() 476 testAll(t, func(test *Test) (*Template, error) { 477 err := ioutil.WriteFile(f.Name(), []byte(test.in), 0600) 478 if err != nil { 479 t.Error("unexpected write error:", err) 480 return nil, err 481 } 482 return ParseFile(f.Name(), formatters) 483 }) 484 // tmpl.ParseFile 485 testAll(t, func(test *Test) (*Template, error) { 486 err := ioutil.WriteFile(f.Name(), []byte(test.in), 0600) 487 if err != nil { 488 t.Error("unexpected write error:", err) 489 return nil, err 490 } 491 tmpl := New(formatters) 492 return tmpl, tmpl.ParseFile(f.Name()) 493 }) 494} 495 496func testAll(t *testing.T, parseFunc func(*Test) (*Template, error)) { 497 s := new(S) 498 // initialized by hand for clarity. 499 s.Header = "Header" 500 s.HeaderPtr = &s.Header 501 s.Integer = 77 502 s.IntegerPtr = &s.Integer 503 s.InnerT = t1 504 s.Data = []T{t1, t2} 505 s.Pdata = []*T{&t1, &t2} 506 s.Empty = []*T{} 507 s.Null = nil 508 s.Vec = []interface{}{"elt1", "elt2"} 509 s.True = true 510 s.False = false 511 s.Mp = make(map[string]string) 512 s.Mp["mapkey"] = "Ahoy!" 513 json.Unmarshal([]byte(`{"maps":[{"a":1,"b":2},{"a":3,"b":4}]}`), &s.JSON) 514 s.Innermap.Mp = make(map[string]int) 515 s.Innermap.Mp["innerkey"] = 55 516 s.Stringmap = make(map[string]string) 517 s.Stringmap["stringkey1"] = "stringresult" // the same value so repeated section is order-independent 518 s.Stringmap["stringkey2"] = "stringresult" 519 s.Ptrmap = make(map[string]*string) 520 x := "pointedToString" 521 s.Ptrmap["stringkey1"] = &x // the same value so repeated section is order-independent 522 s.Ptrmap["stringkey2"] = &x 523 s.Iface = []int{1, 2, 3} 524 s.Ifaceptr = &T{"Item", "Value"} 525 526 var buf bytes.Buffer 527 for _, test := range tests { 528 buf.Reset() 529 tmpl, err := parseFunc(test) 530 if err != nil { 531 t.Error("unexpected parse error: ", err) 532 continue 533 } 534 err = tmpl.Execute(&buf, s) 535 if test.err == "" { 536 if err != nil { 537 t.Error("unexpected execute error:", err) 538 } 539 } else { 540 if err == nil { 541 t.Errorf("expected execute error %q, got nil", test.err) 542 } else if err.Error() != test.err { 543 t.Errorf("expected execute error %q, got %q", test.err, err.Error()) 544 } 545 } 546 if buf.String() != test.out { 547 t.Errorf("for %q: expected %q got %q", test.in, test.out, buf.String()) 548 } 549 } 550} 551 552func TestMapDriverType(t *testing.T) { 553 mp := map[string]string{"footer": "Ahoy!"} 554 tmpl, err := Parse("template: {footer}", nil) 555 if err != nil { 556 t.Error("unexpected parse error:", err) 557 } 558 var b bytes.Buffer 559 err = tmpl.Execute(&b, mp) 560 if err != nil { 561 t.Error("unexpected execute error:", err) 562 } 563 s := b.String() 564 expect := "template: Ahoy!" 565 if s != expect { 566 t.Errorf("failed passing string as data: expected %q got %q", expect, s) 567 } 568} 569 570func TestMapNoEntry(t *testing.T) { 571 mp := make(map[string]int) 572 tmpl, err := Parse("template: {notthere}!", nil) 573 if err != nil { 574 t.Error("unexpected parse error:", err) 575 } 576 var b bytes.Buffer 577 err = tmpl.Execute(&b, mp) 578 if err != nil { 579 t.Error("unexpected execute error:", err) 580 } 581 s := b.String() 582 expect := "template: 0!" 583 if s != expect { 584 t.Errorf("failed passing string as data: expected %q got %q", expect, s) 585 } 586} 587 588func TestStringDriverType(t *testing.T) { 589 tmpl, err := Parse("template: {@}", nil) 590 if err != nil { 591 t.Error("unexpected parse error:", err) 592 } 593 var b bytes.Buffer 594 err = tmpl.Execute(&b, "hello") 595 if err != nil { 596 t.Error("unexpected execute error:", err) 597 } 598 s := b.String() 599 expect := "template: hello" 600 if s != expect { 601 t.Errorf("failed passing string as data: expected %q got %q", expect, s) 602 } 603} 604 605func TestTwice(t *testing.T) { 606 tmpl, err := Parse("template: {@}", nil) 607 if err != nil { 608 t.Error("unexpected parse error:", err) 609 } 610 var b bytes.Buffer 611 err = tmpl.Execute(&b, "hello") 612 if err != nil { 613 t.Error("unexpected parse error:", err) 614 } 615 s := b.String() 616 expect := "template: hello" 617 if s != expect { 618 t.Errorf("failed passing string as data: expected %q got %q", expect, s) 619 } 620 err = tmpl.Execute(&b, "hello") 621 if err != nil { 622 t.Error("unexpected parse error:", err) 623 } 624 s = b.String() 625 expect += expect 626 if s != expect { 627 t.Errorf("failed passing string as data: expected %q got %q", expect, s) 628 } 629} 630 631func TestCustomDelims(t *testing.T) { 632 // try various lengths. zero should catch error. 633 for i := 0; i < 7; i++ { 634 for j := 0; j < 7; j++ { 635 tmpl := New(nil) 636 // first two chars deliberately the same to test equal left and right delims 637 ldelim := "$!#$%^&"[0:i] 638 rdelim := "$*&^%$!"[0:j] 639 tmpl.SetDelims(ldelim, rdelim) 640 // if braces, this would be template: {@}{.meta-left}{.meta-right} 641 text := "template: " + 642 ldelim + "@" + rdelim + 643 ldelim + ".meta-left" + rdelim + 644 ldelim + ".meta-right" + rdelim 645 err := tmpl.Parse(text) 646 if err != nil { 647 if i == 0 || j == 0 { // expected 648 continue 649 } 650 t.Error("unexpected parse error:", err) 651 } else if i == 0 || j == 0 { 652 t.Errorf("expected parse error for empty delimiter: %d %d %q %q", i, j, ldelim, rdelim) 653 continue 654 } 655 var b bytes.Buffer 656 err = tmpl.Execute(&b, "hello") 657 s := b.String() 658 if s != "template: hello"+ldelim+rdelim { 659 t.Errorf("failed delim check(%q %q) %q got %q", ldelim, rdelim, text, s) 660 } 661 } 662 } 663} 664 665// Test that a variable evaluates to the field itself and does not further indirection 666func TestVarIndirection(t *testing.T) { 667 s := new(S) 668 // initialized by hand for clarity. 669 s.InnerPointerT = &t1 670 671 var buf bytes.Buffer 672 input := "{.section @}{InnerPointerT}{.end}" 673 tmpl, err := Parse(input, nil) 674 if err != nil { 675 t.Fatal("unexpected parse error:", err) 676 } 677 err = tmpl.Execute(&buf, s) 678 if err != nil { 679 t.Fatal("unexpected execute error:", err) 680 } 681 expect := fmt.Sprintf("%v", &t1) // output should be hex address of t1 682 if buf.String() != expect { 683 t.Errorf("for %q: expected %q got %q", input, expect, buf.String()) 684 } 685} 686 687func TestHTMLFormatterWithByte(t *testing.T) { 688 s := "Test string." 689 b := []byte(s) 690 var buf bytes.Buffer 691 HTMLFormatter(&buf, "", b) 692 bs := buf.String() 693 if bs != s { 694 t.Errorf("munged []byte, expected: %s got: %s", s, bs) 695 } 696} 697 698type UF struct { 699 I int 700 s string 701} 702 703func TestReferenceToUnexported(t *testing.T) { 704 u := &UF{3, "hello"} 705 var buf bytes.Buffer 706 input := "{.section @}{I}{s}{.end}" 707 tmpl, err := Parse(input, nil) 708 if err != nil { 709 t.Fatal("unexpected parse error:", err) 710 } 711 err = tmpl.Execute(&buf, u) 712 if err == nil { 713 t.Fatal("expected execute error, got none") 714 } 715 if strings.Index(err.Error(), "not exported") < 0 { 716 t.Fatal("expected unexported error; got", err) 717 } 718} 719 720var formatterTests = []Test{ 721 { 722 in: "{Header|uppercase}={Integer|+1}\n" + 723 "{Header|html}={Integer|str}\n", 724 725 out: "HEADER=78\n" + 726 "Header=77\n", 727 }, 728 729 { 730 in: "{Header|uppercase}={Integer Header|multiword}\n" + 731 "{Header|html}={Header Integer|multiword}\n" + 732 "{Header|html}={Header Integer}\n", 733 734 out: "HEADER=<77><Header>\n" + 735 "Header=<Header><77>\n" + 736 "Header=Header77\n", 737 }, 738 { 739 in: "{Raw}\n" + 740 "{Raw|html}\n", 741 742 out: "a <&> b\n" + 743 "a <&> b\n", 744 }, 745 { 746 in: "{Bytes}", 747 out: "hello", 748 }, 749 { 750 in: "{Raw|uppercase|html|html}", 751 out: "A &lt;&amp;&gt; B", 752 }, 753 { 754 in: "{Header Integer|multiword|html}", 755 out: "<Header><77>", 756 }, 757 { 758 in: "{Integer|no_formatter|html}", 759 err: `unknown formatter: "no_formatter"`, 760 }, 761 { 762 in: "{Integer|||||}", // empty string is a valid formatter 763 out: "77", 764 }, 765 { 766 in: `{"%.02f 0x%02X" 1.1 10|printf}`, 767 out: "1.10 0x0A", 768 }, 769 { 770 in: `{""|}{""||}{""|printf}`, // Issue #1896. 771 out: "", 772 }, 773} 774 775func TestFormatters(t *testing.T) { 776 data := map[string]interface{}{ 777 "Header": "Header", 778 "Integer": 77, 779 "Raw": "a <&> b", 780 "Bytes": []byte("hello"), 781 } 782 for _, c := range formatterTests { 783 tmpl, err := Parse(c.in, formatters) 784 if err != nil { 785 if c.err == "" { 786 t.Error("unexpected parse error:", err) 787 continue 788 } 789 if strings.Index(err.Error(), c.err) < 0 { 790 t.Errorf("unexpected error: expected %q, got %q", c.err, err.Error()) 791 continue 792 } 793 } else { 794 if c.err != "" { 795 t.Errorf("For %q, expected error, got none.", c.in) 796 continue 797 } 798 var buf bytes.Buffer 799 err = tmpl.Execute(&buf, data) 800 if err != nil { 801 t.Error("unexpected Execute error: ", err) 802 continue 803 } 804 actual := buf.String() 805 if actual != c.out { 806 t.Errorf("for %q: expected %q but got %q.", c.in, c.out, actual) 807 } 808 } 809 } 810} 811