mirror of
https://gitee.com/johng/gf.git
synced 2024-12-01 11:48:09 +08:00
update unit test cases
This commit is contained in:
parent
bd0baceeca
commit
fa69b581e1
@ -24,8 +24,8 @@ test = "v=1"
|
|||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
port = "3306"
|
port = "3306"
|
||||||
user = "root"
|
user = "root"
|
||||||
pass = "12345678"
|
pass = ""
|
||||||
# pass = ""
|
# pass = "12345678"
|
||||||
name = "test"
|
name = "test"
|
||||||
type = "mysql"
|
type = "mysql"
|
||||||
role = "master"
|
role = "master"
|
||||||
@ -35,8 +35,8 @@ test = "v=1"
|
|||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
port = "3306"
|
port = "3306"
|
||||||
user = "root"
|
user = "root"
|
||||||
pass = "12345678"
|
pass = ""
|
||||||
# pass = ""
|
# pass = "12345678"
|
||||||
name = "test"
|
name = "test"
|
||||||
type = "mysql"
|
type = "mysql"
|
||||||
role = "master"
|
role = "master"
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package fmtsort
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
func Compare(a, b reflect.Value) int {
|
|
||||||
return compare(a, b)
|
|
||||||
}
|
|
@ -1,212 +0,0 @@
|
|||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package fmtsort_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/gogf/gf/g/os/gview/internal/fmtsort"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var compareTests = [][]reflect.Value{
|
|
||||||
ct(reflect.TypeOf(int(0)), -1, 0, 1),
|
|
||||||
ct(reflect.TypeOf(int8(0)), -1, 0, 1),
|
|
||||||
ct(reflect.TypeOf(int16(0)), -1, 0, 1),
|
|
||||||
ct(reflect.TypeOf(int32(0)), -1, 0, 1),
|
|
||||||
ct(reflect.TypeOf(int64(0)), -1, 0, 1),
|
|
||||||
ct(reflect.TypeOf(uint(0)), 0, 1, 5),
|
|
||||||
ct(reflect.TypeOf(uint8(0)), 0, 1, 5),
|
|
||||||
ct(reflect.TypeOf(uint16(0)), 0, 1, 5),
|
|
||||||
ct(reflect.TypeOf(uint32(0)), 0, 1, 5),
|
|
||||||
ct(reflect.TypeOf(uint64(0)), 0, 1, 5),
|
|
||||||
ct(reflect.TypeOf(uintptr(0)), 0, 1, 5),
|
|
||||||
ct(reflect.TypeOf(string("")), "", "a", "ab"),
|
|
||||||
ct(reflect.TypeOf(float32(0)), math.NaN(), math.Inf(-1), -1e10, 0, 1e10, math.Inf(1)),
|
|
||||||
ct(reflect.TypeOf(float64(0)), math.NaN(), math.Inf(-1), -1e10, 0, 1e10, math.Inf(1)),
|
|
||||||
ct(reflect.TypeOf(complex64(0+1i)), -1-1i, -1+0i, -1+1i, 0-1i, 0+0i, 0+1i, 1-1i, 1+0i, 1+1i),
|
|
||||||
ct(reflect.TypeOf(complex128(0+1i)), -1-1i, -1+0i, -1+1i, 0-1i, 0+0i, 0+1i, 1-1i, 1+0i, 1+1i),
|
|
||||||
ct(reflect.TypeOf(false), false, true),
|
|
||||||
ct(reflect.TypeOf(&ints[0]), &ints[0], &ints[1], &ints[2]),
|
|
||||||
ct(reflect.TypeOf(chans[0]), chans[0], chans[1], chans[2]),
|
|
||||||
ct(reflect.TypeOf(toy{}), toy{0, 1}, toy{0, 2}, toy{1, -1}, toy{1, 1}),
|
|
||||||
ct(reflect.TypeOf([2]int{}), [2]int{1, 1}, [2]int{1, 2}, [2]int{2, 0}),
|
|
||||||
ct(reflect.TypeOf(interface{}(interface{}(0))), iFace, 1, 2, 3),
|
|
||||||
}
|
|
||||||
|
|
||||||
var iFace interface{}
|
|
||||||
|
|
||||||
func ct(typ reflect.Type, args ...interface{}) []reflect.Value {
|
|
||||||
value := make([]reflect.Value, len(args))
|
|
||||||
for i, v := range args {
|
|
||||||
x := reflect.ValueOf(v)
|
|
||||||
if !x.IsValid() { // Make it a typed nil.
|
|
||||||
x = reflect.Zero(typ)
|
|
||||||
} else {
|
|
||||||
x = x.Convert(typ)
|
|
||||||
}
|
|
||||||
value[i] = x
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompare(t *testing.T) {
|
|
||||||
for _, test := range compareTests {
|
|
||||||
for i, v0 := range test {
|
|
||||||
for j, v1 := range test {
|
|
||||||
c := fmtsort.Compare(v0, v1)
|
|
||||||
var expect int
|
|
||||||
switch {
|
|
||||||
case i == j:
|
|
||||||
expect = 0
|
|
||||||
// NaNs are tricky.
|
|
||||||
if typ := v0.Type(); (typ.Kind() == reflect.Float32 || typ.Kind() == reflect.Float64) && math.IsNaN(v0.Float()) {
|
|
||||||
expect = -1
|
|
||||||
}
|
|
||||||
case i < j:
|
|
||||||
expect = -1
|
|
||||||
case i > j:
|
|
||||||
expect = 1
|
|
||||||
}
|
|
||||||
if c != expect {
|
|
||||||
t.Errorf("%s: compare(%v,%v)=%d; expect %d", v0.Type(), v0, v1, c, expect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortTest struct {
|
|
||||||
data interface{} // Always a map.
|
|
||||||
print string // Printed result using our custom printer.
|
|
||||||
}
|
|
||||||
|
|
||||||
var sortTests = []sortTest{
|
|
||||||
{
|
|
||||||
map[int]string{7: "bar", -3: "foo"},
|
|
||||||
"-3:foo 7:bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[uint8]string{7: "bar", 3: "foo"},
|
|
||||||
"3:foo 7:bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]string{"7": "bar", "3": "foo"},
|
|
||||||
"3:foo 7:bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[float64]string{7: "bar", -3: "foo", math.NaN(): "nan", math.Inf(0): "inf"},
|
|
||||||
"NaN:nan -3:foo 7:bar +Inf:inf",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[complex128]string{7 + 2i: "bar2", 7 + 1i: "bar", -3: "foo", complex(math.NaN(), 0i): "nan", complex(math.Inf(0), 0i): "inf"},
|
|
||||||
"(NaN+0i):nan (-3+0i):foo (7+1i):bar (7+2i):bar2 (+Inf+0i):inf",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[bool]string{true: "true", false: "false"},
|
|
||||||
"false:false true:true",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chanMap(),
|
|
||||||
"CHAN0:0 CHAN1:1 CHAN2:2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pointerMap(),
|
|
||||||
"PTR0:0 PTR1:1 PTR2:2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[toy]string{toy{7, 2}: "72", toy{7, 1}: "71", toy{3, 4}: "34"},
|
|
||||||
"{3 4}:34 {7 1}:71 {7 2}:72",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[[2]int]string{{7, 2}: "72", {7, 1}: "71", {3, 4}: "34"},
|
|
||||||
"[3 4]:34 [7 1]:71 [7 2]:72",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[interface{}]string{7: "7", 4: "4", 3: "3", nil: "nil"},
|
|
||||||
"<nil>:nil 3:3 4:4 7:7",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func sprint(data interface{}) string {
|
|
||||||
om := fmtsort.Sort(reflect.ValueOf(data))
|
|
||||||
if om == nil {
|
|
||||||
return "nil"
|
|
||||||
}
|
|
||||||
b := new(strings.Builder)
|
|
||||||
for i, key := range om.Key {
|
|
||||||
if i > 0 {
|
|
||||||
b.WriteRune(' ')
|
|
||||||
}
|
|
||||||
b.WriteString(sprintKey(key))
|
|
||||||
b.WriteRune(':')
|
|
||||||
b.WriteString(fmt.Sprint(om.Value[i]))
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// sprintKey formats a reflect.Value but gives reproducible values for some
|
|
||||||
// problematic types such as pointers. Note that it only does special handling
|
|
||||||
// for the troublesome types used in the test cases; it is not a general
|
|
||||||
// printer.
|
|
||||||
func sprintKey(key reflect.Value) string {
|
|
||||||
switch str := key.Type().String(); str {
|
|
||||||
case "*int":
|
|
||||||
ptr := key.Interface().(*int)
|
|
||||||
for i := range ints {
|
|
||||||
if ptr == &ints[i] {
|
|
||||||
return fmt.Sprintf("PTR%d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "PTR???"
|
|
||||||
case "chan int":
|
|
||||||
c := key.Interface().(chan int)
|
|
||||||
for i := range chans {
|
|
||||||
if c == chans[i] {
|
|
||||||
return fmt.Sprintf("CHAN%d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "CHAN???"
|
|
||||||
default:
|
|
||||||
return fmt.Sprint(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ints [3]int
|
|
||||||
chans = [3]chan int{make(chan int), make(chan int), make(chan int)}
|
|
||||||
)
|
|
||||||
|
|
||||||
func pointerMap() map[*int]string {
|
|
||||||
m := make(map[*int]string)
|
|
||||||
for i := 2; i >= 0; i-- {
|
|
||||||
m[&ints[i]] = fmt.Sprint(i)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func chanMap() map[chan int]string {
|
|
||||||
m := make(map[chan int]string)
|
|
||||||
for i := 2; i >= 0; i-- {
|
|
||||||
m[chans[i]] = fmt.Sprint(i)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type toy struct {
|
|
||||||
A int // Exported.
|
|
||||||
b int // Unexported.
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOrder(t *testing.T) {
|
|
||||||
for _, test := range sortTests {
|
|
||||||
got := sprint(test.data)
|
|
||||||
if got != test.print {
|
|
||||||
t.Errorf("%s: got %q, want %q", reflect.TypeOf(test.data), got, test.print)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package template_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleTemplate() {
|
|
||||||
// Define a template.
|
|
||||||
const letter = `
|
|
||||||
Dear {{.Name}},
|
|
||||||
{{if .Attended}}
|
|
||||||
It was a pleasure to see you at the wedding.
|
|
||||||
{{- else}}
|
|
||||||
It is a shame you couldn't make it to the wedding.
|
|
||||||
{{- end}}
|
|
||||||
{{with .Gift -}}
|
|
||||||
Thank you for the lovely {{.}}.
|
|
||||||
{{end}}
|
|
||||||
Best wishes,
|
|
||||||
Josie
|
|
||||||
`
|
|
||||||
|
|
||||||
// Prepare some data to insert into the template.
|
|
||||||
type Recipient struct {
|
|
||||||
Name, Gift string
|
|
||||||
Attended bool
|
|
||||||
}
|
|
||||||
var recipients = []Recipient{
|
|
||||||
{"Aunt Mildred", "bone china tea set", true},
|
|
||||||
{"Uncle John", "moleskin pants", false},
|
|
||||||
{"Cousin Rodney", "", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new template and parse the letter into it.
|
|
||||||
t := template.Must(template.New("letter").Parse(letter))
|
|
||||||
|
|
||||||
// Execute the template for each recipient.
|
|
||||||
for _, r := range recipients {
|
|
||||||
err := t.Execute(os.Stdout, r)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("executing template:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// Dear Aunt Mildred,
|
|
||||||
//
|
|
||||||
// It was a pleasure to see you at the wedding.
|
|
||||||
// Thank you for the lovely bone china tea set.
|
|
||||||
//
|
|
||||||
// Best wishes,
|
|
||||||
// Josie
|
|
||||||
//
|
|
||||||
// Dear Uncle John,
|
|
||||||
//
|
|
||||||
// It is a shame you couldn't make it to the wedding.
|
|
||||||
// Thank you for the lovely moleskin pants.
|
|
||||||
//
|
|
||||||
// Best wishes,
|
|
||||||
// Josie
|
|
||||||
//
|
|
||||||
// Dear Cousin Rodney,
|
|
||||||
//
|
|
||||||
// It is a shame you couldn't make it to the wedding.
|
|
||||||
//
|
|
||||||
// Best wishes,
|
|
||||||
// Josie
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following example is duplicated in html/template; keep them in sync.
|
|
||||||
|
|
||||||
func ExampleTemplate_block() {
|
|
||||||
const (
|
|
||||||
master = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
|
|
||||||
overlay = `{{define "list"}} {{join . ", "}}{{end}} `
|
|
||||||
)
|
|
||||||
var (
|
|
||||||
funcs = template.FuncMap{"join": strings.Join}
|
|
||||||
guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"}
|
|
||||||
)
|
|
||||||
masterTmpl, err := template.New("master").Funcs(funcs).Parse(master)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// Output:
|
|
||||||
// Names:
|
|
||||||
// - Gamora
|
|
||||||
// - Groot
|
|
||||||
// - Nebula
|
|
||||||
// - Rocket
|
|
||||||
// - Star-Lord
|
|
||||||
// Names: Gamora, Groot, Nebula, Rocket, Star-Lord
|
|
||||||
}
|
|
@ -1,182 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package template_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
// templateFile defines the contents of a template to be stored in a file, for testing.
|
|
||||||
type templateFile struct {
|
|
||||||
name string
|
|
||||||
contents string
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTestDir(files []templateFile) string {
|
|
||||||
dir, err := ioutil.TempDir("", "template")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
f, err := os.Create(filepath.Join(dir, file.name))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = io.WriteString(f, file.contents)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here we demonstrate loading a set of templates from a directory.
|
|
||||||
func ExampleTemplate_glob() {
|
|
||||||
// Here we create a temporary directory and populate it with our sample
|
|
||||||
// template definition files; usually the template files would already
|
|
||||||
// exist in some location known to the program.
|
|
||||||
dir := createTestDir([]templateFile{
|
|
||||||
// T0.tmpl is a plain template file that just invokes T1.
|
|
||||||
{"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
|
|
||||||
// T1.tmpl defines a template, T1 that invokes T2.
|
|
||||||
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
|
|
||||||
// T2.tmpl defines a template T2.
|
|
||||||
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
|
|
||||||
})
|
|
||||||
// Clean up after the test; another quirk of running as an example.
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// pattern is the glob pattern used to find all the template files.
|
|
||||||
pattern := filepath.Join(dir, "*.tmpl")
|
|
||||||
|
|
||||||
// Here starts the example proper.
|
|
||||||
// T0.tmpl is the first name matched, so it becomes the starting template,
|
|
||||||
// the value returned by ParseGlob.
|
|
||||||
tmpl := template.Must(template.ParseGlob(pattern))
|
|
||||||
|
|
||||||
err := tmpl.Execute(os.Stdout, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("template execution: %s", err)
|
|
||||||
}
|
|
||||||
// Output:
|
|
||||||
// T0 invokes T1: (T1 invokes T2: (This is T2))
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example demonstrates one way to share some templates
|
|
||||||
// and use them in different contexts. In this variant we add multiple driver
|
|
||||||
// templates by hand to an existing bundle of templates.
|
|
||||||
func ExampleTemplate_helpers() {
|
|
||||||
// Here we create a temporary directory and populate it with our sample
|
|
||||||
// template definition files; usually the template files would already
|
|
||||||
// exist in some location known to the program.
|
|
||||||
dir := createTestDir([]templateFile{
|
|
||||||
// T1.tmpl defines a template, T1 that invokes T2.
|
|
||||||
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
|
|
||||||
// T2.tmpl defines a template T2.
|
|
||||||
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
|
|
||||||
})
|
|
||||||
// Clean up after the test; another quirk of running as an example.
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// pattern is the glob pattern used to find all the template files.
|
|
||||||
pattern := filepath.Join(dir, "*.tmpl")
|
|
||||||
|
|
||||||
// Here starts the example proper.
|
|
||||||
// Load the helpers.
|
|
||||||
templates := template.Must(template.ParseGlob(pattern))
|
|
||||||
// Add one driver template to the bunch; we do this with an explicit template definition.
|
|
||||||
_, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("parsing driver1: ", err)
|
|
||||||
}
|
|
||||||
// Add another driver template.
|
|
||||||
_, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("parsing driver2: ", err)
|
|
||||||
}
|
|
||||||
// We load all the templates before execution. This package does not require
|
|
||||||
// that behavior but html/template's escaping does, so it's a good habit.
|
|
||||||
err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("driver1 execution: %s", err)
|
|
||||||
}
|
|
||||||
err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("driver2 execution: %s", err)
|
|
||||||
}
|
|
||||||
// Output:
|
|
||||||
// Driver 1 calls T1: (T1 invokes T2: (This is T2))
|
|
||||||
// Driver 2 calls T2: (This is T2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example demonstrates how to use one group of driver
|
|
||||||
// templates with distinct sets of helper templates.
|
|
||||||
func ExampleTemplate_share() {
|
|
||||||
// Here we create a temporary directory and populate it with our sample
|
|
||||||
// template definition files; usually the template files would already
|
|
||||||
// exist in some location known to the program.
|
|
||||||
dir := createTestDir([]templateFile{
|
|
||||||
// T0.tmpl is a plain template file that just invokes T1.
|
|
||||||
{"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
|
|
||||||
// T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
|
|
||||||
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
|
|
||||||
})
|
|
||||||
// Clean up after the test; another quirk of running as an example.
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// pattern is the glob pattern used to find all the template files.
|
|
||||||
pattern := filepath.Join(dir, "*.tmpl")
|
|
||||||
|
|
||||||
// Here starts the example proper.
|
|
||||||
// Load the drivers.
|
|
||||||
drivers := template.Must(template.ParseGlob(pattern))
|
|
||||||
|
|
||||||
// We must define an implementation of the T2 template. First we clone
|
|
||||||
// the drivers, then add a definition of T2 to the template name space.
|
|
||||||
|
|
||||||
// 1. Clone the helper set to create a new name space from which to run them.
|
|
||||||
first, err := drivers.Clone()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("cloning helpers: ", err)
|
|
||||||
}
|
|
||||||
// 2. Define T2, version A, and parse it.
|
|
||||||
_, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("parsing T2: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now repeat the whole thing, using a different version of T2.
|
|
||||||
// 1. Clone the drivers.
|
|
||||||
second, err := drivers.Clone()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("cloning drivers: ", err)
|
|
||||||
}
|
|
||||||
// 2. Define T2, version B, and parse it.
|
|
||||||
_, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("parsing T2: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the templates in the reverse order to verify the
|
|
||||||
// first is unaffected by the second.
|
|
||||||
err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("second execution: %s", err)
|
|
||||||
}
|
|
||||||
err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("first: execution: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
|
|
||||||
// T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package template_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example demonstrates a custom function to process template text.
|
|
||||||
// It installs the strings.Title function and uses it to
|
|
||||||
// Make Title Text Look Good In Our Template's Output.
|
|
||||||
func ExampleTemplate_func() {
|
|
||||||
// First we create a FuncMap with which to register the function.
|
|
||||||
funcMap := template.FuncMap{
|
|
||||||
// The name "title" is what the function will be called in the template text.
|
|
||||||
"title": strings.Title,
|
|
||||||
}
|
|
||||||
|
|
||||||
// A simple template definition to test our function.
|
|
||||||
// We print the input text several ways:
|
|
||||||
// - the original
|
|
||||||
// - title-cased
|
|
||||||
// - title-cased and then printed with %q
|
|
||||||
// - printed with %q and then title-cased.
|
|
||||||
const templateText = `
|
|
||||||
Input: {{printf "%q" .}}
|
|
||||||
Output 0: {{title .}}
|
|
||||||
Output 1: {{title . | printf "%q"}}
|
|
||||||
Output 2: {{printf "%q" . | title}}
|
|
||||||
`
|
|
||||||
|
|
||||||
// Create a template, add the function map, and parse the text.
|
|
||||||
tmpl, err := template.New("titleTest").Funcs(funcMap).Parse(templateText)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("parsing: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the template to verify the output.
|
|
||||||
err = tmpl.Execute(os.Stdout, "the go programming language")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("execution: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// Input: "the go programming language"
|
|
||||||
// Output 0: The Go Programming Language
|
|
||||||
// Output 1: "The Go Programming Language"
|
|
||||||
// Output 2: "The Go Programming Language"
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,423 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package template
|
|
||||||
|
|
||||||
// Tests for multiple-template parsing and execution.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"text/template/parse"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
noError = true
|
|
||||||
hasError = false
|
|
||||||
)
|
|
||||||
|
|
||||||
type multiParseTest struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
ok bool
|
|
||||||
names []string
|
|
||||||
results []string
|
|
||||||
}
|
|
||||||
|
|
||||||
var multiParseTests = []multiParseTest{
|
|
||||||
{"empty", "", noError,
|
|
||||||
nil,
|
|
||||||
nil},
|
|
||||||
{"one", `{{define "foo"}} FOO {{end}}`, noError,
|
|
||||||
[]string{"foo"},
|
|
||||||
[]string{" FOO "}},
|
|
||||||
{"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError,
|
|
||||||
[]string{"foo", "bar"},
|
|
||||||
[]string{" FOO ", " BAR "}},
|
|
||||||
// errors
|
|
||||||
{"missing end", `{{define "foo"}} FOO `, hasError,
|
|
||||||
nil,
|
|
||||||
nil},
|
|
||||||
{"malformed name", `{{define "foo}} FOO `, hasError,
|
|
||||||
nil,
|
|
||||||
nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultiParse(t *testing.T) {
|
|
||||||
for _, test := range multiParseTests {
|
|
||||||
template, err := New("root").Parse(test.input)
|
|
||||||
switch {
|
|
||||||
case err == nil && !test.ok:
|
|
||||||
t.Errorf("%q: expected error; got none", test.name)
|
|
||||||
continue
|
|
||||||
case err != nil && test.ok:
|
|
||||||
t.Errorf("%q: unexpected error: %v", test.name, err)
|
|
||||||
continue
|
|
||||||
case err != nil && !test.ok:
|
|
||||||
// expected error, got one
|
|
||||||
if *debug {
|
|
||||||
fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if template == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(template.tmpl) != len(test.names)+1 { // +1 for root
|
|
||||||
t.Errorf("%s: wrong number of templates; wanted %d got %d", test.name, len(test.names), len(template.tmpl))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for i, name := range test.names {
|
|
||||||
tmpl, ok := template.tmpl[name]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%s: can't find template %q", test.name, name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result := tmpl.Root.String()
|
|
||||||
if result != test.results[i] {
|
|
||||||
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var multiExecTests = []execTest{
|
|
||||||
{"empty", "", "", nil, true},
|
|
||||||
{"text", "some text", "some text", nil, true},
|
|
||||||
{"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true},
|
|
||||||
{"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true},
|
|
||||||
{"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true},
|
|
||||||
{"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true},
|
|
||||||
{"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true},
|
|
||||||
{"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true},
|
|
||||||
{"variable declared by template", `{{template "nested" $x:=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true},
|
|
||||||
|
|
||||||
// User-defined function: test argument evaluator.
|
|
||||||
{"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true},
|
|
||||||
{"testFunc .", `{{oneArg .}}`, "oneArg=joe", "joe", true},
|
|
||||||
}
|
|
||||||
|
|
||||||
// These strings are also in testdata/*.
|
|
||||||
const multiText1 = `
|
|
||||||
{{define "x"}}TEXT{{end}}
|
|
||||||
{{define "dotV"}}{{.V}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
const multiText2 = `
|
|
||||||
{{define "dot"}}{{.}}{{end}}
|
|
||||||
{{define "nested"}}{{template "dot" .}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestMultiExecute(t *testing.T) {
|
|
||||||
// Declare a couple of templates first.
|
|
||||||
template, err := New("root").Parse(multiText1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("parse error for 1: %s", err)
|
|
||||||
}
|
|
||||||
_, err = template.Parse(multiText2)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("parse error for 2: %s", err)
|
|
||||||
}
|
|
||||||
testExecute(multiExecTests, template, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseFiles(t *testing.T) {
|
|
||||||
_, err := ParseFiles("DOES NOT EXIST")
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error for non-existent file; got none")
|
|
||||||
}
|
|
||||||
template := New("root")
|
|
||||||
_, err = template.ParseFiles("testdata/file1.tmpl", "testdata/file2.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error parsing files: %v", err)
|
|
||||||
}
|
|
||||||
testExecute(multiExecTests, template, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseGlob(t *testing.T) {
|
|
||||||
_, err := ParseGlob("DOES NOT EXIST")
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error for non-existent file; got none")
|
|
||||||
}
|
|
||||||
_, err = New("error").ParseGlob("[x")
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error for bad pattern; got none")
|
|
||||||
}
|
|
||||||
template := New("root")
|
|
||||||
_, err = template.ParseGlob("testdata/file*.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error parsing files: %v", err)
|
|
||||||
}
|
|
||||||
testExecute(multiExecTests, template, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// In these tests, actual content (not just template definitions) comes from the parsed files.
|
|
||||||
|
|
||||||
var templateFileExecTests = []execTest{
|
|
||||||
{"test", `{{template "tmpl1.tmpl"}}{{template "tmpl2.tmpl"}}`, "template1\n\ny\ntemplate2\n\nx\n", 0, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseFilesWithData(t *testing.T) {
|
|
||||||
template, err := New("root").ParseFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error parsing files: %v", err)
|
|
||||||
}
|
|
||||||
testExecute(templateFileExecTests, template, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseGlobWithData(t *testing.T) {
|
|
||||||
template, err := New("root").ParseGlob("testdata/tmpl*.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error parsing files: %v", err)
|
|
||||||
}
|
|
||||||
testExecute(templateFileExecTests, template, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
cloneText1 = `{{define "a"}}{{template "b"}}{{template "c"}}{{end}}`
|
|
||||||
cloneText2 = `{{define "b"}}b{{end}}`
|
|
||||||
cloneText3 = `{{define "c"}}root{{end}}`
|
|
||||||
cloneText4 = `{{define "c"}}clone{{end}}`
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClone(t *testing.T) {
|
|
||||||
// Create some templates and clone the root.
|
|
||||||
root, err := New("root").Parse(cloneText1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = root.Parse(cloneText2)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
clone := Must(root.Clone())
|
|
||||||
// Add variants to both.
|
|
||||||
_, err = root.Parse(cloneText3)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = clone.Parse(cloneText4)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Verify that the clone is self-consistent.
|
|
||||||
for k, v := range clone.tmpl {
|
|
||||||
if k == clone.name && v.tmpl[k] != clone {
|
|
||||||
t.Error("clone does not contain root")
|
|
||||||
}
|
|
||||||
if v != v.tmpl[v.name] {
|
|
||||||
t.Errorf("clone does not contain self for %q", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Execute root.
|
|
||||||
var b bytes.Buffer
|
|
||||||
err = root.ExecuteTemplate(&b, "a", 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if b.String() != "broot" {
|
|
||||||
t.Errorf("expected %q got %q", "broot", b.String())
|
|
||||||
}
|
|
||||||
// Execute copy.
|
|
||||||
b.Reset()
|
|
||||||
err = clone.ExecuteTemplate(&b, "a", 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if b.String() != "bclone" {
|
|
||||||
t.Errorf("expected %q got %q", "bclone", b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddParseTree(t *testing.T) {
|
|
||||||
// Create some templates.
|
|
||||||
root, err := New("root").Parse(cloneText1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = root.Parse(cloneText2)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Add a new parse tree.
|
|
||||||
tree, err := parse.Parse("cloneText3", cloneText3, "", "", nil, builtins)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
added, err := root.AddParseTree("c", tree["c"])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Execute.
|
|
||||||
var b bytes.Buffer
|
|
||||||
err = added.ExecuteTemplate(&b, "a", 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if b.String() != "broot" {
|
|
||||||
t.Errorf("expected %q got %q", "broot", b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue 7032
|
|
||||||
func TestAddParseTreeToUnparsedTemplate(t *testing.T) {
|
|
||||||
master := "{{define \"master\"}}{{end}}"
|
|
||||||
tmpl := New("master")
|
|
||||||
tree, err := parse.Parse("master", master, "", "", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected parse err: %v", err)
|
|
||||||
}
|
|
||||||
masterTree := tree["master"]
|
|
||||||
tmpl.AddParseTree("master", masterTree) // used to panic
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRedefinition(t *testing.T) {
|
|
||||||
var tmpl *Template
|
|
||||||
var err error
|
|
||||||
if tmpl, err = New("tmpl1").Parse(`{{define "test"}}foo{{end}}`); err != nil {
|
|
||||||
t.Fatalf("parse 1: %v", err)
|
|
||||||
}
|
|
||||||
if _, err = tmpl.Parse(`{{define "test"}}bar{{end}}`); err != nil {
|
|
||||||
t.Fatalf("got error %v, expected nil", err)
|
|
||||||
}
|
|
||||||
if _, err = tmpl.New("tmpl2").Parse(`{{define "test"}}bar{{end}}`); err != nil {
|
|
||||||
t.Fatalf("got error %v, expected nil", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue 10879
|
|
||||||
func TestEmptyTemplateCloneCrash(t *testing.T) {
|
|
||||||
t1 := New("base")
|
|
||||||
t1.Clone() // used to panic
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue 10910, 10926
|
|
||||||
func TestTemplateLookUp(t *testing.T) {
|
|
||||||
t1 := New("foo")
|
|
||||||
if t1.Lookup("foo") != nil {
|
|
||||||
t.Error("Lookup returned non-nil value for undefined template foo")
|
|
||||||
}
|
|
||||||
t1.New("bar")
|
|
||||||
if t1.Lookup("bar") != nil {
|
|
||||||
t.Error("Lookup returned non-nil value for undefined template bar")
|
|
||||||
}
|
|
||||||
t1.Parse(`{{define "foo"}}test{{end}}`)
|
|
||||||
if t1.Lookup("foo") == nil {
|
|
||||||
t.Error("Lookup returned nil value for defined template")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
|
||||||
// template with same name already exists
|
|
||||||
t1, _ := New("test").Parse(`{{define "test"}}foo{{end}}`)
|
|
||||||
t2 := t1.New("test")
|
|
||||||
|
|
||||||
if t1.common != t2.common {
|
|
||||||
t.Errorf("t1 & t2 didn't share common struct; got %v != %v", t1.common, t2.common)
|
|
||||||
}
|
|
||||||
if t1.Tree == nil {
|
|
||||||
t.Error("defined template got nil Tree")
|
|
||||||
}
|
|
||||||
if t2.Tree != nil {
|
|
||||||
t.Error("undefined template got non-nil Tree")
|
|
||||||
}
|
|
||||||
|
|
||||||
containsT1 := false
|
|
||||||
for _, tmpl := range t1.Templates() {
|
|
||||||
if tmpl == t2 {
|
|
||||||
t.Error("Templates included undefined template")
|
|
||||||
}
|
|
||||||
if tmpl == t1 {
|
|
||||||
containsT1 = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !containsT1 {
|
|
||||||
t.Error("Templates didn't include defined template")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
|
||||||
// In multiple calls to Parse with the same receiver template, only one call
|
|
||||||
// can contain text other than space, comments, and template definitions
|
|
||||||
t1 := New("test")
|
|
||||||
if _, err := t1.Parse(`{{define "test"}}{{end}}`); err != nil {
|
|
||||||
t.Fatalf("parsing test: %s", err)
|
|
||||||
}
|
|
||||||
if _, err := t1.Parse(`{{define "test"}}{{/* this is a comment */}}{{end}}`); err != nil {
|
|
||||||
t.Fatalf("parsing test: %s", err)
|
|
||||||
}
|
|
||||||
if _, err := t1.Parse(`{{define "test"}}foo{{end}}`); err != nil {
|
|
||||||
t.Fatalf("parsing test: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyTemplate(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
defn []string
|
|
||||||
in string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{[]string{""}, "once", ""},
|
|
||||||
{[]string{"", ""}, "twice", ""},
|
|
||||||
{[]string{"{{.}}", "{{.}}"}, "twice", "twice"},
|
|
||||||
{[]string{"{{/* a comment */}}", "{{/* a comment */}}"}, "comment", ""},
|
|
||||||
{[]string{"{{.}}", ""}, "twice", ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, c := range cases {
|
|
||||||
root := New("root")
|
|
||||||
|
|
||||||
var (
|
|
||||||
m *Template
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for _, d := range c.defn {
|
|
||||||
m, err = root.New(c.in).Parse(d)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
if err := m.Execute(buf, c.in); err != nil {
|
|
||||||
t.Error(i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if buf.String() != c.want {
|
|
||||||
t.Errorf("expected string %q: got %q", c.want, buf.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue 19249 was a regression in 1.8 caused by the handling of empty
|
|
||||||
// templates added in that release, which got different answers depending
|
|
||||||
// on the order templates appeared in the internal map.
|
|
||||||
func TestIssue19294(t *testing.T) {
|
|
||||||
// The empty block in "xhtml" should be replaced during execution
|
|
||||||
// by the contents of "stylesheet", but if the internal map associating
|
|
||||||
// names with templates is built in the wrong order, the empty block
|
|
||||||
// looks non-empty and this doesn't happen.
|
|
||||||
var inlined = map[string]string{
|
|
||||||
"stylesheet": `{{define "stylesheet"}}stylesheet{{end}}`,
|
|
||||||
"xhtml": `{{block "stylesheet" .}}{{end}}`,
|
|
||||||
}
|
|
||||||
all := []string{"stylesheet", "xhtml"}
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
res, err := New("title.xhtml").Parse(`{{template "xhtml" .}}`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, name := range all {
|
|
||||||
_, err := res.New(name).Parse(inlined[name])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
res.Execute(&buf, 0)
|
|
||||||
if buf.String() != "stylesheet" {
|
|
||||||
t.Fatalf("iteration %d: got %q; expected %q", i, buf.String(), "stylesheet")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,544 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Make the types prettyprint.
|
|
||||||
var itemName = map[itemType]string{
|
|
||||||
itemError: "error",
|
|
||||||
itemBool: "bool",
|
|
||||||
itemChar: "char",
|
|
||||||
itemCharConstant: "charconst",
|
|
||||||
itemComplex: "complex",
|
|
||||||
itemDeclare: ":=",
|
|
||||||
itemEOF: "EOF",
|
|
||||||
itemField: "field",
|
|
||||||
itemIdentifier: "identifier",
|
|
||||||
itemLeftDelim: "left delim",
|
|
||||||
itemLeftParen: "(",
|
|
||||||
itemNumber: "number",
|
|
||||||
itemPipe: "pipe",
|
|
||||||
itemRawString: "raw string",
|
|
||||||
itemRightDelim: "right delim",
|
|
||||||
itemRightParen: ")",
|
|
||||||
itemSpace: "space",
|
|
||||||
itemString: "string",
|
|
||||||
itemVariable: "variable",
|
|
||||||
|
|
||||||
// keywords
|
|
||||||
itemDot: ".",
|
|
||||||
itemBlock: "block",
|
|
||||||
itemDefine: "define",
|
|
||||||
itemElse: "else",
|
|
||||||
itemIf: "if",
|
|
||||||
itemEnd: "end",
|
|
||||||
itemNil: "nil",
|
|
||||||
itemRange: "range",
|
|
||||||
itemTemplate: "template",
|
|
||||||
itemWith: "with",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i itemType) String() string {
|
|
||||||
s := itemName[i]
|
|
||||||
if s == "" {
|
|
||||||
return fmt.Sprintf("item%d", int(i))
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
type lexTest struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
items []item
|
|
||||||
}
|
|
||||||
|
|
||||||
func mkItem(typ itemType, text string) item {
|
|
||||||
return item{
|
|
||||||
typ: typ,
|
|
||||||
val: text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
tDot = mkItem(itemDot, ".")
|
|
||||||
tBlock = mkItem(itemBlock, "block")
|
|
||||||
tEOF = mkItem(itemEOF, "")
|
|
||||||
tFor = mkItem(itemIdentifier, "for")
|
|
||||||
tLeft = mkItem(itemLeftDelim, "{{")
|
|
||||||
tLpar = mkItem(itemLeftParen, "(")
|
|
||||||
tPipe = mkItem(itemPipe, "|")
|
|
||||||
tQuote = mkItem(itemString, `"abc \n\t\" "`)
|
|
||||||
tRange = mkItem(itemRange, "range")
|
|
||||||
tRight = mkItem(itemRightDelim, "}}")
|
|
||||||
tRpar = mkItem(itemRightParen, ")")
|
|
||||||
tSpace = mkItem(itemSpace, " ")
|
|
||||||
raw = "`" + `abc\n\t\" ` + "`"
|
|
||||||
rawNL = "`now is{{\n}}the time`" // Contains newline inside raw quote.
|
|
||||||
tRawQuote = mkItem(itemRawString, raw)
|
|
||||||
tRawQuoteNL = mkItem(itemRawString, rawNL)
|
|
||||||
)
|
|
||||||
|
|
||||||
var lexTests = []lexTest{
|
|
||||||
{"empty", "", []item{tEOF}},
|
|
||||||
{"spaces", " \t\n", []item{mkItem(itemText, " \t\n"), tEOF}},
|
|
||||||
{"text", `now is the time`, []item{mkItem(itemText, "now is the time"), tEOF}},
|
|
||||||
{"text with comment", "hello-{{/* this is a comment */}}-world", []item{
|
|
||||||
mkItem(itemText, "hello-"),
|
|
||||||
mkItem(itemText, "-world"),
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"punctuation", "{{,@% }}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemChar, ","),
|
|
||||||
mkItem(itemChar, "@"),
|
|
||||||
mkItem(itemChar, "%"),
|
|
||||||
tSpace,
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"parens", "{{((3))}}", []item{
|
|
||||||
tLeft,
|
|
||||||
tLpar,
|
|
||||||
tLpar,
|
|
||||||
mkItem(itemNumber, "3"),
|
|
||||||
tRpar,
|
|
||||||
tRpar,
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"empty action", `{{}}`, []item{tLeft, tRight, tEOF}},
|
|
||||||
{"for", `{{for}}`, []item{tLeft, tFor, tRight, tEOF}},
|
|
||||||
{"block", `{{block "foo" .}}`, []item{
|
|
||||||
tLeft, tBlock, tSpace, mkItem(itemString, `"foo"`), tSpace, tDot, tRight, tEOF,
|
|
||||||
}},
|
|
||||||
{"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}},
|
|
||||||
{"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}},
|
|
||||||
{"raw quote with newline", "{{" + rawNL + "}}", []item{tLeft, tRawQuoteNL, tRight, tEOF}},
|
|
||||||
{"numbers", "{{1 02 0x14 -7.2i 1e3 +1.2e-4 4.2i 1+2i}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemNumber, "1"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "02"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "0x14"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "-7.2i"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "1e3"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "+1.2e-4"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "4.2i"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemComplex, "1+2i"),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemCharConstant, `'a'`),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemCharConstant, `'\n'`),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemCharConstant, `'\''`),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemCharConstant, `'\\'`),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemCharConstant, `'\u00FF'`),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemCharConstant, `'\xFF'`),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemCharConstant, `'本'`),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"bools", "{{true false}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemBool, "true"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemBool, "false"),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"dot", "{{.}}", []item{
|
|
||||||
tLeft,
|
|
||||||
tDot,
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"nil", "{{nil}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemNil, "nil"),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"dots", "{{.x . .2 .x.y.z}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemField, ".x"),
|
|
||||||
tSpace,
|
|
||||||
tDot,
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, ".2"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemField, ".x"),
|
|
||||||
mkItem(itemField, ".y"),
|
|
||||||
mkItem(itemField, ".z"),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"keywords", "{{range if else end with}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemRange, "range"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemIf, "if"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemElse, "else"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemEnd, "end"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemWith, "with"),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemVariable, "$c"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemDeclare, ":="),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemIdentifier, "printf"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemVariable, "$"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemVariable, "$hello"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemVariable, "$23"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemVariable, "$"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemVariable, "$var"),
|
|
||||||
mkItem(itemField, ".Field"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemField, ".Method"),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"variable invocation", "{{$x 23}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemVariable, "$x"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "23"),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{
|
|
||||||
mkItem(itemText, "intro "),
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemIdentifier, "echo"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemIdentifier, "hi"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "1.2"),
|
|
||||||
tSpace,
|
|
||||||
tPipe,
|
|
||||||
mkItem(itemIdentifier, "noargs"),
|
|
||||||
tPipe,
|
|
||||||
mkItem(itemIdentifier, "args"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "1"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemString, `"hi"`),
|
|
||||||
tRight,
|
|
||||||
mkItem(itemText, " outro"),
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"declaration", "{{$v := 3}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemVariable, "$v"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemDeclare, ":="),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "3"),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"2 declarations", "{{$v , $w := 3}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemVariable, "$v"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemChar, ","),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemVariable, "$w"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemDeclare, ":="),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemNumber, "3"),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"field of parenthesized expression", "{{(.X).Y}}", []item{
|
|
||||||
tLeft,
|
|
||||||
tLpar,
|
|
||||||
mkItem(itemField, ".X"),
|
|
||||||
tRpar,
|
|
||||||
mkItem(itemField, ".Y"),
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"trimming spaces before and after", "hello- {{- 3 -}} -world", []item{
|
|
||||||
mkItem(itemText, "hello-"),
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemNumber, "3"),
|
|
||||||
tRight,
|
|
||||||
mkItem(itemText, "-world"),
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"trimming spaces before and after comment", "hello- {{- /* hello */ -}} -world", []item{
|
|
||||||
mkItem(itemText, "hello-"),
|
|
||||||
mkItem(itemText, "-world"),
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
// errors
|
|
||||||
{"badchar", "#{{\x01}}", []item{
|
|
||||||
mkItem(itemText, "#"),
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemError, "unrecognized character in action: U+0001"),
|
|
||||||
}},
|
|
||||||
{"unclosed action", "{{\n}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemError, "unclosed action"),
|
|
||||||
}},
|
|
||||||
{"EOF in action", "{{range", []item{
|
|
||||||
tLeft,
|
|
||||||
tRange,
|
|
||||||
mkItem(itemError, "unclosed action"),
|
|
||||||
}},
|
|
||||||
{"unclosed quote", "{{\"\n\"}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemError, "unterminated quoted string"),
|
|
||||||
}},
|
|
||||||
{"unclosed raw quote", "{{`xx}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemError, "unterminated raw quoted string"),
|
|
||||||
}},
|
|
||||||
{"unclosed char constant", "{{'\n}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemError, "unterminated character constant"),
|
|
||||||
}},
|
|
||||||
{"bad number", "{{3k}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemError, `bad number syntax: "3k"`),
|
|
||||||
}},
|
|
||||||
{"unclosed paren", "{{(3}}", []item{
|
|
||||||
tLeft,
|
|
||||||
tLpar,
|
|
||||||
mkItem(itemNumber, "3"),
|
|
||||||
mkItem(itemError, `unclosed left paren`),
|
|
||||||
}},
|
|
||||||
{"extra right paren", "{{3)}}", []item{
|
|
||||||
tLeft,
|
|
||||||
mkItem(itemNumber, "3"),
|
|
||||||
tRpar,
|
|
||||||
mkItem(itemError, `unexpected right paren U+0029 ')'`),
|
|
||||||
}},
|
|
||||||
|
|
||||||
// Fixed bugs
|
|
||||||
// Many elements in an action blew the lookahead until
|
|
||||||
// we made lexInsideAction not loop.
|
|
||||||
{"long pipeline deadlock", "{{|||||}}", []item{
|
|
||||||
tLeft,
|
|
||||||
tPipe,
|
|
||||||
tPipe,
|
|
||||||
tPipe,
|
|
||||||
tPipe,
|
|
||||||
tPipe,
|
|
||||||
tRight,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"text with bad comment", "hello-{{/*/}}-world", []item{
|
|
||||||
mkItem(itemText, "hello-"),
|
|
||||||
mkItem(itemError, `unclosed comment`),
|
|
||||||
}},
|
|
||||||
{"text with comment close separated from delim", "hello-{{/* */ }}-world", []item{
|
|
||||||
mkItem(itemText, "hello-"),
|
|
||||||
mkItem(itemError, `comment ends before closing delimiter`),
|
|
||||||
}},
|
|
||||||
// This one is an error that we can't catch because it breaks templates with
|
|
||||||
// minimized JavaScript. Should have fixed it before Go 1.1.
|
|
||||||
{"unmatched right delimiter", "hello-{.}}-world", []item{
|
|
||||||
mkItem(itemText, "hello-{.}}-world"),
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect gathers the emitted items into a slice.
|
|
||||||
func collect(t *lexTest, left, right string) (items []item) {
|
|
||||||
l := lex(t.name, t.input, left, right)
|
|
||||||
for {
|
|
||||||
item := l.nextItem()
|
|
||||||
items = append(items, item)
|
|
||||||
if item.typ == itemEOF || item.typ == itemError {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func equal(i1, i2 []item, checkPos bool) bool {
|
|
||||||
if len(i1) != len(i2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for k := range i1 {
|
|
||||||
if i1[k].typ != i2[k].typ {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if i1[k].val != i2[k].val {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if checkPos && i1[k].pos != i2[k].pos {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if checkPos && i1[k].line != i2[k].line {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLex(t *testing.T) {
|
|
||||||
for _, test := range lexTests {
|
|
||||||
items := collect(&test, "", "")
|
|
||||||
if !equal(items, test.items, false) {
|
|
||||||
t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some easy cases from above, but with delimiters $$ and @@
|
|
||||||
var lexDelimTests = []lexTest{
|
|
||||||
{"punctuation", "$$,@%{{}}@@", []item{
|
|
||||||
tLeftDelim,
|
|
||||||
mkItem(itemChar, ","),
|
|
||||||
mkItem(itemChar, "@"),
|
|
||||||
mkItem(itemChar, "%"),
|
|
||||||
mkItem(itemChar, "{"),
|
|
||||||
mkItem(itemChar, "{"),
|
|
||||||
mkItem(itemChar, "}"),
|
|
||||||
mkItem(itemChar, "}"),
|
|
||||||
tRightDelim,
|
|
||||||
tEOF,
|
|
||||||
}},
|
|
||||||
{"empty action", `$$@@`, []item{tLeftDelim, tRightDelim, tEOF}},
|
|
||||||
{"for", `$$for@@`, []item{tLeftDelim, tFor, tRightDelim, tEOF}},
|
|
||||||
{"quote", `$$"abc \n\t\" "@@`, []item{tLeftDelim, tQuote, tRightDelim, tEOF}},
|
|
||||||
{"raw quote", "$$" + raw + "@@", []item{tLeftDelim, tRawQuote, tRightDelim, tEOF}},
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
tLeftDelim = mkItem(itemLeftDelim, "$$")
|
|
||||||
tRightDelim = mkItem(itemRightDelim, "@@")
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDelims(t *testing.T) {
|
|
||||||
for _, test := range lexDelimTests {
|
|
||||||
items := collect(&test, "$$", "@@")
|
|
||||||
if !equal(items, test.items, false) {
|
|
||||||
t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var lexPosTests = []lexTest{
|
|
||||||
{"empty", "", []item{{itemEOF, 0, "", 1}}},
|
|
||||||
{"punctuation", "{{,@%#}}", []item{
|
|
||||||
{itemLeftDelim, 0, "{{", 1},
|
|
||||||
{itemChar, 2, ",", 1},
|
|
||||||
{itemChar, 3, "@", 1},
|
|
||||||
{itemChar, 4, "%", 1},
|
|
||||||
{itemChar, 5, "#", 1},
|
|
||||||
{itemRightDelim, 6, "}}", 1},
|
|
||||||
{itemEOF, 8, "", 1},
|
|
||||||
}},
|
|
||||||
{"sample", "0123{{hello}}xyz", []item{
|
|
||||||
{itemText, 0, "0123", 1},
|
|
||||||
{itemLeftDelim, 4, "{{", 1},
|
|
||||||
{itemIdentifier, 6, "hello", 1},
|
|
||||||
{itemRightDelim, 11, "}}", 1},
|
|
||||||
{itemText, 13, "xyz", 1},
|
|
||||||
{itemEOF, 16, "", 1},
|
|
||||||
}},
|
|
||||||
{"trimafter", "{{x -}}\n{{y}}", []item{
|
|
||||||
{itemLeftDelim, 0, "{{", 1},
|
|
||||||
{itemIdentifier, 2, "x", 1},
|
|
||||||
{itemRightDelim, 5, "}}", 1},
|
|
||||||
{itemLeftDelim, 8, "{{", 2},
|
|
||||||
{itemIdentifier, 10, "y", 2},
|
|
||||||
{itemRightDelim, 11, "}}", 2},
|
|
||||||
{itemEOF, 13, "", 2},
|
|
||||||
}},
|
|
||||||
{"trimbefore", "{{x}}\n{{- y}}", []item{
|
|
||||||
{itemLeftDelim, 0, "{{", 1},
|
|
||||||
{itemIdentifier, 2, "x", 1},
|
|
||||||
{itemRightDelim, 3, "}}", 1},
|
|
||||||
{itemLeftDelim, 6, "{{", 2},
|
|
||||||
{itemIdentifier, 10, "y", 2},
|
|
||||||
{itemRightDelim, 11, "}}", 2},
|
|
||||||
{itemEOF, 13, "", 2},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
// The other tests don't check position, to make the test cases easier to construct.
|
|
||||||
// This one does.
|
|
||||||
func TestPos(t *testing.T) {
|
|
||||||
for _, test := range lexPosTests {
|
|
||||||
items := collect(&test, "", "")
|
|
||||||
if !equal(items, test.items, true) {
|
|
||||||
t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
|
|
||||||
if len(items) == len(test.items) {
|
|
||||||
// Detailed print; avoid item.String() to expose the position value.
|
|
||||||
for i := range items {
|
|
||||||
if !equal(items[i:i+1], test.items[i:i+1], true) {
|
|
||||||
i1 := items[i]
|
|
||||||
i2 := test.items[i]
|
|
||||||
t.Errorf("\t#%d: got {%v %d %q %d} expected {%v %d %q %d}",
|
|
||||||
i, i1.typ, i1.pos, i1.val, i1.line, i2.typ, i2.pos, i2.val, i2.line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that an error shuts down the lexing goroutine.
|
|
||||||
func TestShutdown(t *testing.T) {
|
|
||||||
// We need to duplicate template.Parse here to hold on to the lexer.
|
|
||||||
const text = "erroneous{{define}}{{else}}1234"
|
|
||||||
lexer := lex("foo", text, "{{", "}}")
|
|
||||||
_, err := New("root").parseLexer(lexer)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error")
|
|
||||||
}
|
|
||||||
// The error should have drained the input. Therefore, the lexer should be shut down.
|
|
||||||
token, ok := <-lexer.items
|
|
||||||
if ok {
|
|
||||||
t.Fatalf("input was not drained; got %v", token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseLexer is a local version of parse that lets us pass in the lexer instead of building it.
|
|
||||||
// We expect an error, so the tree set and funcs list are explicitly nil.
|
|
||||||
func (t *Tree) parseLexer(lex *lexer) (tree *Tree, err error) {
|
|
||||||
defer t.recover(&err)
|
|
||||||
t.ParseName = t.Name
|
|
||||||
t.startParse(nil, lex, map[string]*Tree{})
|
|
||||||
t.parse()
|
|
||||||
t.add()
|
|
||||||
t.stopParse()
|
|
||||||
return t, nil
|
|
||||||
}
|
|
@ -1,542 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var debug = flag.Bool("debug", false, "show the errors produced by the main tests")
|
|
||||||
|
|
||||||
type numberTest struct {
|
|
||||||
text string
|
|
||||||
isInt bool
|
|
||||||
isUint bool
|
|
||||||
isFloat bool
|
|
||||||
isComplex bool
|
|
||||||
int64
|
|
||||||
uint64
|
|
||||||
float64
|
|
||||||
complex128
|
|
||||||
}
|
|
||||||
|
|
||||||
var numberTests = []numberTest{
|
|
||||||
// basics
|
|
||||||
{"0", true, true, true, false, 0, 0, 0, 0},
|
|
||||||
{"-0", true, true, true, false, 0, 0, 0, 0}, // check that -0 is a uint.
|
|
||||||
{"73", true, true, true, false, 73, 73, 73, 0},
|
|
||||||
{"073", true, true, true, false, 073, 073, 073, 0},
|
|
||||||
{"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0},
|
|
||||||
{"-73", true, false, true, false, -73, 0, -73, 0},
|
|
||||||
{"+73", true, false, true, false, 73, 0, 73, 0},
|
|
||||||
{"100", true, true, true, false, 100, 100, 100, 0},
|
|
||||||
{"1e9", true, true, true, false, 1e9, 1e9, 1e9, 0},
|
|
||||||
{"-1e9", true, false, true, false, -1e9, 0, -1e9, 0},
|
|
||||||
{"-1.2", false, false, true, false, 0, 0, -1.2, 0},
|
|
||||||
{"1e19", false, true, true, false, 0, 1e19, 1e19, 0},
|
|
||||||
{"-1e19", false, false, true, false, 0, 0, -1e19, 0},
|
|
||||||
{"4i", false, false, false, true, 0, 0, 0, 4i},
|
|
||||||
{"-1.2+4.2i", false, false, false, true, 0, 0, 0, -1.2 + 4.2i},
|
|
||||||
{"073i", false, false, false, true, 0, 0, 0, 73i}, // not octal!
|
|
||||||
// complex with 0 imaginary are float (and maybe integer)
|
|
||||||
{"0i", true, true, true, true, 0, 0, 0, 0},
|
|
||||||
{"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2},
|
|
||||||
{"-12+0i", true, false, true, true, -12, 0, -12, -12},
|
|
||||||
{"13+0i", true, true, true, true, 13, 13, 13, 13},
|
|
||||||
// funny bases
|
|
||||||
{"0123", true, true, true, false, 0123, 0123, 0123, 0},
|
|
||||||
{"-0x0", true, true, true, false, 0, 0, 0, 0},
|
|
||||||
{"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0},
|
|
||||||
// character constants
|
|
||||||
{`'a'`, true, true, true, false, 'a', 'a', 'a', 0},
|
|
||||||
{`'\n'`, true, true, true, false, '\n', '\n', '\n', 0},
|
|
||||||
{`'\\'`, true, true, true, false, '\\', '\\', '\\', 0},
|
|
||||||
{`'\''`, true, true, true, false, '\'', '\'', '\'', 0},
|
|
||||||
{`'\xFF'`, true, true, true, false, 0xFF, 0xFF, 0xFF, 0},
|
|
||||||
{`'パ'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
|
|
||||||
{`'\u30d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
|
|
||||||
{`'\U000030d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
|
|
||||||
// some broken syntax
|
|
||||||
{text: "+-2"},
|
|
||||||
{text: "0x123."},
|
|
||||||
{text: "1e."},
|
|
||||||
{text: "0xi."},
|
|
||||||
{text: "1+2."},
|
|
||||||
{text: "'x"},
|
|
||||||
{text: "'xx'"},
|
|
||||||
{text: "'433937734937734969526500969526500'"}, // Integer too large - issue 10634.
|
|
||||||
// Issue 8622 - 0xe parsed as floating point. Very embarrassing.
|
|
||||||
{"0xef", true, true, true, false, 0xef, 0xef, 0xef, 0},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNumberParse(t *testing.T) {
|
|
||||||
for _, test := range numberTests {
|
|
||||||
// If fmt.Sscan thinks it's complex, it's complex. We can't trust the output
|
|
||||||
// because imaginary comes out as a number.
|
|
||||||
var c complex128
|
|
||||||
typ := itemNumber
|
|
||||||
var tree *Tree
|
|
||||||
if test.text[0] == '\'' {
|
|
||||||
typ = itemCharConstant
|
|
||||||
} else {
|
|
||||||
_, err := fmt.Sscan(test.text, &c)
|
|
||||||
if err == nil {
|
|
||||||
typ = itemComplex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n, err := tree.newNumber(0, test.text, typ)
|
|
||||||
ok := test.isInt || test.isUint || test.isFloat || test.isComplex
|
|
||||||
if ok && err != nil {
|
|
||||||
t.Errorf("unexpected error for %q: %s", test.text, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !ok && err == nil {
|
|
||||||
t.Errorf("expected error for %q", test.text)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
if *debug {
|
|
||||||
fmt.Printf("%s\n\t%s\n", test.text, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if n.IsComplex != test.isComplex {
|
|
||||||
t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex)
|
|
||||||
}
|
|
||||||
if test.isInt {
|
|
||||||
if !n.IsInt {
|
|
||||||
t.Errorf("expected integer for %q", test.text)
|
|
||||||
}
|
|
||||||
if n.Int64 != test.int64 {
|
|
||||||
t.Errorf("int64 for %q should be %d Is %d", test.text, test.int64, n.Int64)
|
|
||||||
}
|
|
||||||
} else if n.IsInt {
|
|
||||||
t.Errorf("did not expect integer for %q", test.text)
|
|
||||||
}
|
|
||||||
if test.isUint {
|
|
||||||
if !n.IsUint {
|
|
||||||
t.Errorf("expected unsigned integer for %q", test.text)
|
|
||||||
}
|
|
||||||
if n.Uint64 != test.uint64 {
|
|
||||||
t.Errorf("uint64 for %q should be %d Is %d", test.text, test.uint64, n.Uint64)
|
|
||||||
}
|
|
||||||
} else if n.IsUint {
|
|
||||||
t.Errorf("did not expect unsigned integer for %q", test.text)
|
|
||||||
}
|
|
||||||
if test.isFloat {
|
|
||||||
if !n.IsFloat {
|
|
||||||
t.Errorf("expected float for %q", test.text)
|
|
||||||
}
|
|
||||||
if n.Float64 != test.float64 {
|
|
||||||
t.Errorf("float64 for %q should be %g Is %g", test.text, test.float64, n.Float64)
|
|
||||||
}
|
|
||||||
} else if n.IsFloat {
|
|
||||||
t.Errorf("did not expect float for %q", test.text)
|
|
||||||
}
|
|
||||||
if test.isComplex {
|
|
||||||
if !n.IsComplex {
|
|
||||||
t.Errorf("expected complex for %q", test.text)
|
|
||||||
}
|
|
||||||
if n.Complex128 != test.complex128 {
|
|
||||||
t.Errorf("complex128 for %q should be %g Is %g", test.text, test.complex128, n.Complex128)
|
|
||||||
}
|
|
||||||
} else if n.IsComplex {
|
|
||||||
t.Errorf("did not expect complex for %q", test.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type parseTest struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
ok bool
|
|
||||||
result string // what the user would see in an error message.
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
noError = true
|
|
||||||
hasError = false
|
|
||||||
)
|
|
||||||
|
|
||||||
var parseTests = []parseTest{
|
|
||||||
{"empty", "", noError,
|
|
||||||
``},
|
|
||||||
{"comment", "{{/*\n\n\n*/}}", noError,
|
|
||||||
``},
|
|
||||||
{"spaces", " \t\n", noError,
|
|
||||||
`" \t\n"`},
|
|
||||||
{"text", "some text", noError,
|
|
||||||
`"some text"`},
|
|
||||||
{"emptyAction", "{{}}", hasError,
|
|
||||||
`{{}}`},
|
|
||||||
{"field", "{{.X}}", noError,
|
|
||||||
`{{.X}}`},
|
|
||||||
{"simple command", "{{printf}}", noError,
|
|
||||||
`{{printf}}`},
|
|
||||||
{"$ invocation", "{{$}}", noError,
|
|
||||||
"{{$}}"},
|
|
||||||
{"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError,
|
|
||||||
"{{with $x := 3}}{{$x 23}}{{end}}"},
|
|
||||||
{"variable with fields", "{{$.I}}", noError,
|
|
||||||
"{{$.I}}"},
|
|
||||||
{"multi-word command", "{{printf `%d` 23}}", noError,
|
|
||||||
"{{printf `%d` 23}}"},
|
|
||||||
{"pipeline", "{{.X|.Y}}", noError,
|
|
||||||
`{{.X | .Y}}`},
|
|
||||||
{"pipeline with decl", "{{$x := .X|.Y}}", noError,
|
|
||||||
`{{$x := .X | .Y}}`},
|
|
||||||
{"nested pipeline", "{{.X (.Y .Z) (.A | .B .C) (.E)}}", noError,
|
|
||||||
`{{.X (.Y .Z) (.A | .B .C) (.E)}}`},
|
|
||||||
{"field applied to parentheses", "{{(.Y .Z).Field}}", noError,
|
|
||||||
`{{(.Y .Z).Field}}`},
|
|
||||||
{"simple if", "{{if .X}}hello{{end}}", noError,
|
|
||||||
`{{if .X}}"hello"{{end}}`},
|
|
||||||
{"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
|
|
||||||
`{{if .X}}"true"{{else}}"false"{{end}}`},
|
|
||||||
{"if with else if", "{{if .X}}true{{else if .Y}}false{{end}}", noError,
|
|
||||||
`{{if .X}}"true"{{else}}{{if .Y}}"false"{{end}}{{end}}`},
|
|
||||||
{"if else chain", "+{{if .X}}X{{else if .Y}}Y{{else if .Z}}Z{{end}}+", noError,
|
|
||||||
`"+"{{if .X}}"X"{{else}}{{if .Y}}"Y"{{else}}{{if .Z}}"Z"{{end}}{{end}}{{end}}"+"`},
|
|
||||||
{"simple range", "{{range .X}}hello{{end}}", noError,
|
|
||||||
`{{range .X}}"hello"{{end}}`},
|
|
||||||
{"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError,
|
|
||||||
`{{range .X.Y.Z}}"hello"{{end}}`},
|
|
||||||
{"nested range", "{{range .X}}hello{{range .Y}}goodbye{{end}}{{end}}", noError,
|
|
||||||
`{{range .X}}"hello"{{range .Y}}"goodbye"{{end}}{{end}}`},
|
|
||||||
{"range with else", "{{range .X}}true{{else}}false{{end}}", noError,
|
|
||||||
`{{range .X}}"true"{{else}}"false"{{end}}`},
|
|
||||||
{"range over pipeline", "{{range .X|.M}}true{{else}}false{{end}}", noError,
|
|
||||||
`{{range .X | .M}}"true"{{else}}"false"{{end}}`},
|
|
||||||
{"range []int", "{{range .SI}}{{.}}{{end}}", noError,
|
|
||||||
`{{range .SI}}{{.}}{{end}}`},
|
|
||||||
{"range 1 var", "{{range $x := .SI}}{{.}}{{end}}", noError,
|
|
||||||
`{{range $x := .SI}}{{.}}{{end}}`},
|
|
||||||
{"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
|
|
||||||
`{{range $x, $y := .SI}}{{.}}{{end}}`},
|
|
||||||
{"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError,
|
|
||||||
`{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
|
|
||||||
{"template", "{{template `x`}}", noError,
|
|
||||||
`{{template "x"}}`},
|
|
||||||
{"template with arg", "{{template `x` .Y}}", noError,
|
|
||||||
`{{template "x" .Y}}`},
|
|
||||||
{"with", "{{with .X}}hello{{end}}", noError,
|
|
||||||
`{{with .X}}"hello"{{end}}`},
|
|
||||||
{"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
|
|
||||||
`{{with .X}}"hello"{{else}}"goodbye"{{end}}`},
|
|
||||||
// Trimming spaces.
|
|
||||||
{"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`},
|
|
||||||
{"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`},
|
|
||||||
{"trim left and right", "x \r\n\t{{- 3 -}}\n\n\ty", noError, `"x"{{3}}"y"`},
|
|
||||||
{"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"`},
|
|
||||||
{"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `"y"`},
|
|
||||||
{"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x""y"`},
|
|
||||||
{"block definition", `{{block "foo" .}}hello{{end}}`, noError,
|
|
||||||
`{{template "foo" .}}`},
|
|
||||||
// Errors.
|
|
||||||
{"unclosed action", "hello{{range", hasError, ""},
|
|
||||||
{"unmatched end", "{{end}}", hasError, ""},
|
|
||||||
{"unmatched else", "{{else}}", hasError, ""},
|
|
||||||
{"unmatched else after if", "{{if .X}}hello{{end}}{{else}}", hasError, ""},
|
|
||||||
{"multiple else", "{{if .X}}1{{else}}2{{else}}3{{end}}", hasError, ""},
|
|
||||||
{"missing end", "hello{{range .x}}", hasError, ""},
|
|
||||||
{"missing end after else", "hello{{range .x}}{{else}}", hasError, ""},
|
|
||||||
{"undefined function", "hello{{undefined}}", hasError, ""},
|
|
||||||
{"undefined variable", "{{$x}}", hasError, ""},
|
|
||||||
{"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""},
|
|
||||||
{"variable undefined in template", "{{template $v}}", hasError, ""},
|
|
||||||
{"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""},
|
|
||||||
{"template with field ref", "{{template .X}}", hasError, ""},
|
|
||||||
{"template with var", "{{template $v}}", hasError, ""},
|
|
||||||
{"invalid punctuation", "{{printf 3, 4}}", hasError, ""},
|
|
||||||
{"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""},
|
|
||||||
{"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""},
|
|
||||||
{"dot applied to parentheses", "{{printf (printf .).}}", hasError, ""},
|
|
||||||
{"adjacent args", "{{printf 3`x`}}", hasError, ""},
|
|
||||||
{"adjacent args with .", "{{printf `x`.}}", hasError, ""},
|
|
||||||
{"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""},
|
|
||||||
// Other kinds of assignments and operators aren't available yet.
|
|
||||||
{"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"},
|
|
||||||
{"bug0b", "{{$x += 1}}{{$x}}", hasError, ""},
|
|
||||||
{"bug0c", "{{$x ! 2}}{{$x}}", hasError, ""},
|
|
||||||
{"bug0d", "{{$x % 3}}{{$x}}", hasError, ""},
|
|
||||||
// Check the parse fails for := rather than comma.
|
|
||||||
{"bug0e", "{{range $x := $y := 3}}{{end}}", hasError, ""},
|
|
||||||
// Another bug: variable read must ignore following punctuation.
|
|
||||||
{"bug1a", "{{$x:=.}}{{$x!2}}", hasError, ""}, // ! is just illegal here.
|
|
||||||
{"bug1b", "{{$x:=.}}{{$x+2}}", hasError, ""}, // $x+2 should not parse as ($x) (+2).
|
|
||||||
{"bug1c", "{{$x:=.}}{{$x +2}}", noError, "{{$x := .}}{{$x +2}}"}, // It's OK with a space.
|
|
||||||
// dot following a literal value
|
|
||||||
{"dot after integer", "{{1.E}}", hasError, ""},
|
|
||||||
{"dot after float", "{{0.1.E}}", hasError, ""},
|
|
||||||
{"dot after boolean", "{{true.E}}", hasError, ""},
|
|
||||||
{"dot after char", "{{'a'.any}}", hasError, ""},
|
|
||||||
{"dot after string", `{{"hello".guys}}`, hasError, ""},
|
|
||||||
{"dot after dot", "{{..E}}", hasError, ""},
|
|
||||||
{"dot after nil", "{{nil.E}}", hasError, ""},
|
|
||||||
// Wrong pipeline
|
|
||||||
{"wrong pipeline dot", "{{12|.}}", hasError, ""},
|
|
||||||
{"wrong pipeline number", "{{.|12|printf}}", hasError, ""},
|
|
||||||
{"wrong pipeline string", "{{.|printf|\"error\"}}", hasError, ""},
|
|
||||||
{"wrong pipeline char", "{{12|printf|'e'}}", hasError, ""},
|
|
||||||
{"wrong pipeline boolean", "{{.|true}}", hasError, ""},
|
|
||||||
{"wrong pipeline nil", "{{'c'|nil}}", hasError, ""},
|
|
||||||
{"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""},
|
|
||||||
// Missing pipeline in block
|
|
||||||
{"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
var builtins = map[string]interface{}{
|
|
||||||
"printf": fmt.Sprintf,
|
|
||||||
}
|
|
||||||
|
|
||||||
func testParse(doCopy bool, t *testing.T) {
|
|
||||||
textFormat = "%q"
|
|
||||||
defer func() { textFormat = "%s" }()
|
|
||||||
for _, test := range parseTests {
|
|
||||||
tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins)
|
|
||||||
switch {
|
|
||||||
case err == nil && !test.ok:
|
|
||||||
t.Errorf("%q: expected error; got none", test.name)
|
|
||||||
continue
|
|
||||||
case err != nil && test.ok:
|
|
||||||
t.Errorf("%q: unexpected error: %v", test.name, err)
|
|
||||||
continue
|
|
||||||
case err != nil && !test.ok:
|
|
||||||
// expected error, got one
|
|
||||||
if *debug {
|
|
||||||
fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var result string
|
|
||||||
if doCopy {
|
|
||||||
result = tmpl.Root.Copy().String()
|
|
||||||
} else {
|
|
||||||
result = tmpl.Root.String()
|
|
||||||
}
|
|
||||||
if result != test.result {
|
|
||||||
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
|
||||||
testParse(false, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same as TestParse, but we copy the node first
|
|
||||||
func TestParseCopy(t *testing.T) {
|
|
||||||
testParse(true, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
type isEmptyTest struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
empty bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var isEmptyTests = []isEmptyTest{
|
|
||||||
{"empty", ``, true},
|
|
||||||
{"nonempty", `hello`, false},
|
|
||||||
{"spaces only", " \t\n \t\n", true},
|
|
||||||
{"definition", `{{define "x"}}something{{end}}`, true},
|
|
||||||
{"definitions and space", "{{define `x`}}something{{end}}\n\n{{define `y`}}something{{end}}\n\n", true},
|
|
||||||
{"definitions and text", "{{define `x`}}something{{end}}\nx\n{{define `y`}}something{{end}}\ny\n", false},
|
|
||||||
{"definition and action", "{{define `x`}}something{{end}}{{if 3}}foo{{end}}", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsEmpty(t *testing.T) {
|
|
||||||
if !IsEmptyTree(nil) {
|
|
||||||
t.Errorf("nil tree is not empty")
|
|
||||||
}
|
|
||||||
for _, test := range isEmptyTests {
|
|
||||||
tree, err := New("root").Parse(test.input, "", "", make(map[string]*Tree), nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%q: unexpected error: %v", test.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if empty := IsEmptyTree(tree.Root); empty != test.empty {
|
|
||||||
t.Errorf("%q: expected %t got %t", test.name, test.empty, empty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorContextWithTreeCopy(t *testing.T) {
|
|
||||||
tree, err := New("root").Parse("{{if true}}{{end}}", "", "", make(map[string]*Tree), nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected tree parse failure: %v", err)
|
|
||||||
}
|
|
||||||
treeCopy := tree.Copy()
|
|
||||||
wantLocation, wantContext := tree.ErrorContext(tree.Root.Nodes[0])
|
|
||||||
gotLocation, gotContext := treeCopy.ErrorContext(treeCopy.Root.Nodes[0])
|
|
||||||
if wantLocation != gotLocation {
|
|
||||||
t.Errorf("wrong error location want %q got %q", wantLocation, gotLocation)
|
|
||||||
}
|
|
||||||
if wantContext != gotContext {
|
|
||||||
t.Errorf("wrong error location want %q got %q", wantContext, gotContext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All failures, and the result is a string that must appear in the error message.
|
|
||||||
var errorTests = []parseTest{
|
|
||||||
// Check line numbers are accurate.
|
|
||||||
{"unclosed1",
|
|
||||||
"line1\n{{",
|
|
||||||
hasError, `unclosed1:2: unexpected unclosed action in command`},
|
|
||||||
{"unclosed2",
|
|
||||||
"line1\n{{define `x`}}line2\n{{",
|
|
||||||
hasError, `unclosed2:3: unexpected unclosed action in command`},
|
|
||||||
// Specific errors.
|
|
||||||
{"function",
|
|
||||||
"{{foo}}",
|
|
||||||
hasError, `function "foo" not defined`},
|
|
||||||
{"comment",
|
|
||||||
"{{/*}}",
|
|
||||||
hasError, `unclosed comment`},
|
|
||||||
{"lparen",
|
|
||||||
"{{.X (1 2 3}}",
|
|
||||||
hasError, `unclosed left paren`},
|
|
||||||
{"rparen",
|
|
||||||
"{{.X 1 2 3)}}",
|
|
||||||
hasError, `unexpected ")"`},
|
|
||||||
{"space",
|
|
||||||
"{{`x`3}}",
|
|
||||||
hasError, `in operand`},
|
|
||||||
{"idchar",
|
|
||||||
"{{a#}}",
|
|
||||||
hasError, `'#'`},
|
|
||||||
{"charconst",
|
|
||||||
"{{'a}}",
|
|
||||||
hasError, `unterminated character constant`},
|
|
||||||
{"stringconst",
|
|
||||||
`{{"a}}`,
|
|
||||||
hasError, `unterminated quoted string`},
|
|
||||||
{"rawstringconst",
|
|
||||||
"{{`a}}",
|
|
||||||
hasError, `unterminated raw quoted string`},
|
|
||||||
{"number",
|
|
||||||
"{{0xi}}",
|
|
||||||
hasError, `number syntax`},
|
|
||||||
{"multidefine",
|
|
||||||
"{{define `a`}}a{{end}}{{define `a`}}b{{end}}",
|
|
||||||
hasError, `multiple definition of template`},
|
|
||||||
{"eof",
|
|
||||||
"{{range .X}}",
|
|
||||||
hasError, `unexpected EOF`},
|
|
||||||
{"variable",
|
|
||||||
// Declare $x so it's defined, to avoid that error, and then check we don't parse a declaration.
|
|
||||||
"{{$x := 23}}{{with $x.y := 3}}{{$x 23}}{{end}}",
|
|
||||||
hasError, `unexpected ":="`},
|
|
||||||
{"multidecl",
|
|
||||||
"{{$a,$b,$c := 23}}",
|
|
||||||
hasError, `too many declarations`},
|
|
||||||
{"undefvar",
|
|
||||||
"{{$a}}",
|
|
||||||
hasError, `undefined variable`},
|
|
||||||
{"wrongdot",
|
|
||||||
"{{true.any}}",
|
|
||||||
hasError, `unexpected . after term`},
|
|
||||||
{"wrongpipeline",
|
|
||||||
"{{12|false}}",
|
|
||||||
hasError, `non executable command in pipeline`},
|
|
||||||
{"emptypipeline",
|
|
||||||
`{{ ( ) }}`,
|
|
||||||
hasError, `missing value for parenthesized pipeline`},
|
|
||||||
{"multilinerawstring",
|
|
||||||
"{{ $v := `\n` }} {{",
|
|
||||||
hasError, `multilinerawstring:2: unexpected unclosed action`},
|
|
||||||
{"rangeundefvar",
|
|
||||||
"{{range $k}}{{end}}",
|
|
||||||
hasError, `undefined variable`},
|
|
||||||
{"rangeundefvars",
|
|
||||||
"{{range $k, $v}}{{end}}",
|
|
||||||
hasError, `undefined variable`},
|
|
||||||
{"rangemissingvalue1",
|
|
||||||
"{{range $k,}}{{end}}",
|
|
||||||
hasError, `missing value for range`},
|
|
||||||
{"rangemissingvalue2",
|
|
||||||
"{{range $k, $v := }}{{end}}",
|
|
||||||
hasError, `missing value for range`},
|
|
||||||
{"rangenotvariable1",
|
|
||||||
"{{range $k, .}}{{end}}",
|
|
||||||
hasError, `range can only initialize variables`},
|
|
||||||
{"rangenotvariable2",
|
|
||||||
"{{range $k, 123 := .}}{{end}}",
|
|
||||||
hasError, `range can only initialize variables`},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrors(t *testing.T) {
|
|
||||||
for _, test := range errorTests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
_, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error %q, got nil", test.result)
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), test.result) {
|
|
||||||
t.Fatalf("error %q does not contain %q", err, test.result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBlock(t *testing.T) {
|
|
||||||
const (
|
|
||||||
input = `a{{block "inner" .}}bar{{.}}baz{{end}}b`
|
|
||||||
outer = `a{{template "inner" .}}b`
|
|
||||||
inner = `bar{{.}}baz`
|
|
||||||
)
|
|
||||||
treeSet := make(map[string]*Tree)
|
|
||||||
tmpl, err := New("outer").Parse(input, "", "", treeSet, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if g, w := tmpl.Root.String(), outer; g != w {
|
|
||||||
t.Errorf("outer template = %q, want %q", g, w)
|
|
||||||
}
|
|
||||||
inTmpl := treeSet["inner"]
|
|
||||||
if inTmpl == nil {
|
|
||||||
t.Fatal("block did not define template")
|
|
||||||
}
|
|
||||||
if g, w := inTmpl.Root.String(), inner; g != w {
|
|
||||||
t.Errorf("inner template = %q, want %q", g, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLineNum(t *testing.T) {
|
|
||||||
const count = 100
|
|
||||||
text := strings.Repeat("{{printf 1234}}\n", count)
|
|
||||||
tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Check the line numbers. Each line is an action containing a template, followed by text.
|
|
||||||
// That's two nodes per line.
|
|
||||||
nodes := tree.Root.Nodes
|
|
||||||
for i := 0; i < len(nodes); i += 2 {
|
|
||||||
line := 1 + i/2
|
|
||||||
// Action first.
|
|
||||||
action := nodes[i].(*ActionNode)
|
|
||||||
if action.Line != line {
|
|
||||||
t.Fatalf("line %d: action is line %d", line, action.Line)
|
|
||||||
}
|
|
||||||
pipe := action.Pipe
|
|
||||||
if pipe.Line != line {
|
|
||||||
t.Fatalf("line %d: pipe is line %d", line, pipe.Line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkParseLarge(b *testing.B) {
|
|
||||||
text := strings.Repeat("{{1234}}\n", 10000)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
{{define "x"}}TEXT{{end}}
|
|
||||||
{{define "dotV"}}{{.V}}{{end}}
|
|
@ -1,2 +0,0 @@
|
|||||||
{{define "dot"}}{{.}}{{end}}
|
|
||||||
{{define "nested"}}{{template "dot" .}}{{end}}
|
|
@ -1,3 +0,0 @@
|
|||||||
template1
|
|
||||||
{{define "x"}}x{{end}}
|
|
||||||
{{template "y"}}
|
|
@ -1,3 +0,0 @@
|
|||||||
template2
|
|
||||||
{{define "y"}}y{{end}}
|
|
||||||
{{template "x"}}
|
|
Loading…
Reference in New Issue
Block a user