1 |
747 |
jeremybenn |
// 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 |
|
|
|
5 |
|
|
package template
|
6 |
|
|
|
7 |
|
|
import (
|
8 |
|
|
"bytes"
|
9 |
|
|
"errors"
|
10 |
|
|
"flag"
|
11 |
|
|
"fmt"
|
12 |
|
|
"os"
|
13 |
|
|
"reflect"
|
14 |
|
|
"strings"
|
15 |
|
|
"testing"
|
16 |
|
|
)
|
17 |
|
|
|
18 |
|
|
var debug = flag.Bool("debug", false, "show the errors produced by the tests")
|
19 |
|
|
|
20 |
|
|
// T has lots of interesting pieces to use to test execution.
|
21 |
|
|
type T struct {
|
22 |
|
|
// Basics
|
23 |
|
|
True bool
|
24 |
|
|
I int
|
25 |
|
|
U16 uint16
|
26 |
|
|
X string
|
27 |
|
|
FloatZero float64
|
28 |
|
|
ComplexZero float64
|
29 |
|
|
// Nested structs.
|
30 |
|
|
U *U
|
31 |
|
|
// Struct with String method.
|
32 |
|
|
V0 V
|
33 |
|
|
V1, V2 *V
|
34 |
|
|
// Struct with Error method.
|
35 |
|
|
W0 W
|
36 |
|
|
W1, W2 *W
|
37 |
|
|
// Slices
|
38 |
|
|
SI []int
|
39 |
|
|
SIEmpty []int
|
40 |
|
|
SB []bool
|
41 |
|
|
// Maps
|
42 |
|
|
MSI map[string]int
|
43 |
|
|
MSIone map[string]int // one element, for deterministic output
|
44 |
|
|
MSIEmpty map[string]int
|
45 |
|
|
MXI map[interface{}]int
|
46 |
|
|
MII map[int]int
|
47 |
|
|
SMSI []map[string]int
|
48 |
|
|
// Empty interfaces; used to see if we can dig inside one.
|
49 |
|
|
Empty0 interface{} // nil
|
50 |
|
|
Empty1 interface{}
|
51 |
|
|
Empty2 interface{}
|
52 |
|
|
Empty3 interface{}
|
53 |
|
|
Empty4 interface{}
|
54 |
|
|
// Non-empty interface.
|
55 |
|
|
NonEmptyInterface I
|
56 |
|
|
// Stringer.
|
57 |
|
|
Str fmt.Stringer
|
58 |
|
|
Err error
|
59 |
|
|
// Pointers
|
60 |
|
|
PI *int
|
61 |
|
|
PSI *[]int
|
62 |
|
|
NIL *int
|
63 |
|
|
// Template to test evaluation of templates.
|
64 |
|
|
Tmpl *Template
|
65 |
|
|
}
|
66 |
|
|
|
67 |
|
|
type U struct {
|
68 |
|
|
V string
|
69 |
|
|
}
|
70 |
|
|
|
71 |
|
|
type V struct {
|
72 |
|
|
j int
|
73 |
|
|
}
|
74 |
|
|
|
75 |
|
|
func (v *V) String() string {
|
76 |
|
|
if v == nil {
|
77 |
|
|
return "nilV"
|
78 |
|
|
}
|
79 |
|
|
return fmt.Sprintf("<%d>", v.j)
|
80 |
|
|
}
|
81 |
|
|
|
82 |
|
|
type W struct {
|
83 |
|
|
k int
|
84 |
|
|
}
|
85 |
|
|
|
86 |
|
|
func (w *W) Error() string {
|
87 |
|
|
if w == nil {
|
88 |
|
|
return "nilW"
|
89 |
|
|
}
|
90 |
|
|
return fmt.Sprintf("[%d]", w.k)
|
91 |
|
|
}
|
92 |
|
|
|
93 |
|
|
var tVal = &T{
|
94 |
|
|
True: true,
|
95 |
|
|
I: 17,
|
96 |
|
|
U16: 16,
|
97 |
|
|
X: "x",
|
98 |
|
|
U: &U{"v"},
|
99 |
|
|
V0: V{6666},
|
100 |
|
|
V1: &V{7777}, // leave V2 as nil
|
101 |
|
|
W0: W{888},
|
102 |
|
|
W1: &W{999}, // leave W2 as nil
|
103 |
|
|
SI: []int{3, 4, 5},
|
104 |
|
|
SB: []bool{true, false},
|
105 |
|
|
MSI: map[string]int{"one": 1, "two": 2, "three": 3},
|
106 |
|
|
MSIone: map[string]int{"one": 1},
|
107 |
|
|
MXI: map[interface{}]int{"one": 1},
|
108 |
|
|
MII: map[int]int{1: 1},
|
109 |
|
|
SMSI: []map[string]int{
|
110 |
|
|
{"one": 1, "two": 2},
|
111 |
|
|
{"eleven": 11, "twelve": 12},
|
112 |
|
|
},
|
113 |
|
|
Empty1: 3,
|
114 |
|
|
Empty2: "empty2",
|
115 |
|
|
Empty3: []int{7, 8},
|
116 |
|
|
Empty4: &U{"UinEmpty"},
|
117 |
|
|
NonEmptyInterface: new(T),
|
118 |
|
|
Str: bytes.NewBuffer([]byte("foozle")),
|
119 |
|
|
Err: errors.New("erroozle"),
|
120 |
|
|
PI: newInt(23),
|
121 |
|
|
PSI: newIntSlice(21, 22, 23),
|
122 |
|
|
Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X
|
123 |
|
|
}
|
124 |
|
|
|
125 |
|
|
// A non-empty interface.
|
126 |
|
|
type I interface {
|
127 |
|
|
Method0() string
|
128 |
|
|
}
|
129 |
|
|
|
130 |
|
|
var iVal I = tVal
|
131 |
|
|
|
132 |
|
|
// Helpers for creation.
|
133 |
|
|
func newInt(n int) *int {
|
134 |
|
|
p := new(int)
|
135 |
|
|
*p = n
|
136 |
|
|
return p
|
137 |
|
|
}
|
138 |
|
|
|
139 |
|
|
func newIntSlice(n ...int) *[]int {
|
140 |
|
|
p := new([]int)
|
141 |
|
|
*p = make([]int, len(n))
|
142 |
|
|
copy(*p, n)
|
143 |
|
|
return p
|
144 |
|
|
}
|
145 |
|
|
|
146 |
|
|
// Simple methods with and without arguments.
|
147 |
|
|
func (t *T) Method0() string {
|
148 |
|
|
return "M0"
|
149 |
|
|
}
|
150 |
|
|
|
151 |
|
|
func (t *T) Method1(a int) int {
|
152 |
|
|
return a
|
153 |
|
|
}
|
154 |
|
|
|
155 |
|
|
func (t *T) Method2(a uint16, b string) string {
|
156 |
|
|
return fmt.Sprintf("Method2: %d %s", a, b)
|
157 |
|
|
}
|
158 |
|
|
|
159 |
|
|
func (t *T) Method3(v interface{}) string {
|
160 |
|
|
return fmt.Sprintf("Method3: %v", v)
|
161 |
|
|
}
|
162 |
|
|
|
163 |
|
|
func (t *T) MAdd(a int, b []int) []int {
|
164 |
|
|
v := make([]int, len(b))
|
165 |
|
|
for i, x := range b {
|
166 |
|
|
v[i] = x + a
|
167 |
|
|
}
|
168 |
|
|
return v
|
169 |
|
|
}
|
170 |
|
|
|
171 |
|
|
// EPERM returns a value and an error according to its argument.
|
172 |
|
|
func (t *T) EPERM(error bool) (bool, error) {
|
173 |
|
|
if error {
|
174 |
|
|
return true, os.EPERM
|
175 |
|
|
}
|
176 |
|
|
return false, nil
|
177 |
|
|
}
|
178 |
|
|
|
179 |
|
|
// A few methods to test chaining.
|
180 |
|
|
func (t *T) GetU() *U {
|
181 |
|
|
return t.U
|
182 |
|
|
}
|
183 |
|
|
|
184 |
|
|
func (u *U) TrueFalse(b bool) string {
|
185 |
|
|
if b {
|
186 |
|
|
return "true"
|
187 |
|
|
}
|
188 |
|
|
return ""
|
189 |
|
|
}
|
190 |
|
|
|
191 |
|
|
func typeOf(arg interface{}) string {
|
192 |
|
|
return fmt.Sprintf("%T", arg)
|
193 |
|
|
}
|
194 |
|
|
|
195 |
|
|
type execTest struct {
|
196 |
|
|
name string
|
197 |
|
|
input string
|
198 |
|
|
output string
|
199 |
|
|
data interface{}
|
200 |
|
|
ok bool
|
201 |
|
|
}
|
202 |
|
|
|
203 |
|
|
// bigInt and bigUint are hex string representing numbers either side
|
204 |
|
|
// of the max int boundary.
|
205 |
|
|
// We do it this way so the test doesn't depend on ints being 32 bits.
|
206 |
|
|
var (
|
207 |
|
|
bigInt = fmt.Sprintf("0x%x", int(1<
|
208 |
|
|
bigUint = fmt.Sprintf("0x%x", uint(1<
|
209 |
|
|
)
|
210 |
|
|
|
211 |
|
|
var execTests = []execTest{
|
212 |
|
|
// Trivial cases.
|
213 |
|
|
{"empty", "", "", nil, true},
|
214 |
|
|
{"text", "some text", "some text", nil, true},
|
215 |
|
|
|
216 |
|
|
// Ideal constants.
|
217 |
|
|
{"ideal int", "{{typeOf 3}}", "int", 0, true},
|
218 |
|
|
{"ideal float", "{{typeOf 1.0}}", "float64", 0, true},
|
219 |
|
|
{"ideal exp float", "{{typeOf 1e1}}", "float64", 0, true},
|
220 |
|
|
{"ideal complex", "{{typeOf 1i}}", "complex128", 0, true},
|
221 |
|
|
{"ideal int", "{{typeOf " + bigInt + "}}", "int", 0, true},
|
222 |
|
|
{"ideal too big", "{{typeOf " + bigUint + "}}", "", 0, false},
|
223 |
|
|
|
224 |
|
|
// Fields of structs.
|
225 |
|
|
{".X", "-{{.X}}-", "-x-", tVal, true},
|
226 |
|
|
{".U.V", "-{{.U.V}}-", "-v-", tVal, true},
|
227 |
|
|
|
228 |
|
|
// Fields on maps.
|
229 |
|
|
{"map .one", "{{.MSI.one}}", "1", tVal, true},
|
230 |
|
|
{"map .two", "{{.MSI.two}}", "2", tVal, true},
|
231 |
|
|
{"map .NO", "{{.MSI.NO}}", "", tVal, true},
|
232 |
|
|
{"map .one interface", "{{.MXI.one}}", "1", tVal, true},
|
233 |
|
|
{"map .WRONG args", "{{.MSI.one 1}}", "", tVal, false},
|
234 |
|
|
{"map .WRONG type", "{{.MII.one}}", "", tVal, false},
|
235 |
|
|
|
236 |
|
|
// Dots of all kinds to test basic evaluation.
|
237 |
|
|
{"dot int", "<{{.}}>", "<13>", 13, true},
|
238 |
|
|
{"dot uint", "<{{.}}>", "<14>", uint(14), true},
|
239 |
|
|
{"dot float", "<{{.}}>", "<15.1>", 15.1, true},
|
240 |
|
|
{"dot bool", "<{{.}}>", "", true, true},
|
241 |
|
|
{"dot complex", "<{{.}}>", "<(16.2-17i)>", 16.2 - 17i, true},
|
242 |
|
|
{"dot string", "<{{.}}>", "", "hello", true},
|
243 |
|
|
{"dot slice", "<{{.}}>", "<[-1 -2 -3]>", []int{-1, -2, -3}, true},
|
244 |
|
|
{"dot map", "<{{.}}>", "
|
245 |
|
|
{"dot struct", "<{{.}}>", "<{7 seven}>", struct {
|
246 |
|
|
a int
|
247 |
|
|
b string
|
248 |
|
|
}{7, "seven"}, true},
|
249 |
|
|
|
250 |
|
|
// Variables.
|
251 |
|
|
{"$ int", "{{$}}", "123", 123, true},
|
252 |
|
|
{"$.I", "{{$.I}}", "17", tVal, true},
|
253 |
|
|
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
|
254 |
|
|
{"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
|
255 |
|
|
|
256 |
|
|
// Type with String method.
|
257 |
|
|
{"V{6666}.String()", "-{{.V0}}-", "-<6666>-", tVal, true},
|
258 |
|
|
{"&V{7777}.String()", "-{{.V1}}-", "-<7777>-", tVal, true},
|
259 |
|
|
{"(*V)(nil).String()", "-{{.V2}}-", "-nilV-", tVal, true},
|
260 |
|
|
|
261 |
|
|
// Type with Error method.
|
262 |
|
|
{"W{888}.Error()", "-{{.W0}}-", "-[888]-", tVal, true},
|
263 |
|
|
{"&W{999}.Error()", "-{{.W1}}-", "-[999]-", tVal, true},
|
264 |
|
|
{"(*W)(nil).Error()", "-{{.W2}}-", "-nilW-", tVal, true},
|
265 |
|
|
|
266 |
|
|
// Pointers.
|
267 |
|
|
{"*int", "{{.PI}}", "23", tVal, true},
|
268 |
|
|
{"*[]int", "{{.PSI}}", "[21 22 23]", tVal, true},
|
269 |
|
|
{"*[]int[1]", "{{index .PSI 1}}", "22", tVal, true},
|
270 |
|
|
{"NIL", "{{.NIL}}", "", tVal, true},
|
271 |
|
|
|
272 |
|
|
// Empty interfaces holding values.
|
273 |
|
|
{"empty nil", "{{.Empty0}}", "", tVal, true},
|
274 |
|
|
{"empty with int", "{{.Empty1}}", "3", tVal, true},
|
275 |
|
|
{"empty with string", "{{.Empty2}}", "empty2", tVal, true},
|
276 |
|
|
{"empty with slice", "{{.Empty3}}", "[7 8]", tVal, true},
|
277 |
|
|
{"empty with struct", "{{.Empty4}}", "{UinEmpty}", tVal, true},
|
278 |
|
|
{"empty with struct, field", "{{.Empty4.V}}", "UinEmpty", tVal, true},
|
279 |
|
|
|
280 |
|
|
// Method calls.
|
281 |
|
|
{".Method0", "-{{.Method0}}-", "-M0-", tVal, true},
|
282 |
|
|
{".Method1(1234)", "-{{.Method1 1234}}-", "-1234-", tVal, true},
|
283 |
|
|
{".Method1(.I)", "-{{.Method1 .I}}-", "-17-", tVal, true},
|
284 |
|
|
{".Method2(3, .X)", "-{{.Method2 3 .X}}-", "-Method2: 3 x-", tVal, true},
|
285 |
|
|
{".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true},
|
286 |
|
|
{".Method2(.U16, $x)", "{{if $x := .X}}-{{.Method2 .U16 $x}}{{end}}-", "-Method2: 16 x-", tVal, true},
|
287 |
|
|
{".Method3(nil)", "-{{.Method3 .MXI.unset}}-", "-Method3: -", tVal, true},
|
288 |
|
|
{"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
|
289 |
|
|
{"method on chained var",
|
290 |
|
|
"{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
291 |
|
|
"true", tVal, true},
|
292 |
|
|
{"chained method",
|
293 |
|
|
"{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
294 |
|
|
"true", tVal, true},
|
295 |
|
|
{"chained method on variable",
|
296 |
|
|
"{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
|
297 |
|
|
"true", tVal, true},
|
298 |
|
|
|
299 |
|
|
// Pipelines.
|
300 |
|
|
{"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true},
|
301 |
|
|
|
302 |
|
|
// If.
|
303 |
|
|
{"if true", "{{if true}}TRUE{{end}}", "TRUE", tVal, true},
|
304 |
|
|
{"if false", "{{if false}}TRUE{{else}}FALSE{{end}}", "FALSE", tVal, true},
|
305 |
|
|
{"if 1", "{{if 1}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
|
306 |
|
|
{"if 0", "{{if 0}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
|
307 |
|
|
{"if 1.5", "{{if 1.5}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
|
308 |
|
|
{"if 0.0", "{{if .FloatZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
|
309 |
|
|
{"if 1.5i", "{{if 1.5i}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
|
310 |
|
|
{"if 0.0i", "{{if .ComplexZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
|
311 |
|
|
{"if emptystring", "{{if ``}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
312 |
|
|
{"if string", "{{if `notempty`}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
|
313 |
|
|
{"if emptyslice", "{{if .SIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
314 |
|
|
{"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
|
315 |
|
|
{"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
316 |
|
|
{"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
|
317 |
|
|
{"if map unset", "{{if .MXI.none}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
|
318 |
|
|
{"if map not unset", "{{if not .MXI.none}}ZERO{{else}}NON-ZERO{{end}}", "ZERO", tVal, true},
|
319 |
|
|
{"if $x with $y int", "{{if $x := true}}{{with $y := .I}}{{$x}},{{$y}}{{end}}{{end}}", "true,17", tVal, true},
|
320 |
|
|
{"if $x with $x int", "{{if $x := true}}{{with $x := .I}}{{$x}},{{end}}{{$x}}{{end}}", "17,true", tVal, true},
|
321 |
|
|
|
322 |
|
|
// Print etc.
|
323 |
|
|
{"print", `{{print "hello, print"}}`, "hello, print", tVal, true},
|
324 |
|
|
{"print", `{{print 1 2 3}}`, "1 2 3", tVal, true},
|
325 |
|
|
{"println", `{{println 1 2 3}}`, "1 2 3\n", tVal, true},
|
326 |
|
|
{"printf int", `{{printf "%04x" 127}}`, "007f", tVal, true},
|
327 |
|
|
{"printf float", `{{printf "%g" 3.5}}`, "3.5", tVal, true},
|
328 |
|
|
{"printf complex", `{{printf "%g" 1+7i}}`, "(1+7i)", tVal, true},
|
329 |
|
|
{"printf string", `{{printf "%s" "hello"}}`, "hello", tVal, true},
|
330 |
|
|
{"printf function", `{{printf "%#q" zeroArgs}}`, "`zeroArgs`", tVal, true},
|
331 |
|
|
{"printf field", `{{printf "%s" .U.V}}`, "v", tVal, true},
|
332 |
|
|
{"printf method", `{{printf "%s" .Method0}}`, "M0", tVal, true},
|
333 |
|
|
{"printf dot", `{{with .I}}{{printf "%d" .}}{{end}}`, "17", tVal, true},
|
334 |
|
|
{"printf var", `{{with $x := .I}}{{printf "%d" $x}}{{end}}`, "17", tVal, true},
|
335 |
|
|
{"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
|
336 |
|
|
|
337 |
|
|
// HTML.
|
338 |
|
|
{"html", `{{html ""}}`,
|
339 |
|
|
"<script>alert("XSS");</script>", nil, true},
|
340 |
|
|
{"html pipeline", `{{printf "" | html}}`,
|
341 |
|
|
"<script>alert("XSS");</script>", nil, true},
|
342 |
|
|
|
343 |
|
|
// JavaScript.
|
344 |
|
|
{"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true},
|
345 |
|
|
|
346 |
|
|
// URL query.
|
347 |
|
|
{"urlquery", `{{"http://www.example.org/"|urlquery}}`, "http%3A%2F%2Fwww.example.org%2F", nil, true},
|
348 |
|
|
|
349 |
|
|
// Booleans
|
350 |
|
|
{"not", "{{not true}} {{not false}}", "false true", nil, true},
|
351 |
|
|
{"and", "{{and false 0}} {{and 1 0}} {{and 0 true}} {{and 1 1}}", "false 0 0 1", nil, true},
|
352 |
|
|
{"or", "{{or 0 0}} {{or 1 0}} {{or 0 true}} {{or 1 1}}", "0 1 true 1", nil, true},
|
353 |
|
|
{"boolean if", "{{if and true 1 `hi`}}TRUE{{else}}FALSE{{end}}", "TRUE", tVal, true},
|
354 |
|
|
{"boolean if not", "{{if and true 1 `hi` | not}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true},
|
355 |
|
|
|
356 |
|
|
// Indexing.
|
357 |
|
|
{"slice[0]", "{{index .SI 0}}", "3", tVal, true},
|
358 |
|
|
{"slice[1]", "{{index .SI 1}}", "4", tVal, true},
|
359 |
|
|
{"slice[HUGE]", "{{index .SI 10}}", "", tVal, false},
|
360 |
|
|
{"slice[WRONG]", "{{index .SI `hello`}}", "", tVal, false},
|
361 |
|
|
{"map[one]", "{{index .MSI `one`}}", "1", tVal, true},
|
362 |
|
|
{"map[two]", "{{index .MSI `two`}}", "2", tVal, true},
|
363 |
|
|
{"map[NO]", "{{index .MSI `XXX`}}", "", tVal, true},
|
364 |
|
|
{"map[WRONG]", "{{index .MSI 10}}", "", tVal, false},
|
365 |
|
|
{"double index", "{{index .SMSI 1 `eleven`}}", "11", tVal, true},
|
366 |
|
|
|
367 |
|
|
// Len.
|
368 |
|
|
{"slice", "{{len .SI}}", "3", tVal, true},
|
369 |
|
|
{"map", "{{len .MSI }}", "3", tVal, true},
|
370 |
|
|
{"len of int", "{{len 3}}", "", tVal, false},
|
371 |
|
|
{"len of nothing", "{{len .Empty0}}", "", tVal, false},
|
372 |
|
|
|
373 |
|
|
// With.
|
374 |
|
|
{"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true},
|
375 |
|
|
{"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true},
|
376 |
|
|
{"with 1", "{{with 1}}{{.}}{{else}}ZERO{{end}}", "1", tVal, true},
|
377 |
|
|
{"with 0", "{{with 0}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
|
378 |
|
|
{"with 1.5", "{{with 1.5}}{{.}}{{else}}ZERO{{end}}", "1.5", tVal, true},
|
379 |
|
|
{"with 0.0", "{{with .FloatZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
|
380 |
|
|
{"with 1.5i", "{{with 1.5i}}{{.}}{{else}}ZERO{{end}}", "(0+1.5i)", tVal, true},
|
381 |
|
|
{"with 0.0i", "{{with .ComplexZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
|
382 |
|
|
{"with emptystring", "{{with ``}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
383 |
|
|
{"with string", "{{with `notempty`}}{{.}}{{else}}EMPTY{{end}}", "notempty", tVal, true},
|
384 |
|
|
{"with emptyslice", "{{with .SIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
385 |
|
|
{"with slice", "{{with .SI}}{{.}}{{else}}EMPTY{{end}}", "[3 4 5]", tVal, true},
|
386 |
|
|
{"with emptymap", "{{with .MSIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
387 |
|
|
{"with map", "{{with .MSIone}}{{.}}{{else}}EMPTY{{end}}", "map[one:1]", tVal, true},
|
388 |
|
|
{"with empty interface, struct field", "{{with .Empty4}}{{.V}}{{end}}", "UinEmpty", tVal, true},
|
389 |
|
|
{"with $x int", "{{with $x := .I}}{{$x}}{{end}}", "17", tVal, true},
|
390 |
|
|
{"with $x struct.U.V", "{{with $x := $}}{{$x.U.V}}{{end}}", "v", tVal, true},
|
391 |
|
|
{"with variable and action", "{{with $x := $}}{{$y := $.U.V}}{{$y}}{{end}}", "v", tVal, true},
|
392 |
|
|
|
393 |
|
|
// Range.
|
394 |
|
|
{"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true},
|
395 |
|
|
{"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true},
|
396 |
|
|
{"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
|
397 |
|
|
{"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
398 |
|
|
{"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true},
|
399 |
|
|
{"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true},
|
400 |
|
|
{"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true},
|
401 |
|
|
{"range empty map no else", "{{range .MSIEmpty}}-{{.}}-{{end}}", "", tVal, true},
|
402 |
|
|
{"range map else", "{{range .MSI}}-{{.}}-{{else}}EMPTY{{end}}", "-1--3--2-", tVal, true},
|
403 |
|
|
{"range empty map else", "{{range .MSIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
404 |
|
|
{"range empty interface", "{{range .Empty3}}-{{.}}-{{else}}EMPTY{{end}}", "-7--8-", tVal, true},
|
405 |
|
|
{"range empty nil", "{{range .Empty0}}-{{.}}-{{end}}", "", tVal, true},
|
406 |
|
|
{"range $x SI", "{{range $x := .SI}}<{{$x}}>{{end}}", "<3><4><5>", tVal, true},
|
407 |
|
|
{"range $x $y SI", "{{range $x, $y := .SI}}<{{$x}}={{$y}}>{{end}}", "<0=3><1=4><2=5>", tVal, true},
|
408 |
|
|
{"range $x MSIone", "{{range $x := .MSIone}}<{{$x}}>{{end}}", "<1>", tVal, true},
|
409 |
|
|
{"range $x $y MSIone", "{{range $x, $y := .MSIone}}<{{$x}}={{$y}}>{{end}}", "", tVal, true},
|
410 |
|
|
{"range $x PSI", "{{range $x := .PSI}}<{{$x}}>{{end}}", "<21><22><23>", tVal, true},
|
411 |
|
|
{"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true},
|
412 |
|
|
{"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true},
|
413 |
|
|
{"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true},
|
414 |
|
|
|
415 |
|
|
// Cute examples.
|
416 |
|
|
{"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true},
|
417 |
|
|
{"or as if false", `{{or .SIEmpty "slice is empty"}}`, "slice is empty", tVal, true},
|
418 |
|
|
|
419 |
|
|
// Error handling.
|
420 |
|
|
{"error method, error", "{{.EPERM true}}", "", tVal, false},
|
421 |
|
|
{"error method, no error", "{{.EPERM false}}", "false", tVal, true},
|
422 |
|
|
|
423 |
|
|
// Fixed bugs.
|
424 |
|
|
// Must separate dot and receiver; otherwise args are evaluated with dot set to variable.
|
425 |
|
|
{"bug0", "{{range .MSIone}}{{if $.Method1 .}}X{{end}}{{end}}", "X", tVal, true},
|
426 |
|
|
// Do not loop endlessly in indirect for non-empty interfaces.
|
427 |
|
|
// The bug appears with *interface only; looped forever.
|
428 |
|
|
{"bug1", "{{.Method0}}", "M0", &iVal, true},
|
429 |
|
|
// Was taking address of interface field, so method set was empty.
|
430 |
|
|
{"bug2", "{{$.NonEmptyInterface.Method0}}", "M0", tVal, true},
|
431 |
|
|
// Struct values were not legal in with - mere oversight.
|
432 |
|
|
{"bug3", "{{with $}}{{.Method0}}{{end}}", "M0", tVal, true},
|
433 |
|
|
// Nil interface values in if.
|
434 |
|
|
{"bug4", "{{if .Empty0}}non-nil{{else}}nil{{end}}", "nil", tVal, true},
|
435 |
|
|
// Stringer.
|
436 |
|
|
{"bug5", "{{.Str}}", "foozle", tVal, true},
|
437 |
|
|
{"bug5a", "{{.Err}}", "erroozle", tVal, true},
|
438 |
|
|
// Args need to be indirected and dereferenced sometimes.
|
439 |
|
|
{"bug6a", "{{vfunc .V0 .V1}}", "vfunc", tVal, true},
|
440 |
|
|
{"bug6b", "{{vfunc .V0 .V0}}", "vfunc", tVal, true},
|
441 |
|
|
{"bug6c", "{{vfunc .V1 .V0}}", "vfunc", tVal, true},
|
442 |
|
|
{"bug6d", "{{vfunc .V1 .V1}}", "vfunc", tVal, true},
|
443 |
|
|
}
|
444 |
|
|
|
445 |
|
|
func zeroArgs() string {
|
446 |
|
|
return "zeroArgs"
|
447 |
|
|
}
|
448 |
|
|
|
449 |
|
|
func oneArg(a string) string {
|
450 |
|
|
return "oneArg=" + a
|
451 |
|
|
}
|
452 |
|
|
|
453 |
|
|
// count returns a channel that will deliver n sequential 1-letter strings starting at "a"
|
454 |
|
|
func count(n int) chan string {
|
455 |
|
|
if n == 0 {
|
456 |
|
|
return nil
|
457 |
|
|
}
|
458 |
|
|
c := make(chan string)
|
459 |
|
|
go func() {
|
460 |
|
|
for i := 0; i < n; i++ {
|
461 |
|
|
c <- "abcdefghijklmnop"[i : i+1]
|
462 |
|
|
}
|
463 |
|
|
close(c)
|
464 |
|
|
}()
|
465 |
|
|
return c
|
466 |
|
|
}
|
467 |
|
|
|
468 |
|
|
// vfunc takes a *V and a V
|
469 |
|
|
func vfunc(V, *V) string {
|
470 |
|
|
return "vfunc"
|
471 |
|
|
}
|
472 |
|
|
|
473 |
|
|
func testExecute(execTests []execTest, template *Template, t *testing.T) {
|
474 |
|
|
b := new(bytes.Buffer)
|
475 |
|
|
funcs := FuncMap{
|
476 |
|
|
"count": count,
|
477 |
|
|
"oneArg": oneArg,
|
478 |
|
|
"typeOf": typeOf,
|
479 |
|
|
"vfunc": vfunc,
|
480 |
|
|
"zeroArgs": zeroArgs,
|
481 |
|
|
}
|
482 |
|
|
for _, test := range execTests {
|
483 |
|
|
var tmpl *Template
|
484 |
|
|
var err error
|
485 |
|
|
if template == nil {
|
486 |
|
|
tmpl, err = New(test.name).Funcs(funcs).Parse(test.input)
|
487 |
|
|
} else {
|
488 |
|
|
tmpl, err = template.New(test.name).Funcs(funcs).Parse(test.input)
|
489 |
|
|
}
|
490 |
|
|
if err != nil {
|
491 |
|
|
t.Errorf("%s: parse error: %s", test.name, err)
|
492 |
|
|
continue
|
493 |
|
|
}
|
494 |
|
|
b.Reset()
|
495 |
|
|
err = tmpl.Execute(b, test.data)
|
496 |
|
|
switch {
|
497 |
|
|
case !test.ok && err == nil:
|
498 |
|
|
t.Errorf("%s: expected error; got none", test.name)
|
499 |
|
|
continue
|
500 |
|
|
case test.ok && err != nil:
|
501 |
|
|
t.Errorf("%s: unexpected execute error: %s", test.name, err)
|
502 |
|
|
continue
|
503 |
|
|
case !test.ok && err != nil:
|
504 |
|
|
// expected error, got one
|
505 |
|
|
if *debug {
|
506 |
|
|
fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
|
507 |
|
|
}
|
508 |
|
|
}
|
509 |
|
|
result := b.String()
|
510 |
|
|
if result != test.output {
|
511 |
|
|
t.Errorf("%s: expected\n\t%q\ngot\n\t%q", test.name, test.output, result)
|
512 |
|
|
}
|
513 |
|
|
}
|
514 |
|
|
}
|
515 |
|
|
|
516 |
|
|
func TestExecute(t *testing.T) {
|
517 |
|
|
testExecute(execTests, nil, t)
|
518 |
|
|
}
|
519 |
|
|
|
520 |
|
|
var delimPairs = []string{
|
521 |
|
|
"", "", // default
|
522 |
|
|
"{{", "}}", // same as default
|
523 |
|
|
"<<", ">>", // distinct
|
524 |
|
|
"|", "|", // same
|
525 |
|
|
"(日)", "(本)", // peculiar
|
526 |
|
|
}
|
527 |
|
|
|
528 |
|
|
func TestDelims(t *testing.T) {
|
529 |
|
|
const hello = "Hello, world"
|
530 |
|
|
var value = struct{ Str string }{hello}
|
531 |
|
|
for i := 0; i < len(delimPairs); i += 2 {
|
532 |
|
|
text := ".Str"
|
533 |
|
|
left := delimPairs[i+0]
|
534 |
|
|
trueLeft := left
|
535 |
|
|
right := delimPairs[i+1]
|
536 |
|
|
trueRight := right
|
537 |
|
|
if left == "" { // default case
|
538 |
|
|
trueLeft = "{{"
|
539 |
|
|
}
|
540 |
|
|
if right == "" { // default case
|
541 |
|
|
trueRight = "}}"
|
542 |
|
|
}
|
543 |
|
|
text = trueLeft + text + trueRight
|
544 |
|
|
// Now add a comment
|
545 |
|
|
text += trueLeft + "/*comment*/" + trueRight
|
546 |
|
|
// Now add an action containing a string.
|
547 |
|
|
text += trueLeft + `"` + trueLeft + `"` + trueRight
|
548 |
|
|
// At this point text looks like `{{.Str}}{{/*comment*/}}{{"{{"}}`.
|
549 |
|
|
tmpl, err := New("delims").Delims(left, right).Parse(text)
|
550 |
|
|
if err != nil {
|
551 |
|
|
t.Fatalf("delim %q text %q parse err %s", left, text, err)
|
552 |
|
|
}
|
553 |
|
|
var b = new(bytes.Buffer)
|
554 |
|
|
err = tmpl.Execute(b, value)
|
555 |
|
|
if err != nil {
|
556 |
|
|
t.Fatalf("delim %q exec err %s", left, err)
|
557 |
|
|
}
|
558 |
|
|
if b.String() != hello+trueLeft {
|
559 |
|
|
t.Errorf("expected %q got %q", hello+trueLeft, b.String())
|
560 |
|
|
}
|
561 |
|
|
}
|
562 |
|
|
}
|
563 |
|
|
|
564 |
|
|
// Check that an error from a method flows back to the top.
|
565 |
|
|
func TestExecuteError(t *testing.T) {
|
566 |
|
|
b := new(bytes.Buffer)
|
567 |
|
|
tmpl := New("error")
|
568 |
|
|
_, err := tmpl.Parse("{{.EPERM true}}")
|
569 |
|
|
if err != nil {
|
570 |
|
|
t.Fatalf("parse error: %s", err)
|
571 |
|
|
}
|
572 |
|
|
err = tmpl.Execute(b, tVal)
|
573 |
|
|
if err == nil {
|
574 |
|
|
t.Errorf("expected error; got none")
|
575 |
|
|
} else if !strings.Contains(err.Error(), os.EPERM.Error()) {
|
576 |
|
|
if *debug {
|
577 |
|
|
fmt.Printf("test execute error: %s\n", err)
|
578 |
|
|
}
|
579 |
|
|
t.Errorf("expected os.EPERM; got %s", err)
|
580 |
|
|
}
|
581 |
|
|
}
|
582 |
|
|
|
583 |
|
|
func TestJSEscaping(t *testing.T) {
|
584 |
|
|
testCases := []struct {
|
585 |
|
|
in, exp string
|
586 |
|
|
}{
|
587 |
|
|
{`a`, `a`},
|
588 |
|
|
{`'foo`, `\'foo`},
|
589 |
|
|
{`Go "jump" \`, `Go \"jump\" \\`},
|
590 |
|
|
{`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`},
|
591 |
|
|
{"unprintable \uFDFF", `unprintable \uFDFF`},
|
592 |
|
|
{``, `\x3Chtml\x3E`},
|
593 |
|
|
}
|
594 |
|
|
for _, tc := range testCases {
|
595 |
|
|
s := JSEscapeString(tc.in)
|
596 |
|
|
if s != tc.exp {
|
597 |
|
|
t.Errorf("JS escaping [%s] got [%s] want [%s]", tc.in, s, tc.exp)
|
598 |
|
|
}
|
599 |
|
|
}
|
600 |
|
|
}
|
601 |
|
|
|
602 |
|
|
// A nice example: walk a binary tree.
|
603 |
|
|
|
604 |
|
|
type Tree struct {
|
605 |
|
|
Val int
|
606 |
|
|
Left, Right *Tree
|
607 |
|
|
}
|
608 |
|
|
|
609 |
|
|
// Use different delimiters to test Set.Delims.
|
610 |
|
|
const treeTemplate = `
|
611 |
|
|
(define "tree")
|
612 |
|
|
[
|
613 |
|
|
(.Val)
|
614 |
|
|
(with .Left)
|
615 |
|
|
(template "tree" .)
|
616 |
|
|
(end)
|
617 |
|
|
(with .Right)
|
618 |
|
|
(template "tree" .)
|
619 |
|
|
(end)
|
620 |
|
|
]
|
621 |
|
|
(end)
|
622 |
|
|
`
|
623 |
|
|
|
624 |
|
|
func TestTree(t *testing.T) {
|
625 |
|
|
var tree = &Tree{
|
626 |
|
|
1,
|
627 |
|
|
&Tree{
|
628 |
|
|
2, &Tree{
|
629 |
|
|
3,
|
630 |
|
|
&Tree{
|
631 |
|
|
4, nil, nil,
|
632 |
|
|
},
|
633 |
|
|
nil,
|
634 |
|
|
},
|
635 |
|
|
&Tree{
|
636 |
|
|
5,
|
637 |
|
|
&Tree{
|
638 |
|
|
6, nil, nil,
|
639 |
|
|
},
|
640 |
|
|
nil,
|
641 |
|
|
},
|
642 |
|
|
},
|
643 |
|
|
&Tree{
|
644 |
|
|
7,
|
645 |
|
|
&Tree{
|
646 |
|
|
8,
|
647 |
|
|
&Tree{
|
648 |
|
|
9, nil, nil,
|
649 |
|
|
},
|
650 |
|
|
nil,
|
651 |
|
|
},
|
652 |
|
|
&Tree{
|
653 |
|
|
10,
|
654 |
|
|
&Tree{
|
655 |
|
|
11, nil, nil,
|
656 |
|
|
},
|
657 |
|
|
nil,
|
658 |
|
|
},
|
659 |
|
|
},
|
660 |
|
|
}
|
661 |
|
|
tmpl, err := New("root").Delims("(", ")").Parse(treeTemplate)
|
662 |
|
|
if err != nil {
|
663 |
|
|
t.Fatal("parse error:", err)
|
664 |
|
|
}
|
665 |
|
|
var b bytes.Buffer
|
666 |
|
|
stripSpace := func(r rune) rune {
|
667 |
|
|
if r == '\t' || r == '\n' {
|
668 |
|
|
return -1
|
669 |
|
|
}
|
670 |
|
|
return r
|
671 |
|
|
}
|
672 |
|
|
const expect = "[1[2[3[4]][5[6]]][7[8[9]][10[11]]]]"
|
673 |
|
|
// First by looking up the template.
|
674 |
|
|
err = tmpl.Lookup("tree").Execute(&b, tree)
|
675 |
|
|
if err != nil {
|
676 |
|
|
t.Fatal("exec error:", err)
|
677 |
|
|
}
|
678 |
|
|
result := strings.Map(stripSpace, b.String())
|
679 |
|
|
if result != expect {
|
680 |
|
|
t.Errorf("expected %q got %q", expect, result)
|
681 |
|
|
}
|
682 |
|
|
// Then direct to execution.
|
683 |
|
|
b.Reset()
|
684 |
|
|
err = tmpl.ExecuteTemplate(&b, "tree", tree)
|
685 |
|
|
if err != nil {
|
686 |
|
|
t.Fatal("exec error:", err)
|
687 |
|
|
}
|
688 |
|
|
result = strings.Map(stripSpace, b.String())
|
689 |
|
|
if result != expect {
|
690 |
|
|
t.Errorf("expected %q got %q", expect, result)
|
691 |
|
|
}
|
692 |
|
|
}
|