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