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 }