github.com/sean-/go@v0.0.0-20151219100004-97f854cd7bb6/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 }