blob: eb5ab71187a8b63a75f9f355846d5afbfb8f0245 [file] [log] [blame]
Rob Pike81592c22011-06-28 23:04:08 +10001// Copyright 2011 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 "fmt"
10 "os"
Rob Pike7c477412011-07-12 13:15:26 +100011 "reflect"
Rob Pike81592c22011-06-28 23:04:08 +100012 "sort"
13 "strings"
14 "testing"
15)
16
17// T has lots of interesting pieces to use to test execution.
18type T struct {
19 // Basics
Rob Pikec3344d62011-07-14 07:52:07 +100020 True bool
Rob Pike13f88972011-07-04 15:15:47 +100021 I int
22 U16 uint16
23 X string
24 FloatZero float64
25 ComplexZero float64
Rob Pike81592c22011-06-28 23:04:08 +100026 // Nested structs.
27 U *U
28 // Slices
Rob Pike13f88972011-07-04 15:15:47 +100029 SI []int
30 SIEmpty []int
31 SB []bool
Rob Pike81592c22011-06-28 23:04:08 +100032 // Maps
33 MSI map[string]int
Rob Pike13f88972011-07-04 15:15:47 +100034 MSIone map[string]int // one element, for deterministic output
Rob Pike81592c22011-06-28 23:04:08 +100035 MSIEmpty map[string]int
Rob Pike6732bd32011-07-06 22:27:06 +100036 SMSI []map[string]int
Rob Pike238274e2011-07-07 14:51:35 +100037 // Empty interfaces; used to see if we can dig inside one.
38 Empty0 interface{} // nil
39 Empty1 interface{}
40 Empty2 interface{}
41 Empty3 interface{}
42 Empty4 interface{}
43 // Pointers
44 PI *int
45 PSI *[]int
46 NIL *int
Rob Pike3987b912011-07-10 07:32:01 +100047 // Template to test evaluation of templates.
48 Tmpl *Template
Rob Pike238274e2011-07-07 14:51:35 +100049}
50
Rob Pikea8529812011-07-08 15:22:05 +100051type U struct {
52 V string
53}
54
Rob Pike238274e2011-07-07 14:51:35 +100055var tVal = &T{
Rob Pikec3344d62011-07-14 07:52:07 +100056 True: true,
Rob Pike238274e2011-07-07 14:51:35 +100057 I: 17,
58 U16: 16,
59 X: "x",
60 U: &U{"v"},
61 SI: []int{3, 4, 5},
62 SB: []bool{true, false},
63 MSI: map[string]int{"one": 1, "two": 2, "three": 3},
64 MSIone: map[string]int{"one": 1},
65 SMSI: []map[string]int{
66 {"one": 1, "two": 2},
67 {"eleven": 11, "twelve": 12},
68 },
69 Empty1: 3,
70 Empty2: "empty2",
71 Empty3: []int{7, 8},
Rob Pike9a5bb282011-07-18 17:34:42 +100072 Empty4: &U{"UinEmpty"},
Rob Pike238274e2011-07-07 14:51:35 +100073 PI: newInt(23),
74 PSI: newIntSlice(21, 22, 23),
Rob Pike3987b912011-07-10 07:32:01 +100075 Tmpl: New("x").MustParse("test template"), // "x" is the value of .X
Rob Pike238274e2011-07-07 14:51:35 +100076}
77
78// Helpers for creation.
79func newInt(n int) *int {
80 p := new(int)
81 *p = n
82 return p
83}
84
85func newIntSlice(n ...int) *[]int {
86 p := new([]int)
87 *p = make([]int, len(n))
88 copy(*p, n)
89 return p
Rob Pike81592c22011-06-28 23:04:08 +100090}
91
92// Simple methods with and without arguments.
93func (t *T) Method0() string {
Rob Pikea8529812011-07-08 15:22:05 +100094 return "M0"
Rob Pike81592c22011-06-28 23:04:08 +100095}
96
97func (t *T) Method1(a int) int {
98 return a
99}
100
101func (t *T) Method2(a uint16, b string) string {
102 return fmt.Sprintf("Method2: %d %s", a, b)
103}
104
105func (t *T) MAdd(a int, b []int) []int {
106 v := make([]int, len(b))
107 for i, x := range b {
108 v[i] = x + a
109 }
110 return v
111}
112
113// MSort is used to sort map keys for stable output. (Nice trick!)
114func (t *T) MSort(m map[string]int) []string {
115 keys := make([]string, len(m))
116 i := 0
117 for k := range m {
118 keys[i] = k
119 i++
120 }
Andrew Gerrand5bcbcab32011-07-08 10:52:50 +1000121 sort.Strings(keys)
Rob Pike81592c22011-06-28 23:04:08 +1000122 return keys
123}
124
125// EPERM returns a value and an os.Error according to its argument.
Rob Pikec756a192011-06-29 15:02:04 +1000126func (t *T) EPERM(error bool) (bool, os.Error) {
127 if error {
128 return true, os.EPERM
Rob Pike81592c22011-06-28 23:04:08 +1000129 }
Rob Pikec756a192011-06-29 15:02:04 +1000130 return false, nil
Rob Pike81592c22011-06-28 23:04:08 +1000131}
132
Rob Pikec3344d62011-07-14 07:52:07 +1000133// A few methods to test chaining.
134func (t *T) GetU() *U {
135 return t.U
136}
137
138func (u *U) TrueFalse(b bool) string {
139 if b {
140 return "true"
141 }
142 return ""
143}
144
Rob Pike7c477412011-07-12 13:15:26 +1000145func typeOf(arg interface{}) string {
146 return fmt.Sprintf("%T", arg)
147}
148
Rob Pike81592c22011-06-28 23:04:08 +1000149type execTest struct {
150 name string
151 input string
152 output string
153 data interface{}
154 ok bool
155}
156
Rob Pike7c477412011-07-12 13:15:26 +1000157// bigInt and bigUint are hex string representing numbers either side
158// of the max int boundary.
159// We do it this way so the test doesn't depend on ints being 32 bits.
160var (
161 bigInt = fmt.Sprintf("0x%x", int(1<<uint(reflect.TypeOf(0).Bits()-1)-1))
162 bigUint = fmt.Sprintf("0x%x", uint(1<<uint(reflect.TypeOf(0).Bits()-1)))
163)
164
Rob Pike81592c22011-06-28 23:04:08 +1000165var execTests = []execTest{
Rob Pike13f88972011-07-04 15:15:47 +1000166 // Trivial cases.
Rob Pike81592c22011-06-28 23:04:08 +1000167 {"empty", "", "", nil, true},
168 {"text", "some text", "some text", nil, true},
Rob Pike238274e2011-07-07 14:51:35 +1000169
Rob Pike7c477412011-07-12 13:15:26 +1000170 // Ideal constants.
171 {"ideal int", "{{typeOf 3}}", "int", 0, true},
172 {"ideal float", "{{typeOf 1.0}}", "float64", 0, true},
173 {"ideal exp float", "{{typeOf 1e1}}", "float64", 0, true},
174 {"ideal complex", "{{typeOf 1i}}", "complex128", 0, true},
175 {"ideal int", "{{typeOf " + bigInt + "}}", "int", 0, true},
176 {"ideal too big", "{{typeOf " + bigUint + "}}", "", 0, false},
177
Rob Pike13f88972011-07-04 15:15:47 +1000178 // Fields of structs.
Rob Pike81592c22011-06-28 23:04:08 +1000179 {".X", "-{{.X}}-", "-x-", tVal, true},
180 {".U.V", "-{{.U.V}}-", "-v-", tVal, true},
Rob Pike238274e2011-07-07 14:51:35 +1000181
Rob Pike13f88972011-07-04 15:15:47 +1000182 // Dots of all kinds to test basic evaluation.
183 {"dot int", "<{{.}}>", "<13>", 13, true},
184 {"dot uint", "<{{.}}>", "<14>", uint(14), true},
185 {"dot float", "<{{.}}>", "<15.1>", 15.1, true},
186 {"dot bool", "<{{.}}>", "<true>", true, true},
187 {"dot complex", "<{{.}}>", "<(16.2-17i)>", 16.2 - 17i, true},
188 {"dot string", "<{{.}}>", "<hello>", "hello", true},
189 {"dot slice", "<{{.}}>", "<[-1 -2 -3]>", []int{-1, -2, -3}, true},
190 {"dot map", "<{{.}}>", "<map[two:22 one:11]>", map[string]int{"one": 11, "two": 22}, true},
191 {"dot struct", "<{{.}}>", "<{7 seven}>", struct {
192 a int
193 b string
194 }{7, "seven"}, true},
Rob Pike238274e2011-07-07 14:51:35 +1000195
Rob Pike58baf642011-07-09 12:05:39 +1000196 // Variables.
197 {"$ int", "{{$}}", "123", 123, true},
Rob Pike7b79b3b2011-07-11 15:23:38 +1000198 {"$.I", "{{$.I}}", "17", tVal, true},
199 {"$.U.V", "{{$.U.V}}", "v", tVal, true},
Rob Pikec7057012011-07-17 13:31:59 +1000200 {"declare in action", "{{$x := $.U.V}},{{$x}}", "v,v", tVal, true},
Rob Pike58baf642011-07-09 12:05:39 +1000201
Rob Pike238274e2011-07-07 14:51:35 +1000202 // Pointers.
203 {"*int", "{{.PI}}", "23", tVal, true},
204 {"*[]int", "{{.PSI}}", "[21 22 23]", tVal, true},
205 {"*[]int[1]", "{{index .PSI 1}}", "22", tVal, true},
206 {"NIL", "{{.NIL}}", "<nil>", tVal, true},
207
Rob Piked3d08e12011-07-08 18:25:46 +1000208 // Empty interfaces holding values.
Rob Pike238274e2011-07-07 14:51:35 +1000209 {"empty nil", "{{.Empty0}}", "<no value>", tVal, true},
210 {"empty with int", "{{.Empty1}}", "3", tVal, true},
211 {"empty with string", "{{.Empty2}}", "empty2", tVal, true},
212 {"empty with slice", "{{.Empty3}}", "[7 8]", tVal, true},
Rob Pike9a5bb282011-07-18 17:34:42 +1000213 {"empty with struct", "{{.Empty4}}", "{UinEmpty}", tVal, true},
214 {"empty with struct, field", "{{.Empty4.V}}", "UinEmpty", tVal, true},
Rob Pike238274e2011-07-07 14:51:35 +1000215
Rob Pike13f88972011-07-04 15:15:47 +1000216 // Method calls.
Rob Pikea8529812011-07-08 15:22:05 +1000217 {".Method0", "-{{.Method0}}-", "-M0-", tVal, true},
Rob Pike81592c22011-06-28 23:04:08 +1000218 {".Method1(1234)", "-{{.Method1 1234}}-", "-1234-", tVal, true},
219 {".Method1(.I)", "-{{.Method1 .I}}-", "-17-", tVal, true},
220 {".Method2(3, .X)", "-{{.Method2 3 .X}}-", "-Method2: 3 x-", tVal, true},
221 {".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true},
Rob Pikee86d7272011-07-09 17:11:35 +1000222 {".Method2(.U16, $x)", "{{if $x := .X}}-{{.Method2 .U16 $x}}{{end}}-", "-Method2: 16 x-", tVal, true},
Rob Pike7b79b3b2011-07-11 15:23:38 +1000223 {"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
Rob Pikec3344d62011-07-14 07:52:07 +1000224 {"method on chained var",
225 "{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
226 "true", tVal, true},
227 {"chained method",
228 "{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
229 "true", tVal, true},
230 {"chained method on variable",
231 "{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
232 "true", tVal, true},
Rob Pike238274e2011-07-07 14:51:35 +1000233
Rob Pike13f88972011-07-04 15:15:47 +1000234 // Pipelines.
Rob Pikea8529812011-07-08 15:22:05 +1000235 {"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true},
Rob Pike238274e2011-07-07 14:51:35 +1000236
Rob Pike13f88972011-07-04 15:15:47 +1000237 // If.
238 {"if true", "{{if true}}TRUE{{end}}", "TRUE", tVal, true},
239 {"if false", "{{if false}}TRUE{{else}}FALSE{{end}}", "FALSE", tVal, true},
240 {"if 1", "{{if 1}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
241 {"if 0", "{{if 0}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
242 {"if 1.5", "{{if 1.5}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
243 {"if 0.0", "{{if .FloatZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
244 {"if 1.5i", "{{if 1.5i}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
245 {"if 0.0i", "{{if .ComplexZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
246 {"if emptystring", "{{if ``}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
247 {"if string", "{{if `notempty`}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
248 {"if emptyslice", "{{if .SIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
249 {"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
250 {"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
251 {"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
Rob Pikefc1f0bd2011-07-14 13:15:55 +1000252 {"if $x with $y int", "{{if $x := true}}{{with $y := .I}}{{$x}},{{$y}}{{end}}{{end}}", "true,17", tVal, true},
253 {"if $x with $x int", "{{if $x := true}}{{with $x := .I}}{{$x}},{{end}}{{$x}}{{end}}", "17,true", tVal, true},
Rob Pike238274e2011-07-07 14:51:35 +1000254
Rob Pikeabae8472011-07-11 09:19:18 +1000255 // Print etc.
256 {"print", `{{print "hello, print"}}`, "hello, print", tVal, true},
257 {"print", `{{print 1 2 3}}`, "1 2 3", tVal, true},
258 {"println", `{{println 1 2 3}}`, "1 2 3\n", tVal, true},
Rob Pikeb177c972011-07-05 14:23:51 +1000259 {"printf int", `{{printf "%04x" 127}}`, "007f", tVal, true},
260 {"printf float", `{{printf "%g" 3.5}}`, "3.5", tVal, true},
261 {"printf complex", `{{printf "%g" 1+7i}}`, "(1+7i)", tVal, true},
262 {"printf string", `{{printf "%s" "hello"}}`, "hello", tVal, true},
Rob Piked3d08e12011-07-08 18:25:46 +1000263 {"printf function", `{{printf "%#q" zeroArgs}}`, "`zeroArgs`", tVal, true},
Rob Pikeb177c972011-07-05 14:23:51 +1000264 {"printf field", `{{printf "%s" .U.V}}`, "v", tVal, true},
Rob Pikea8529812011-07-08 15:22:05 +1000265 {"printf method", `{{printf "%s" .Method0}}`, "M0", tVal, true},
Rob Pikee86d7272011-07-09 17:11:35 +1000266 {"printf dot", `{{with .I}}{{printf "%d" .}}{{end}}`, "17", tVal, true},
267 {"printf var", `{{with $x := .I}}{{printf "%d" $x}}{{end}}`, "17", tVal, true},
Rob Pikea8529812011-07-08 15:22:05 +1000268 {"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
Rob Pike238274e2011-07-07 14:51:35 +1000269
Rob Pikeeea54432011-07-05 17:05:15 +1000270 // HTML.
Rob Pikecc9fed72011-07-05 15:58:54 +1000271 {"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
Rob Pikeeea54432011-07-05 17:05:15 +1000272 "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
Rob Pikecc9fed72011-07-05 15:58:54 +1000273 {"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
Rob Pikeeea54432011-07-05 17:05:15 +1000274 "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
Rob Pike238274e2011-07-07 14:51:35 +1000275
276 // JavaScript.
David Symonds33705dd2011-07-06 16:51:49 +1000277 {"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true},
Rob Pike238274e2011-07-07 14:51:35 +1000278
Rob Pikeeea54432011-07-05 17:05:15 +1000279 // Booleans
280 {"not", "{{not true}} {{not false}}", "false true", nil, true},
Rob Pike469e3332011-07-14 07:59:04 +1000281 {"and", "{{and false 0}} {{and 1 0}} {{and 0 true}} {{and 1 1}}", "false 0 0 1", nil, true},
282 {"or", "{{or 0 0}} {{or 1 0}} {{or 0 true}} {{or 1 1}}", "0 1 true 1", nil, true},
Rob Pikeeea54432011-07-05 17:05:15 +1000283 {"boolean if", "{{if and true 1 `hi`}}TRUE{{else}}FALSE{{end}}", "TRUE", tVal, true},
284 {"boolean if not", "{{if and true 1 `hi` | not}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true},
Rob Pike238274e2011-07-07 14:51:35 +1000285
Rob Pike6732bd32011-07-06 22:27:06 +1000286 // Indexing.
287 {"slice[0]", "{{index .SI 0}}", "3", tVal, true},
288 {"slice[1]", "{{index .SI 1}}", "4", tVal, true},
289 {"slice[HUGE]", "{{index .SI 10}}", "", tVal, false},
290 {"slice[WRONG]", "{{index .SI `hello`}}", "", tVal, false},
291 {"map[one]", "{{index .MSI `one`}}", "1", tVal, true},
292 {"map[two]", "{{index .MSI `two`}}", "2", tVal, true},
293 {"map[NO]", "{{index .MSI `XXX`}}", "", tVal, false},
294 {"map[WRONG]", "{{index .MSI 10}}", "", tVal, false},
295 {"double index", "{{index .SMSI 1 `eleven`}}", "11", tVal, true},
Rob Pike238274e2011-07-07 14:51:35 +1000296
Rob Pike13f88972011-07-04 15:15:47 +1000297 // With.
298 {"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true},
299 {"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true},
300 {"with 1", "{{with 1}}{{.}}{{else}}ZERO{{end}}", "1", tVal, true},
301 {"with 0", "{{with 0}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
302 {"with 1.5", "{{with 1.5}}{{.}}{{else}}ZERO{{end}}", "1.5", tVal, true},
303 {"with 0.0", "{{with .FloatZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
304 {"with 1.5i", "{{with 1.5i}}{{.}}{{else}}ZERO{{end}}", "(0+1.5i)", tVal, true},
305 {"with 0.0i", "{{with .ComplexZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
306 {"with emptystring", "{{with ``}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
307 {"with string", "{{with `notempty`}}{{.}}{{else}}EMPTY{{end}}", "notempty", tVal, true},
308 {"with emptyslice", "{{with .SIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
309 {"with slice", "{{with .SI}}{{.}}{{else}}EMPTY{{end}}", "[3 4 5]", tVal, true},
310 {"with emptymap", "{{with .MSIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
311 {"with map", "{{with .MSIone}}{{.}}{{else}}EMPTY{{end}}", "map[one:1]", tVal, true},
Rob Pike9a5bb282011-07-18 17:34:42 +1000312 {"with empty interface, struct field", "{{with .Empty4}}{{.V}}{{end}}", "UinEmpty", tVal, true},
Rob Pikefc1f0bd2011-07-14 13:15:55 +1000313 {"with $x int", "{{with $x := .I}}{{$x}}{{end}}", "17", tVal, true},
Rob Pikec7057012011-07-17 13:31:59 +1000314 {"with $x struct.U.V", "{{with $x := $}}{{$x.U.V}}{{end}}", "v", tVal, true},
315 {"with variable and action", "{{with $x := $}}{{$y := $.U.V}},{{$y}}{{end}}", "v,v", tVal, true},
Rob Pike238274e2011-07-07 14:51:35 +1000316
Rob Pike13f88972011-07-04 15:15:47 +1000317 // Range.
Rob Pike81592c22011-06-28 23:04:08 +1000318 {"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true},
Rob Pike13f88972011-07-04 15:15:47 +1000319 {"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true},
Rob Pike81592c22011-06-28 23:04:08 +1000320 {"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
Rob Pike13f88972011-07-04 15:15:47 +1000321 {"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
Rob Pikec756a192011-06-29 15:02:04 +1000322 {"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true},
Rob Pike81592c22011-06-28 23:04:08 +1000323 {"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true},
324 {"range map", "{{range .MSI | .MSort}}-{{.}}-{{end}}", "-one--three--two-", tVal, true},
325 {"range empty map no else", "{{range .MSIEmpty}}-{{.}}-{{end}}", "", tVal, true},
326 {"range map else", "{{range .MSI | .MSort}}-{{.}}-{{else}}EMPTY{{end}}", "-one--three--two-", tVal, true},
327 {"range empty map else", "{{range .MSIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
Rob Pike238274e2011-07-07 14:51:35 +1000328 {"range empty interface", "{{range .Empty3}}-{{.}}-{{else}}EMPTY{{end}}", "-7--8-", tVal, true},
Rob Pikefc1f0bd2011-07-14 13:15:55 +1000329 {"range $x SI", "{{range $x := .SI}}<{{$x}}>{{end}}", "<3><4><5>", tVal, true},
330 {"range $x $y SI", "{{range $x, $y := .SI}}<{{$x}}={{$y}}>{{end}}", "<0=3><1=4><2=5>", tVal, true},
331 {"range $x MSIone", "{{range $x := .MSIone}}<{{$x}}>{{end}}", "<1>", tVal, true},
332 {"range $x $y MSIone", "{{range $x, $y := .MSIone}}<{{$x}}={{$y}}>{{end}}", "<one=1>", tVal, true},
333 {"range $x PSI", "{{range $x := .PSI}}<{{$x}}>{{end}}", "<21><22><23>", tVal, true},
Rob Pike238274e2011-07-07 14:51:35 +1000334
Rob Pike469e3332011-07-14 07:59:04 +1000335 // Cute examples.
336 {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true},
337 {"or as if false", `{{or .SIEmpty "slice is empty"}}`, "slice is empty", tVal, true},
338
Rob Pike13f88972011-07-04 15:15:47 +1000339 // Error handling.
Rob Pikec756a192011-06-29 15:02:04 +1000340 {"error method, error", "{{.EPERM true}}", "", tVal, false},
341 {"error method, no error", "{{.EPERM false}}", "false", tVal, true},
Rob Piked366c362011-07-11 18:06:24 +1000342
343 // Fixed bugs.
344 // Must separate dot and receiver; otherwise args are evaluated with dot set to variable.
Rob Pikec3344d62011-07-14 07:52:07 +1000345 {"bug0", "{{range .MSIone}}{{if $.Method1 .}}X{{end}}{{end}}", "X", tVal, true},
Rob Pike81592c22011-06-28 23:04:08 +1000346}
347
Rob Piked3d08e12011-07-08 18:25:46 +1000348func zeroArgs() string {
349 return "zeroArgs"
350}
351
352func oneArg(a string) string {
353 return "oneArg=" + a
Rob Pikeb177c972011-07-05 14:23:51 +1000354}
355
Rob Pike13f88972011-07-04 15:15:47 +1000356func testExecute(execTests []execTest, set *Set, t *testing.T) {
Rob Pike81592c22011-06-28 23:04:08 +1000357 b := new(bytes.Buffer)
Rob Pike7c477412011-07-12 13:15:26 +1000358 funcs := FuncMap{"zeroArgs": zeroArgs, "oneArg": oneArg, "typeOf": typeOf}
Rob Pike81592c22011-06-28 23:04:08 +1000359 for _, test := range execTests {
Rob Pikeb177c972011-07-05 14:23:51 +1000360 tmpl := New(test.name).Funcs(funcs)
Rob Pike81592c22011-06-28 23:04:08 +1000361 err := tmpl.Parse(test.input)
362 if err != nil {
363 t.Errorf("%s: parse error: %s", test.name, err)
364 continue
365 }
366 b.Reset()
Rob Pike13f88972011-07-04 15:15:47 +1000367 err = tmpl.ExecuteInSet(b, test.data, set)
Rob Pike81592c22011-06-28 23:04:08 +1000368 switch {
369 case !test.ok && err == nil:
370 t.Errorf("%s: expected error; got none", test.name)
371 continue
372 case test.ok && err != nil:
373 t.Errorf("%s: unexpected execute error: %s", test.name, err)
374 continue
375 case !test.ok && err != nil:
Rob Pike6732bd32011-07-06 22:27:06 +1000376 // expected error, got one
377 if *debug {
378 fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
379 }
Rob Pike81592c22011-06-28 23:04:08 +1000380 }
381 result := b.String()
382 if result != test.output {
383 t.Errorf("%s: expected\n\t%q\ngot\n\t%q", test.name, test.output, result)
384 }
385 }
386}
387
Rob Pike13f88972011-07-04 15:15:47 +1000388func TestExecute(t *testing.T) {
389 testExecute(execTests, nil, t)
390}
391
Rob Pike81592c22011-06-28 23:04:08 +1000392// Check that an error from a method flows back to the top.
393func TestExecuteError(t *testing.T) {
394 b := new(bytes.Buffer)
395 tmpl := New("error")
Rob Pikec756a192011-06-29 15:02:04 +1000396 err := tmpl.Parse("{{.EPERM true}}")
Rob Pike81592c22011-06-28 23:04:08 +1000397 if err != nil {
398 t.Fatalf("parse error: %s", err)
399 }
400 err = tmpl.Execute(b, tVal)
401 if err == nil {
402 t.Errorf("expected error; got none")
403 } else if !strings.Contains(err.String(), os.EPERM.String()) {
Rob Pikedfffc7a2011-07-14 11:32:06 +1000404 if *debug {
405 fmt.Printf("test execute error: %s\n", err)
406 }
Rob Pikec756a192011-06-29 15:02:04 +1000407 t.Errorf("expected os.EPERM; got %s", err)
Rob Pike81592c22011-06-28 23:04:08 +1000408 }
409}
David Symonds33705dd2011-07-06 16:51:49 +1000410
411func TestJSEscaping(t *testing.T) {
412 testCases := []struct {
413 in, exp string
414 }{
415 {`a`, `a`},
416 {`'foo`, `\'foo`},
417 {`Go "jump" \`, `Go \"jump\" \\`},
418 {`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`},
419 {"unprintable \uFDFF", `unprintable \uFDFF`},
David Symondsa16ad6f2011-07-14 12:02:58 +1000420 {`<html>`, `\x3Chtml\x3E`},
David Symonds33705dd2011-07-06 16:51:49 +1000421 }
422 for _, tc := range testCases {
423 s := JSEscapeString(tc.in)
424 if s != tc.exp {
425 t.Errorf("JS escaping [%s] got [%s] want [%s]", tc.in, s, tc.exp)
426 }
427 }
428}
Rob Pike02039b62011-07-08 16:49:06 +1000429
430// A nice example: walk a binary tree.
431
432type Tree struct {
433 Val int
434 Left, Right *Tree
435}
436
437const treeTemplate = `
438 {{define "tree"}}
439 [
440 {{.Val}}
441 {{with .Left}}
442 {{template "tree" .}}
443 {{end}}
444 {{with .Right}}
445 {{template "tree" .}}
446 {{end}}
447 ]
448 {{end}}
449`
450
451func TestTree(t *testing.T) {
452 var tree = &Tree{
453 1,
454 &Tree{
455 2, &Tree{
456 3,
457 &Tree{
458 4, nil, nil,
459 },
460 nil,
461 },
462 &Tree{
463 5,
464 &Tree{
465 6, nil, nil,
466 },
467 nil,
468 },
469 },
470 &Tree{
471 7,
472 &Tree{
473 8,
474 &Tree{
475 9, nil, nil,
476 },
477 nil,
478 },
479 &Tree{
480 10,
481 &Tree{
482 11, nil, nil,
483 },
484 nil,
485 },
486 },
487 }
Rob Pike7aa1a1a2011-07-13 15:58:31 +1000488 set := new(Set)
Rob Pike02039b62011-07-08 16:49:06 +1000489 err := set.Parse(treeTemplate)
490 if err != nil {
491 t.Fatal("parse error:", err)
492 }
493 var b bytes.Buffer
Rob Pike7aa1a1a2011-07-13 15:58:31 +1000494 err = set.Execute(&b, "tree", tree)
Rob Pike02039b62011-07-08 16:49:06 +1000495 if err != nil {
496 t.Fatal("exec error:", err)
497 }
498 stripSpace := func(r int) int {
499 if r == '\t' || r == '\n' {
500 return -1
501 }
502 return r
503 }
504 result := strings.Map(stripSpace, b.String())
505 const expect = "[1[2[3[4]][5[6]]][7[8[9]][10[11]]]]"
506 if result != expect {
507 t.Errorf("expected %q got %q", expect, result)
508 }
509}