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