| 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 |
|
|
}
|