github.com/huandu/go@v0.0.0-20151114150818-04e615e41150/src/text/template/parse/lex_test.go (about)

     1  // 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 parse
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  )
    11  
    12  // Make the types prettyprint.
    13  var itemName = map[itemType]string{
    14  	itemError:        "error",
    15  	itemBool:         "bool",
    16  	itemChar:         "char",
    17  	itemCharConstant: "charconst",
    18  	itemComplex:      "complex",
    19  	itemColonEquals:  ":=",
    20  	itemEOF:          "EOF",
    21  	itemField:        "field",
    22  	itemIdentifier:   "identifier",
    23  	itemLeftDelim:    "left delim",
    24  	itemLeftParen:    "(",
    25  	itemNumber:       "number",
    26  	itemPipe:         "pipe",
    27  	itemRawString:    "raw string",
    28  	itemRightDelim:   "right delim",
    29  	itemRightParen:   ")",
    30  	itemSpace:        "space",
    31  	itemString:       "string",
    32  	itemVariable:     "variable",
    33  
    34  	// keywords
    35  	itemDot:      ".",
    36  	itemDefine:   "define",
    37  	itemElse:     "else",
    38  	itemIf:       "if",
    39  	itemEnd:      "end",
    40  	itemNil:      "nil",
    41  	itemRange:    "range",
    42  	itemTemplate: "template",
    43  	itemWith:     "with",
    44  }
    45  
    46  func (i itemType) String() string {
    47  	s := itemName[i]
    48  	if s == "" {
    49  		return fmt.Sprintf("item%d", int(i))
    50  	}
    51  	return s
    52  }
    53  
    54  type lexTest struct {
    55  	name  string
    56  	input string
    57  	items []item
    58  }
    59  
    60  var (
    61  	tEOF        = item{itemEOF, 0, ""}
    62  	tFor        = item{itemIdentifier, 0, "for"}
    63  	tLeft       = item{itemLeftDelim, 0, "{{"}
    64  	tLpar       = item{itemLeftParen, 0, "("}
    65  	tPipe       = item{itemPipe, 0, "|"}
    66  	tQuote      = item{itemString, 0, `"abc \n\t\" "`}
    67  	tRange      = item{itemRange, 0, "range"}
    68  	tRight      = item{itemRightDelim, 0, "}}"}
    69  	tRpar       = item{itemRightParen, 0, ")"}
    70  	tSpace      = item{itemSpace, 0, " "}
    71  	raw         = "`" + `abc\n\t\" ` + "`"
    72  	rawNL       = "`now is{{\n}}the time`" // Contains newline inside raw quote.
    73  	tRawQuote   = item{itemRawString, 0, raw}
    74  	tRawQuoteNL = item{itemRawString, 0, rawNL}
    75  )
    76  
    77  var lexTests = []lexTest{
    78  	{"empty", "", []item{tEOF}},
    79  	{"spaces", " \t\n", []item{{itemText, 0, " \t\n"}, tEOF}},
    80  	{"text", `now is the time`, []item{{itemText, 0, "now is the time"}, tEOF}},
    81  	{"text with comment", "hello-{{/* this is a comment */}}-world", []item{
    82  		{itemText, 0, "hello-"},
    83  		{itemText, 0, "-world"},
    84  		tEOF,
    85  	}},
    86  	{"punctuation", "{{,@% }}", []item{
    87  		tLeft,
    88  		{itemChar, 0, ","},
    89  		{itemChar, 0, "@"},
    90  		{itemChar, 0, "%"},
    91  		tSpace,
    92  		tRight,
    93  		tEOF,
    94  	}},
    95  	{"parens", "{{((3))}}", []item{
    96  		tLeft,
    97  		tLpar,
    98  		tLpar,
    99  		{itemNumber, 0, "3"},
   100  		tRpar,
   101  		tRpar,
   102  		tRight,
   103  		tEOF,
   104  	}},
   105  	{"empty action", `{{}}`, []item{tLeft, tRight, tEOF}},
   106  	{"for", `{{for}}`, []item{tLeft, tFor, tRight, tEOF}},
   107  	{"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}},
   108  	{"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}},
   109  	{"raw quote with newline", "{{" + rawNL + "}}", []item{tLeft, tRawQuoteNL, tRight, tEOF}},
   110  	{"numbers", "{{1 02 0x14 -7.2i 1e3 +1.2e-4 4.2i 1+2i}}", []item{
   111  		tLeft,
   112  		{itemNumber, 0, "1"},
   113  		tSpace,
   114  		{itemNumber, 0, "02"},
   115  		tSpace,
   116  		{itemNumber, 0, "0x14"},
   117  		tSpace,
   118  		{itemNumber, 0, "-7.2i"},
   119  		tSpace,
   120  		{itemNumber, 0, "1e3"},
   121  		tSpace,
   122  		{itemNumber, 0, "+1.2e-4"},
   123  		tSpace,
   124  		{itemNumber, 0, "4.2i"},
   125  		tSpace,
   126  		{itemComplex, 0, "1+2i"},
   127  		tRight,
   128  		tEOF,
   129  	}},
   130  	{"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{
   131  		tLeft,
   132  		{itemCharConstant, 0, `'a'`},
   133  		tSpace,
   134  		{itemCharConstant, 0, `'\n'`},
   135  		tSpace,
   136  		{itemCharConstant, 0, `'\''`},
   137  		tSpace,
   138  		{itemCharConstant, 0, `'\\'`},
   139  		tSpace,
   140  		{itemCharConstant, 0, `'\u00FF'`},
   141  		tSpace,
   142  		{itemCharConstant, 0, `'\xFF'`},
   143  		tSpace,
   144  		{itemCharConstant, 0, `'本'`},
   145  		tRight,
   146  		tEOF,
   147  	}},
   148  	{"bools", "{{true false}}", []item{
   149  		tLeft,
   150  		{itemBool, 0, "true"},
   151  		tSpace,
   152  		{itemBool, 0, "false"},
   153  		tRight,
   154  		tEOF,
   155  	}},
   156  	{"dot", "{{.}}", []item{
   157  		tLeft,
   158  		{itemDot, 0, "."},
   159  		tRight,
   160  		tEOF,
   161  	}},
   162  	{"nil", "{{nil}}", []item{
   163  		tLeft,
   164  		{itemNil, 0, "nil"},
   165  		tRight,
   166  		tEOF,
   167  	}},
   168  	{"dots", "{{.x . .2 .x.y.z}}", []item{
   169  		tLeft,
   170  		{itemField, 0, ".x"},
   171  		tSpace,
   172  		{itemDot, 0, "."},
   173  		tSpace,
   174  		{itemNumber, 0, ".2"},
   175  		tSpace,
   176  		{itemField, 0, ".x"},
   177  		{itemField, 0, ".y"},
   178  		{itemField, 0, ".z"},
   179  		tRight,
   180  		tEOF,
   181  	}},
   182  	{"keywords", "{{range if else end with}}", []item{
   183  		tLeft,
   184  		{itemRange, 0, "range"},
   185  		tSpace,
   186  		{itemIf, 0, "if"},
   187  		tSpace,
   188  		{itemElse, 0, "else"},
   189  		tSpace,
   190  		{itemEnd, 0, "end"},
   191  		tSpace,
   192  		{itemWith, 0, "with"},
   193  		tRight,
   194  		tEOF,
   195  	}},
   196  	{"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{
   197  		tLeft,
   198  		{itemVariable, 0, "$c"},
   199  		tSpace,
   200  		{itemColonEquals, 0, ":="},
   201  		tSpace,
   202  		{itemIdentifier, 0, "printf"},
   203  		tSpace,
   204  		{itemVariable, 0, "$"},
   205  		tSpace,
   206  		{itemVariable, 0, "$hello"},
   207  		tSpace,
   208  		{itemVariable, 0, "$23"},
   209  		tSpace,
   210  		{itemVariable, 0, "$"},
   211  		tSpace,
   212  		{itemVariable, 0, "$var"},
   213  		{itemField, 0, ".Field"},
   214  		tSpace,
   215  		{itemField, 0, ".Method"},
   216  		tRight,
   217  		tEOF,
   218  	}},
   219  	{"variable invocation", "{{$x 23}}", []item{
   220  		tLeft,
   221  		{itemVariable, 0, "$x"},
   222  		tSpace,
   223  		{itemNumber, 0, "23"},
   224  		tRight,
   225  		tEOF,
   226  	}},
   227  	{"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{
   228  		{itemText, 0, "intro "},
   229  		tLeft,
   230  		{itemIdentifier, 0, "echo"},
   231  		tSpace,
   232  		{itemIdentifier, 0, "hi"},
   233  		tSpace,
   234  		{itemNumber, 0, "1.2"},
   235  		tSpace,
   236  		tPipe,
   237  		{itemIdentifier, 0, "noargs"},
   238  		tPipe,
   239  		{itemIdentifier, 0, "args"},
   240  		tSpace,
   241  		{itemNumber, 0, "1"},
   242  		tSpace,
   243  		{itemString, 0, `"hi"`},
   244  		tRight,
   245  		{itemText, 0, " outro"},
   246  		tEOF,
   247  	}},
   248  	{"declaration", "{{$v := 3}}", []item{
   249  		tLeft,
   250  		{itemVariable, 0, "$v"},
   251  		tSpace,
   252  		{itemColonEquals, 0, ":="},
   253  		tSpace,
   254  		{itemNumber, 0, "3"},
   255  		tRight,
   256  		tEOF,
   257  	}},
   258  	{"2 declarations", "{{$v , $w := 3}}", []item{
   259  		tLeft,
   260  		{itemVariable, 0, "$v"},
   261  		tSpace,
   262  		{itemChar, 0, ","},
   263  		tSpace,
   264  		{itemVariable, 0, "$w"},
   265  		tSpace,
   266  		{itemColonEquals, 0, ":="},
   267  		tSpace,
   268  		{itemNumber, 0, "3"},
   269  		tRight,
   270  		tEOF,
   271  	}},
   272  	{"field of parenthesized expression", "{{(.X).Y}}", []item{
   273  		tLeft,
   274  		tLpar,
   275  		{itemField, 0, ".X"},
   276  		tRpar,
   277  		{itemField, 0, ".Y"},
   278  		tRight,
   279  		tEOF,
   280  	}},
   281  	// errors
   282  	{"badchar", "#{{\x01}}", []item{
   283  		{itemText, 0, "#"},
   284  		tLeft,
   285  		{itemError, 0, "unrecognized character in action: U+0001"},
   286  	}},
   287  	{"unclosed action", "{{\n}}", []item{
   288  		tLeft,
   289  		{itemError, 0, "unclosed action"},
   290  	}},
   291  	{"EOF in action", "{{range", []item{
   292  		tLeft,
   293  		tRange,
   294  		{itemError, 0, "unclosed action"},
   295  	}},
   296  	{"unclosed quote", "{{\"\n\"}}", []item{
   297  		tLeft,
   298  		{itemError, 0, "unterminated quoted string"},
   299  	}},
   300  	{"unclosed raw quote", "{{`xx}}", []item{
   301  		tLeft,
   302  		{itemError, 0, "unterminated raw quoted string"},
   303  	}},
   304  	{"unclosed char constant", "{{'\n}}", []item{
   305  		tLeft,
   306  		{itemError, 0, "unterminated character constant"},
   307  	}},
   308  	{"bad number", "{{3k}}", []item{
   309  		tLeft,
   310  		{itemError, 0, `bad number syntax: "3k"`},
   311  	}},
   312  	{"unclosed paren", "{{(3}}", []item{
   313  		tLeft,
   314  		tLpar,
   315  		{itemNumber, 0, "3"},
   316  		{itemError, 0, `unclosed left paren`},
   317  	}},
   318  	{"extra right paren", "{{3)}}", []item{
   319  		tLeft,
   320  		{itemNumber, 0, "3"},
   321  		tRpar,
   322  		{itemError, 0, `unexpected right paren U+0029 ')'`},
   323  	}},
   324  
   325  	// Fixed bugs
   326  	// Many elements in an action blew the lookahead until
   327  	// we made lexInsideAction not loop.
   328  	{"long pipeline deadlock", "{{|||||}}", []item{
   329  		tLeft,
   330  		tPipe,
   331  		tPipe,
   332  		tPipe,
   333  		tPipe,
   334  		tPipe,
   335  		tRight,
   336  		tEOF,
   337  	}},
   338  	{"text with bad comment", "hello-{{/*/}}-world", []item{
   339  		{itemText, 0, "hello-"},
   340  		{itemError, 0, `unclosed comment`},
   341  	}},
   342  	{"text with comment close separted from delim", "hello-{{/* */ }}-world", []item{
   343  		{itemText, 0, "hello-"},
   344  		{itemError, 0, `comment ends before closing delimiter`},
   345  	}},
   346  	// This one is an error that we can't catch because it breaks templates with
   347  	// minimized JavaScript. Should have fixed it before Go 1.1.
   348  	{"unmatched right delimiter", "hello-{.}}-world", []item{
   349  		{itemText, 0, "hello-{.}}-world"},
   350  		tEOF,
   351  	}},
   352  }
   353  
   354  // collect gathers the emitted items into a slice.
   355  func collect(t *lexTest, left, right string) (items []item) {
   356  	l := lex(t.name, t.input, left, right)
   357  	for {
   358  		item := l.nextItem()
   359  		items = append(items, item)
   360  		if item.typ == itemEOF || item.typ == itemError {
   361  			break
   362  		}
   363  	}
   364  	return
   365  }
   366  
   367  func equal(i1, i2 []item, checkPos bool) bool {
   368  	if len(i1) != len(i2) {
   369  		return false
   370  	}
   371  	for k := range i1 {
   372  		if i1[k].typ != i2[k].typ {
   373  			return false
   374  		}
   375  		if i1[k].val != i2[k].val {
   376  			return false
   377  		}
   378  		if checkPos && i1[k].pos != i2[k].pos {
   379  			return false
   380  		}
   381  	}
   382  	return true
   383  }
   384  
   385  func TestLex(t *testing.T) {
   386  	for _, test := range lexTests {
   387  		items := collect(&test, "", "")
   388  		if !equal(items, test.items, false) {
   389  			t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items)
   390  		}
   391  	}
   392  }
   393  
   394  // Some easy cases from above, but with delimiters $$ and @@
   395  var lexDelimTests = []lexTest{
   396  	{"punctuation", "$$,@%{{}}@@", []item{
   397  		tLeftDelim,
   398  		{itemChar, 0, ","},
   399  		{itemChar, 0, "@"},
   400  		{itemChar, 0, "%"},
   401  		{itemChar, 0, "{"},
   402  		{itemChar, 0, "{"},
   403  		{itemChar, 0, "}"},
   404  		{itemChar, 0, "}"},
   405  		tRightDelim,
   406  		tEOF,
   407  	}},
   408  	{"empty action", `$$@@`, []item{tLeftDelim, tRightDelim, tEOF}},
   409  	{"for", `$$for@@`, []item{tLeftDelim, tFor, tRightDelim, tEOF}},
   410  	{"quote", `$$"abc \n\t\" "@@`, []item{tLeftDelim, tQuote, tRightDelim, tEOF}},
   411  	{"raw quote", "$$" + raw + "@@", []item{tLeftDelim, tRawQuote, tRightDelim, tEOF}},
   412  }
   413  
   414  var (
   415  	tLeftDelim  = item{itemLeftDelim, 0, "$$"}
   416  	tRightDelim = item{itemRightDelim, 0, "@@"}
   417  )
   418  
   419  func TestDelims(t *testing.T) {
   420  	for _, test := range lexDelimTests {
   421  		items := collect(&test, "$$", "@@")
   422  		if !equal(items, test.items, false) {
   423  			t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
   424  		}
   425  	}
   426  }
   427  
   428  var lexPosTests = []lexTest{
   429  	{"empty", "", []item{tEOF}},
   430  	{"punctuation", "{{,@%#}}", []item{
   431  		{itemLeftDelim, 0, "{{"},
   432  		{itemChar, 2, ","},
   433  		{itemChar, 3, "@"},
   434  		{itemChar, 4, "%"},
   435  		{itemChar, 5, "#"},
   436  		{itemRightDelim, 6, "}}"},
   437  		{itemEOF, 8, ""},
   438  	}},
   439  	{"sample", "0123{{hello}}xyz", []item{
   440  		{itemText, 0, "0123"},
   441  		{itemLeftDelim, 4, "{{"},
   442  		{itemIdentifier, 6, "hello"},
   443  		{itemRightDelim, 11, "}}"},
   444  		{itemText, 13, "xyz"},
   445  		{itemEOF, 16, ""},
   446  	}},
   447  }
   448  
   449  // The other tests don't check position, to make the test cases easier to construct.
   450  // This one does.
   451  func TestPos(t *testing.T) {
   452  	for _, test := range lexPosTests {
   453  		items := collect(&test, "", "")
   454  		if !equal(items, test.items, true) {
   455  			t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
   456  			if len(items) == len(test.items) {
   457  				// Detailed print; avoid item.String() to expose the position value.
   458  				for i := range items {
   459  					if !equal(items[i:i+1], test.items[i:i+1], true) {
   460  						i1 := items[i]
   461  						i2 := test.items[i]
   462  						t.Errorf("\t#%d: got {%v %d %q} expected  {%v %d %q}", i, i1.typ, i1.pos, i1.val, i2.typ, i2.pos, i2.val)
   463  					}
   464  				}
   465  			}
   466  		}
   467  	}
   468  }
   469  
   470  // Test that an error shuts down the lexing goroutine.
   471  func TestShutdown(t *testing.T) {
   472  	// We need to duplicate template.Parse here to hold on to the lexer.
   473  	const text = "erroneous{{define}}{{else}}1234"
   474  	lexer := lex("foo", text, "{{", "}}")
   475  	_, err := New("root").parseLexer(lexer, text)
   476  	if err == nil {
   477  		t.Fatalf("expected error")
   478  	}
   479  	// The error should have drained the input. Therefore, the lexer should be shut down.
   480  	token, ok := <-lexer.items
   481  	if ok {
   482  		t.Fatalf("input was not drained; got %v", token)
   483  	}
   484  }
   485  
   486  // parseLexer is a local version of parse that lets us pass in the lexer instead of building it.
   487  // We expect an error, so the tree set and funcs list are explicitly nil.
   488  func (t *Tree) parseLexer(lex *lexer, text string) (tree *Tree, err error) {
   489  	defer t.recover(&err)
   490  	t.ParseName = t.Name
   491  	t.startParse(nil, lex)
   492  	t.parse(nil)
   493  	t.add(nil)
   494  	t.stopParse()
   495  	return t, nil
   496  }