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 &lt;&amp;&gt; b\n",
744	},
745	{
746		in:  "{Bytes}",
747		out: "hello",
748	},
749	{
750		in:  "{Raw|uppercase|html|html}",
751		out: "A &amp;lt;&amp;amp;&amp;gt; B",
752	},
753	{
754		in:  "{Header Integer|multiword|html}",
755		out: "&lt;Header&gt;&lt;77&gt;",
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