| // Copyright 2021 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 |
| |
| import ( |
| "strings" |
| "testing" |
| ) |
| |
| type datum struct { |
| buf string |
| cnt int |
| syms []string // the symbols in the parse of buf |
| } |
| |
| var tmpl = []datum{{` |
| {{if (foo .X.Y)}}{{$A := "hi"}}{{.Z $A}}{{else}} |
| {{$A.X 12}} |
| {{foo (.X.Y) 23 ($A.Zü)}} |
| {{end}}`, 1, []string{"{7,3,foo,Function,false}", "{12,1,X,Method,false}", |
| "{14,1,Y,Method,false}", "{21,2,$A,Variable,true}", "{26,2,,String,false}", |
| "{35,1,Z,Method,false}", "{38,2,$A,Variable,false}", |
| "{53,2,$A,Variable,false}", "{56,1,X,Method,false}", "{57,2,,Number,false}", |
| "{64,3,foo,Function,false}", "{70,1,X,Method,false}", |
| "{72,1,Y,Method,false}", "{75,2,,Number,false}", "{80,2,$A,Variable,false}", |
| "{83,2,Zü,Method,false}", "{94,3,,Constant,false}"}}, |
| |
| {`{{define "zzz"}}{{.}}{{end}} |
| {{template "zzz"}}`, 2, []string{"{10,3,zzz,Namespace,true}", "{18,1,dot,Variable,false}", |
| "{41,3,zzz,Package,false}"}}, |
| |
| {`{{block "aaa" foo}}b{{end}}`, 2, []string{"{9,3,aaa,Namespace,true}", |
| "{9,3,aaa,Package,false}", "{14,3,foo,Function,false}", "{19,1,,Constant,false}"}}, |
| } |
| |
| func TestSymbols(t *testing.T) { |
| for i, x := range tmpl { |
| got := parseBuffer([]byte(x.buf)) |
| if got.ParseErr != nil { |
| t.Errorf("error:%v", got.ParseErr) |
| continue |
| } |
| if len(got.named) != x.cnt { |
| t.Errorf("%d: got %d, expected %d", i, len(got.named), x.cnt) |
| } |
| for n, s := range got.symbols { |
| if s.String() != x.syms[n] { |
| t.Errorf("%d: got %s, expected %s", i, s.String(), x.syms[n]) |
| } |
| } |
| } |
| } |
| |
| func TestWordAt(t *testing.T) { |
| want := []string{"", "", "$A", "$A", "", "", "", "", "", "", |
| "", "", "", "if", "if", "", "$A", "$A", "", "", |
| "B", "", "", "end", "end", "end", "", "", ""} |
| p := parseBuffer([]byte("{{$A := .}}{{if $A}}B{{end}}")) |
| for i := 0; i < len(p.buf); i++ { |
| got := findWordAt(p, i) |
| if got != want[i] { |
| t.Errorf("for %d, got %q, wanted %q", i, got, want[i]) |
| } |
| } |
| } |
| |
| func TestNLS(t *testing.T) { |
| buf := `{{if (foÜx .X.Y)}}{{$A := "hi"}}{{.Z $A}}{{else}} |
| {{$A.X 12}} |
| {{foo (.X.Y) 23 ($A.Z)}} |
| {{end}} |
| ` |
| p := parseBuffer([]byte(buf)) |
| if p.ParseErr != nil { |
| t.Fatal(p.ParseErr) |
| } |
| // line 0 doesn't have a \n in front of it |
| for i := 1; i < len(p.nls)-1; i++ { |
| if buf[p.nls[i]] != '\n' { |
| t.Errorf("line %d got %c", i, buf[p.nls[i]]) |
| } |
| } |
| // fake line at end of file |
| if p.nls[len(p.nls)-1] != len(buf) { |
| t.Errorf("got %d expected %d", p.nls[len(p.nls)-1], len(buf)) |
| } |
| } |
| |
| func TestLineCol(t *testing.T) { |
| buf := `{{if (foÜx .X.Y)}}{{$A := "hi"}}{{.Z $A}}{{else}} |
| {{$A.X 12}} |
| {{foo (.X.Y) 23 ($A.Z)}} |
| {{end}}` |
| if false { |
| t.Error(buf) |
| } |
| for n, cx := range tmpl { |
| buf := cx.buf |
| p := parseBuffer([]byte(buf)) |
| if p.ParseErr != nil { |
| t.Fatal(p.ParseErr) |
| } |
| type loc struct { |
| offset int |
| l, c uint32 |
| } |
| saved := []loc{} |
| // forwards |
| var lastl, lastc uint32 |
| for offset := range buf { |
| l, c := p.LineCol(offset) |
| saved = append(saved, loc{offset, l, c}) |
| if l > lastl { |
| lastl = l |
| if c != 0 { |
| t.Errorf("line %d, got %d instead of 0", l, c) |
| } |
| } |
| if c > lastc { |
| lastc = c |
| } |
| } |
| lines := strings.Split(buf, "\n") |
| mxlen := -1 |
| for _, l := range lines { |
| if len(l) > mxlen { |
| mxlen = len(l) |
| } |
| } |
| if int(lastl) != len(lines)-1 && int(lastc) != mxlen { |
| // lastl is 0 if there is only 1 line(?) |
| t.Errorf("expected %d, %d, got %d, %d for case %d", len(lines)-1, mxlen, lastl, lastc, n) |
| } |
| // backwards |
| for j := len(saved) - 1; j >= 0; j-- { |
| s := saved[j] |
| xl, xc := p.LineCol(s.offset) |
| if xl != s.l || xc != s.c { |
| t.Errorf("at offset %d(%d), got (%d,%d), expected (%d,%d)", s.offset, j, xl, xc, s.l, s.c) |
| } |
| } |
| } |
| } |
| |
| func TestLineColNL(t *testing.T) { |
| buf := "\n\n\n\n\n" |
| p := parseBuffer([]byte(buf)) |
| if p.ParseErr != nil { |
| t.Fatal(p.ParseErr) |
| } |
| for i := 0; i < len(buf); i++ { |
| l, c := p.LineCol(i) |
| if c != 0 || int(l) != i+1 { |
| t.Errorf("got (%d,%d), expected (%d,0)", l, c, i) |
| } |
| } |
| } |
| |
| func TestPos(t *testing.T) { |
| buf := ` |
| {{if (foÜx .X.Y)}}{{$A := "hi"}}{{.Z $A}}{{else}} |
| {{$A.X 12}} |
| {{foo (.X.Y) 23 ($A.Z)}} |
| {{end}}` |
| p := parseBuffer([]byte(buf)) |
| if p.ParseErr != nil { |
| t.Fatal(p.ParseErr) |
| } |
| for pos, r := range buf { |
| if r == '\n' { |
| continue |
| } |
| x := p.Position(pos) |
| n := p.FromPosition(x) |
| if n != pos { |
| // once it's wrong, it will be wrong forever |
| t.Fatalf("at pos %d (rune %c) got %d {%#v]", pos, r, n, x) |
| } |
| |
| } |
| } |
| func TestLen(t *testing.T) { |
| data := []struct { |
| cnt int |
| v string |
| }{{1, "a"}, {1, "膈"}, {4, "😆🥸"}, {7, "3😀4567"}} |
| p := &Parsed{nonASCII: true} |
| for _, d := range data { |
| got := p.utf16len([]byte(d.v)) |
| if got != d.cnt { |
| t.Errorf("%v, got %d wanted %d", d, got, d.cnt) |
| } |
| } |
| } |
| |
| func TestUtf16(t *testing.T) { |
| buf := ` |
| {{if (foÜx .X.Y)}}😀{{$A := "hi"}}{{.Z $A}}{{else}} |
| {{$A.X 12}} |
| {{foo (.X.Y) 23 ($A.Z)}} |
| {{end}}` |
| p := parseBuffer([]byte(buf)) |
| if p.nonASCII == false { |
| t.Error("expected nonASCII to be true") |
| } |
| } |
| |
| type ttest struct { |
| tmpl string |
| tokCnt int |
| elidedCnt int8 |
| } |
| |
| func TestQuotes(t *testing.T) { |
| tsts := []ttest{ |
| {"{{- /*comment*/ -}}", 1, 0}, |
| {"{{/*`\ncomment\n`*/}}", 1, 0}, |
| //{"{{foo\nbar}}\n", 1, 0}, // this action spanning lines parses in 1.16 |
| {"{{\"{{foo}}{{\"}}", 1, 0}, |
| {"{{\n{{- when}}", 1, 1}, // corrected |
| {"{{{{if .}}xx{{\n{{end}}", 2, 2}, // corrected |
| } |
| for _, s := range tsts { |
| p := parseBuffer([]byte(s.tmpl)) |
| if len(p.tokens) != s.tokCnt { |
| t.Errorf("%q: got %d tokens, expected %d", s, len(p.tokens), s.tokCnt) |
| } |
| if p.ParseErr != nil { |
| t.Errorf("%q: %v", string(p.buf), p.ParseErr) |
| } |
| if len(p.elided) != int(s.elidedCnt) { |
| t.Errorf("%q: elided %d, expected %d", s, len(p.elided), s.elidedCnt) |
| } |
| } |
| } |