github.com/cilki/sh@v2.6.4+incompatible/syntax/printer_test.go (about) 1 // Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc> 2 // See LICENSE for licensing information 3 4 package syntax 5 6 import ( 7 "bytes" 8 "fmt" 9 "io/ioutil" 10 "os" 11 "strings" 12 "testing" 13 ) 14 15 func TestPrintCompact(t *testing.T) { 16 t.Parallel() 17 parserBash := NewParser(KeepComments) 18 parserPosix := NewParser(KeepComments, Variant(LangPOSIX)) 19 parserMirBSD := NewParser(KeepComments, Variant(LangMirBSDKorn)) 20 printer := NewPrinter() 21 for i, c := range fileTests { 22 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 23 in := c.Strs[0] 24 parser := parserPosix 25 if c.Bash != nil { 26 parser = parserBash 27 } else if c.MirBSDKorn != nil { 28 parser = parserMirBSD 29 } 30 printTest(t, parser, printer, in, in) 31 }) 32 } 33 } 34 35 func strPrint(p *Printer, node Node) (string, error) { 36 var buf bytes.Buffer 37 err := p.Print(&buf, node) 38 return buf.String(), err 39 } 40 41 type printCase struct { 42 in, want string 43 } 44 45 func samePrint(s string) printCase { return printCase{in: s, want: s} } 46 47 var printTests = []printCase{ 48 samePrint(`fo○ b\år`), 49 samePrint(`"fo○ b\år"`), 50 samePrint(`'fo○ b\år'`), 51 samePrint(`${a#fo○ b\år}`), 52 samePrint(`#fo○ b\år`), 53 samePrint("<<EOF\nfo○ b\\år\nEOF"), 54 samePrint(`$'○ b\år'`), 55 samePrint("${a/b//○}"), 56 {strings.Repeat(" ", bufSize-3) + "○", "○"}, // at the end of a chunk 57 {strings.Repeat(" ", bufSize-0) + "○", "○"}, // at the start of a chunk 58 {strings.Repeat(" ", bufSize-2) + "○", "○"}, // split after 1st byte 59 {strings.Repeat(" ", bufSize-1) + "○", "○"}, // split after 2nd byte 60 // peekByte that would (but cannot) go to the next chunk 61 {strings.Repeat(" ", bufSize-2) + ">(a)", ">(a)"}, 62 // escaped newline at end of chunk 63 {"a" + strings.Repeat(" ", bufSize-2) + "\\\nb", "a \\\n\tb"}, 64 // panics if padding is only 4 (utf8.UTFMax) 65 {strings.Repeat(" ", bufSize-10) + "${a/b//○}", "${a/b//○}"}, 66 // multiple p.fill calls 67 {"a" + strings.Repeat(" ", bufSize*4) + "b", "a b"}, 68 // newline at the beginning of second chunk 69 {"a" + strings.Repeat(" ", bufSize-2) + "\nb", "a\nb"}, 70 {"foo; bar", "foo\nbar"}, 71 {"foo\n\n\nbar", "foo\n\nbar"}, 72 {"foo\n\n", "foo"}, 73 {"\n\nfoo", "foo"}, 74 {"# foo \n # bar\t", "# foo\n# bar"}, 75 samePrint("#"), 76 samePrint("#c1\\\n#c2"), 77 samePrint("#\\\n#"), 78 samePrint("foo\\\\\nbar"), 79 samePrint("a=b # inline\nbar"), 80 samePrint("a=$(b) # inline"), 81 samePrint("foo # inline\n# after"), 82 samePrint("$(a) $(b)"), 83 {"if a\nthen\n\tb\nfi", "if a; then\n\tb\nfi"}, 84 {"if a; then\nb\nelse\nfi", "if a; then\n\tb\nfi"}, 85 {"if a; then b\nelse c\nfi", "if a; then\n\tb\nelse\n\tc\nfi"}, 86 samePrint("foo >&2 <f bar"), 87 samePrint("foo >&2 bar <f"), 88 {"foo >&2 bar <f bar2", "foo >&2 bar bar2 <f"}, 89 {"foo <<EOF bar\nl1\nEOF", "foo bar <<EOF\nl1\nEOF"}, 90 samePrint("foo <<\\\\\\\\EOF\nbar\n\\\\EOF"), 91 samePrint("foo <<\"\\EOF\"\nbar\n\\EOF"), 92 samePrint("foo <<EOF && bar\nl1\nEOF"), 93 samePrint("foo <<EOF &&\nl1\nEOF\n\tbar"), 94 samePrint("foo <<EOF\nl1\nEOF\n\nfoo2"), 95 samePrint("<<EOF\nEOF"), 96 samePrint("foo <<EOF\nEOF\n\nbar"), 97 samePrint("foo <<'EOF'\nEOF\n\nbar"), 98 { 99 "{ foo; bar; }", 100 "{\n\tfoo\n\tbar\n}", 101 }, 102 { 103 "{ foo; bar; }\n#etc", 104 "{\n\tfoo\n\tbar\n}\n#etc", 105 }, 106 { 107 "{\n\tfoo; }", 108 "{\n\tfoo\n}", 109 }, 110 { 111 "{ foo\n}", 112 "{\n\tfoo\n}", 113 }, 114 { 115 "(foo\n)", 116 "(\n\tfoo\n)", 117 }, 118 { 119 "$(foo\n)", 120 "$(\n\tfoo\n)", 121 }, 122 { 123 "a\n\n\n# etc\nb", 124 "a\n\n# etc\nb", 125 }, 126 { 127 "a b\\\nc d", 128 "a bc \\\n\td", 129 }, 130 { 131 "a bb\\\ncc d", 132 "a bbcc \\\n\td", 133 }, 134 samePrint("a \\\n\tb \\\n\tc \\\n\t;"), 135 samePrint("a=1 \\\n\tb=2 \\\n\tc=3 \\\n\t;"), 136 samePrint("if a \\\n\t; then b; fi"), 137 samePrint("a 'b\nb' c"), 138 samePrint("a $'b\nb' c"), 139 { 140 "(foo; bar)", 141 "(\n\tfoo\n\tbar\n)", 142 }, 143 { 144 "{\nfoo\nbar; }", 145 "{\n\tfoo\n\tbar\n}", 146 }, 147 samePrint("\"$foo\"\n{\n\tbar\n}"), 148 { 149 "{\nbar\n# extra\n}", 150 "{\n\tbar\n\t# extra\n}", 151 }, 152 { 153 "foo\nbar # extra", 154 "foo\nbar # extra", 155 }, 156 { 157 "foo # 1\nfooo # 2\nfo # 3", 158 "foo # 1\nfooo # 2\nfo # 3", 159 }, 160 { 161 " foo # 1\n fooo # 2\n fo # 3", 162 "foo # 1\nfooo # 2\nfo # 3", 163 }, 164 { 165 "foo # 1\nfooo # 2\nfo # 3", 166 "foo # 1\nfooo # 2\nfo # 3", 167 }, 168 { 169 "foooooa\nfoo # 1\nfooo # 2\nfo # 3\nfooooo", 170 "foooooa\nfoo # 1\nfooo # 2\nfo # 3\nfooooo", 171 }, 172 { 173 "foo\nbar\nfoo # 1\nfooo # 2", 174 "foo\nbar\nfoo # 1\nfooo # 2", 175 }, 176 samePrint("foobar # 1\nfoo\nfoo # 2"), 177 samePrint("foobar # 1\n#foo\nfoo # 2"), 178 samePrint("foobar # 1\n\nfoo # 2"), 179 { 180 "foo # 2\nfoo2 bar # 1", 181 "foo # 2\nfoo2 bar # 1", 182 }, 183 { 184 "foo bar # 1\n! foo # 2", 185 "foo bar # 1\n! foo # 2", 186 }, 187 { 188 "aa #b\nc #d\ne\nf #g", 189 "aa #b\nc #d\ne\nf #g", 190 }, 191 { 192 "{ a; } #x\nbbb #y\n{ #z\n}", 193 "{ a; } #x\nbbb #y\n{ #z\n}", 194 }, 195 { 196 "foo; foooo # 1", 197 "foo\nfoooo # 1", 198 }, 199 { 200 "aaa; b #1\nc #2", 201 "aaa\nb #1\nc #2", 202 }, 203 { 204 "a #1\nbbb; c #2\nd #3", 205 "a #1\nbbb\nc #2\nd #3", 206 }, 207 samePrint("aa #c1\n{ #c2\n\tb\n}"), 208 { 209 "aa #c1\n{ b; c; } #c2", 210 "aa #c1\n{\n\tb\n\tc\n} #c2", 211 }, 212 samePrint("a #c1\n'b\ncc' #c2"), 213 { 214 "(\nbar\n# extra\n)", 215 "(\n\tbar\n\t# extra\n)", 216 }, 217 { 218 "for a in 1 2\ndo\n\t# bar\ndone", 219 "for a in 1 2; do\n\t# bar\ndone", 220 }, 221 samePrint("#before\nfoo | bar"), 222 samePrint("#before\nfoo && bar"), 223 samePrint("foo | bar # inline"), 224 samePrint("foo && bar # inline"), 225 samePrint("for a in 1 2; do\n\n\tbar\ndone"), 226 { 227 "a \\\n\t&& b", 228 "a &&\n\tb", 229 }, 230 { 231 "a \\\n\t&& b\nc", 232 "a &&\n\tb\nc", 233 }, 234 { 235 "{\n(a \\\n&& b)\nc\n}", 236 "{\n\t(a &&\n\t\tb)\n\tc\n}", 237 }, 238 { 239 "a && b \\\n&& c", 240 "a && b &&\n\tc", 241 }, 242 { 243 "a \\\n&& $(b) && c \\\n&& d", 244 "a &&\n\t$(b) && c &&\n\td", 245 }, 246 { 247 "a \\\n&& b\nc \\\n&& d", 248 "a &&\n\tb\nc &&\n\td", 249 }, 250 { 251 "a \\\n&&\n#c\nb", 252 "a &&\n\t#c\n\tb", 253 }, 254 { 255 "a | {\nb \\\n| c\n}", 256 "a | {\n\tb |\n\t\tc\n}", 257 }, 258 { 259 "a \\\n\t&& if foo; then\nbar\nfi", 260 "a &&\n\tif foo; then\n\t\tbar\n\tfi", 261 }, 262 { 263 "if\nfoo\nthen\nbar\nfi", 264 "if\n\tfoo\nthen\n\tbar\nfi", 265 }, 266 { 267 "if foo \\\nbar\nthen\nbar\nfi", 268 "if foo \\\n\tbar; then\n\tbar\nfi", 269 }, 270 { 271 "if foo \\\n&& bar\nthen\nbar\nfi", 272 "if foo &&\n\tbar; then\n\tbar\nfi", 273 }, 274 { 275 "a |\nb |\nc", 276 "a |\n\tb |\n\tc", 277 }, 278 samePrint("a |\n\tb | c |\n\td"), 279 samePrint("a | b |\n\tc |\n\td"), 280 { 281 "foo |\n# misplaced\nbar", 282 "foo |\n\t# misplaced\n\tbar", 283 }, 284 samePrint("{\n\tfoo\n\t#a\n\tbar\n} | etc"), 285 { 286 "foo &&\n#a1\n#a2\n$(bar)", 287 "foo &&\n\t#a1\n\t#a2\n\t$(bar)", 288 }, 289 { 290 "{\n\tfoo\n\t#a\n} |\n# misplaced\nbar", 291 "{\n\tfoo\n\t#a\n} |\n\t# misplaced\n\tbar", 292 }, 293 samePrint("foo | bar\n#after"), 294 { 295 "a |\nb | #c2\nc", 296 "a |\n\tb | #c2\n\tc", 297 }, 298 { 299 "{\nfoo &&\n#a1\n#a2\n$(bar)\n}", 300 "{\n\tfoo &&\n\t\t#a1\n\t\t#a2\n\t\t$(bar)\n}", 301 }, 302 { 303 "foo | while read l; do\nbar\ndone", 304 "foo | while read l; do\n\tbar\ndone", 305 }, 306 samePrint("while x; do\n\t#comment\ndone"), 307 { 308 "while x\ndo\n\ty\ndone", 309 "while x; do\n\ty\ndone", 310 }, 311 samePrint("\"\\\nfoo\""), 312 samePrint("'\\\nfoo'"), 313 samePrint("\"foo\\\n bar\""), 314 samePrint("'foo\\\n bar'"), 315 { 316 "foo \\\n>bar\netc", 317 "foo \\\n\t>bar\netc", 318 }, 319 { 320 "foo \\\nfoo2 \\\n>bar", 321 "foo \\\n\tfoo2 \\\n\t>bar", 322 }, 323 samePrint("> >(foo)"), 324 samePrint("x > >(foo) y"), 325 samePrint("a | () |\n\tb"), 326 samePrint("a | (\n\tx\n\ty\n) |\n\tb"), 327 samePrint("a |\n\tif foo; then\n\t\tbar\n\tfi |\n\tb"), 328 samePrint("a | if foo; then\n\tbar\nfi"), 329 samePrint("a | b | if foo; then\n\tbar\nfi"), 330 { 331 "case $i in\n1)\nfoo\n;;\nesac", 332 "case $i in\n1)\n\tfoo\n\t;;\nesac", 333 }, 334 { 335 "case $i in\n1)\nfoo\nesac", 336 "case $i in\n1)\n\tfoo\n\t;;\nesac", 337 }, 338 { 339 "case $i in\n1) foo\nesac", 340 "case $i in\n1) foo ;;\nesac", 341 }, 342 { 343 "case $i in\n1) foo; bar\nesac", 344 "case $i in\n1)\n\tfoo\n\tbar\n\t;;\nesac", 345 }, 346 { 347 "case $i in\n1) foo; bar;;\nesac", 348 "case $i in\n1)\n\tfoo\n\tbar\n\t;;\nesac", 349 }, 350 { 351 "case $i in\n1)\n#foo \t\n;;\nesac", 352 "case $i in\n1)\n\t#foo\n\t;;\nesac", 353 }, 354 samePrint("case $i in\n1)\n\ta\n\t#b\n\t;;\nesac"), 355 samePrint("case $i in\n1) foo() { bar; } ;;\nesac"), 356 samePrint("case $i in\n1) ;; #foo\nesac"), 357 samePrint("case $i in\n#foo\nesac"), 358 samePrint("case $i in\n#before\n1) ;;\nesac"), 359 samePrint("case $i in\n#bef\n1) ;; #inl\nesac"), 360 samePrint("case $i in\n1) ;; #inl1\n2) ;; #inl2\nesac"), 361 samePrint("case $i in\n#bef\n1) #inl\n\tfoo\n\t;;\nesac"), 362 samePrint("case $i in\n1) #inl\n\t;;\nesac"), 363 samePrint("case $i in\n1) a \\\n\tb ;;\nesac"), 364 samePrint("case $i in\n1 | 2 | \\\n\t3 | 4) a b ;;\nesac"), 365 samePrint("case $i in\n1 | 2 | \\\n\t3 | 4)\n\ta b\n\t;;\nesac"), 366 samePrint("case $i in\nx) ;;\ny) for n in 1; do echo $n; done ;;\nesac"), 367 { 368 "a=(\nb\nc\n) b=c", 369 "a=(\n\tb\n\tc\n) b=c", 370 }, 371 samePrint("a=(\n\t#before\n\tb #inline\n)"), 372 samePrint("a=(\n\tb #foo\n\tc #bar\n)"), 373 samePrint("a=(\n\tb\n\n\t#foo\n\t#bar\n\tc\n)"), 374 samePrint("a=(\n\t#foo\n\t#bar\n\tc\n)"), 375 samePrint("a=(\n\t#lone\n)"), 376 samePrint("a=(\n\n)"), 377 samePrint("foo <<EOF | $(bar)\n3\nEOF"), 378 { 379 "a <<EOF\n$(\n\tb\n\tc)\nEOF", 380 "a <<EOF\n$(\n\tb\n\tc\n)\nEOF", 381 }, 382 { 383 "<(<<EOF\nbody\nEOF\n)", 384 "<(\n\t<<EOF\nbody\nEOF\n)", 385 }, 386 { 387 "( (foo) )\n$( (foo) )\n<( (foo) )", 388 "( (foo))\n$( (foo))\n<((foo))", 389 }, 390 samePrint("\"foo\n$(bar)\""), 391 samePrint("\"foo\\\n$(bar)\""), 392 samePrint("((foo++)) || bar"), 393 { 394 "a=b \\\nc=d \\\nfoo", 395 "a=b \\\n\tc=d \\\n\tfoo", 396 }, 397 { 398 "a=b \\\nc=d \\\nfoo \\\nbar", 399 "a=b \\\n\tc=d \\\n\tfoo \\\n\tbar", 400 }, 401 samePrint("a $(x) \\\n\tb"), 402 samePrint("\"foo\nbar\"\netc"), 403 samePrint("\"foo\nbar\nbar2\"\netc"), 404 samePrint("a=\"$b\n\"\nd=e"), 405 samePrint("\"\n\"\n\nfoo"), 406 samePrint("$\"\n\"\n\nfoo"), 407 samePrint("'\n'\n\nfoo"), 408 samePrint("$'\n'\n\nfoo"), 409 samePrint("foo <<EOF\na\nb\nc\nd\nEOF\n{\n\tbar\n}"), 410 samePrint("foo bar # one\nif a; then\n\tb\nfi # two"), 411 { 412 "# foo\n\n\nbar", 413 "# foo\n\nbar", 414 }, 415 { 416 "# foo\n\n\nbar\nbaz", 417 "# foo\n\nbar\nbaz", 418 }, 419 samePrint("#foo\n#\n#bar"), 420 { 421 "(0 #\n0)#\n0", 422 "(\n\t0 #\n\t0\n) #\n0", 423 }, 424 samePrint("a | #c1\n\t(\n\t\tb\n\t)"), 425 samePrint("a | #c1\n\t{\n\t\tb\n\t}"), 426 samePrint("a | #c1\n\tif b; then\n\t\tc\n\tfi"), 427 samePrint("a | #c1\n\t#c2\n\t#c3\n\tb"), 428 samePrint("a && #c1\n\t(\n\t\tb\n\t)"), 429 samePrint("f() body # comment"), 430 samePrint("f <<EOF\nbody\nEOF"), 431 samePrint("f <<EOF\nEOF"), 432 samePrint("f <<-EOF\n\tbody\nEOF"), 433 { 434 "f <<-EOF\nbody\nEOF", 435 "f <<-EOF\n\tbody\nEOF", 436 }, 437 samePrint("f <<-EOF\nEOF"), 438 samePrint("{\n\tf <<EOF\nEOF\n}"), 439 samePrint("{\n\tf <<-EOF\n\t\tbody\n\tEOF\n}"), 440 samePrint("{\n\tf <<-EOF\n\t\tbody\n\tEOF\n\tf2\n}"), 441 samePrint("f <<-EOF\n\t{\n\t\tnicely indented\n\t}\nEOF"), 442 samePrint("f <<-EOF\n\t{\n\t\tnicely indented\n\t}\nEOF"), 443 { 444 "f <<-EOF\n\t{\nbadly indented\n\t}\nEOF", 445 "f <<-EOF\n\t{\n\tbadly indented\n\t}\nEOF", 446 }, 447 { 448 "f <<-EOF\n\t\t{\n\t\t\ttoo indented\n\t\t}\nEOF", 449 "f <<-EOF\n\t{\n\t\ttoo indented\n\t}\nEOF", 450 }, 451 { 452 "f <<-EOF\n{\n\ttoo little indented\n}\nEOF", 453 "f <<-EOF\n\t{\n\t\ttoo little indented\n\t}\nEOF", 454 }, 455 samePrint("f <<EOF\nEOF\n# comment"), 456 samePrint("f <<EOF\nEOF\n# comment\nbar"), 457 samePrint("f <<EOF # inline\n$(\n\t# inside\n)\nEOF\n# outside\nbar"), 458 { 459 "if foo # inline\nthen\n\tbar\nfi", 460 "if foo; then # inline\n\tbar\nfi", 461 }, 462 samePrint("for i; do echo $i; done"), 463 samePrint("for i in; do echo $i; done"), 464 { 465 "for foo in a b # inline\ndo\n\tbar\ndone", 466 "for foo in a b; do # inline\n\tbar\ndone", 467 }, 468 { 469 "if x # inline\nthen bar; fi", 470 "if x; then # inline\n\tbar\nfi", 471 }, 472 { 473 "for i in a b # inline\ndo bar; done", 474 "for i in a b; do # inline\n\tbar\ndone", 475 }, 476 { 477 "for i #a\n\tin 1; do #b\ndone", 478 "for i in \\\n\t1; do #a\n\t#b\ndone", 479 }, 480 { 481 "foo() # inline\n{\n\tbar\n}", 482 "foo() { # inline\n\tbar\n}", 483 }, 484 samePrint("if foo; then\n\tbar\n\t# comment\nfi"), 485 samePrint("if foo; then\n\tbar\n# else commented out\nfi"), 486 samePrint("if foo; then\n\tx\nelse\n\tbar\n\t# comment\nfi"), 487 samePrint("if foo; then\n\tx\n#comment\nelse\n\ty\nfi"), 488 samePrint("if foo; then\n\tx\n\t#comment\nelse\n\ty\nfi"), 489 { 490 "if foo; then\n\tx\n#a\n\t#b\n\t#c\nelse\n\ty\nfi", 491 "if foo; then\n\tx\n\t#a\n\t#b\n\t#c\nelse\n\ty\nfi", 492 }, 493 samePrint("if foo; then\n\tx\nelse #comment\n\ty\nfi"), 494 samePrint("if foo; then\n\tx\n#comment\nelif bar; then\n\ty\nfi"), 495 samePrint("if foo; then\n\tx\n\t#comment\nelif bar; then\n\ty\nfi"), 496 samePrint("case i in\nx)\n\ta\n\t;;\n#comment\ny) ;;\nesac"), 497 samePrint("case i in\nx)\n\ta\n\t;;\n\t#comment\ny) ;;\nesac"), 498 { 499 "case i in\nx)\n\ta\n\t;;\n\t#a\n#b\n\t#c\ny) ;;\nesac", 500 "case i in\nx)\n\ta\n\t;;\n\t#a\n\t#b\n\t#c\ny) ;;\nesac", 501 }, 502 } 503 504 func TestPrintWeirdFormat(t *testing.T) { 505 t.Parallel() 506 parser := NewParser(KeepComments) 507 printer := NewPrinter() 508 for i, tc := range printTests { 509 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 510 printTest(t, parser, printer, tc.in, tc.want) 511 }) 512 t.Run(fmt.Sprintf("%03d-nl", i), func(t *testing.T) { 513 printTest(t, parser, printer, "\n"+tc.in+"\n", tc.want) 514 }) 515 t.Run(fmt.Sprintf("%03d-redo", i), func(t *testing.T) { 516 printTest(t, parser, printer, tc.want, tc.want) 517 }) 518 } 519 } 520 521 func parsePath(tb testing.TB, path string) *File { 522 f, err := os.Open(path) 523 if err != nil { 524 tb.Fatal(err) 525 } 526 defer f.Close() 527 prog, err := NewParser(KeepComments).Parse(f, "") 528 if err != nil { 529 tb.Fatal(err) 530 } 531 return prog 532 } 533 534 const canonicalPath = "canonical.sh" 535 536 func TestPrintMultiline(t *testing.T) { 537 t.Parallel() 538 prog := parsePath(t, canonicalPath) 539 got, err := strPrint(NewPrinter(), prog) 540 if err != nil { 541 t.Fatal(err) 542 } 543 544 wantBs, err := ioutil.ReadFile(canonicalPath) 545 if err != nil { 546 t.Fatal(err) 547 } 548 549 // If we're on Windows and it was set up to automatically replace LF 550 // with CRLF, that might make this test fail. Just ignore \r characters. 551 want := strings.Replace(string(wantBs), "\r", "", -1) 552 got = strings.Replace(got, "\r", "", -1) 553 if got != want { 554 t.Fatalf("Print mismatch in canonical.sh") 555 } 556 } 557 558 func BenchmarkPrint(b *testing.B) { 559 prog := parsePath(b, canonicalPath) 560 printer := NewPrinter() 561 for i := 0; i < b.N; i++ { 562 if err := printer.Print(ioutil.Discard, prog); err != nil { 563 b.Fatal(err) 564 } 565 } 566 } 567 568 func TestPrintSpaces(t *testing.T) { 569 t.Parallel() 570 var spaceFormats = [...]struct { 571 spaces uint 572 in, want string 573 }{ 574 { 575 0, 576 "{\nfoo \\\nbar\n}", 577 "{\n\tfoo \\\n\t\tbar\n}", 578 }, 579 { 580 2, 581 "{\nfoo \\\nbar\n}", 582 "{\n foo \\\n bar\n}", 583 }, 584 { 585 4, 586 "{\nfoo \\\nbar\n}", 587 "{\n foo \\\n bar\n}", 588 }, 589 } 590 591 parser := NewParser(KeepComments) 592 for i, tc := range spaceFormats { 593 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 594 printer := NewPrinter(Indent(tc.spaces)) 595 printTest(t, parser, printer, tc.in, tc.want) 596 }) 597 } 598 } 599 600 var errBadWriter = fmt.Errorf("write: expected error") 601 602 type badWriter struct{} 603 604 func (b badWriter) Write(p []byte) (int, error) { return 0, errBadWriter } 605 606 func TestWriteErr(t *testing.T) { 607 t.Parallel() 608 _ = (*byteCounter)(nil).Flush() 609 f := &File{StmtList: StmtList{Stmts: []*Stmt{ 610 { 611 Redirs: []*Redirect{{ 612 Op: RdrOut, 613 Word: litWord("foo"), 614 }}, 615 Cmd: &Subshell{}, 616 }, 617 }}} 618 err := NewPrinter().Print(badWriter{}, f) 619 if err == nil { 620 t.Fatalf("Expected error with bad writer") 621 } 622 if err != errBadWriter { 623 t.Fatalf("Error mismatch with bad writer:\nwant: %v\ngot: %v", 624 errBadWriter, err) 625 } 626 } 627 628 func TestPrintBinaryNextLine(t *testing.T) { 629 t.Parallel() 630 var tests = [...]printCase{ 631 { 632 "foo <<EOF &&\nl1\nEOF\nbar", 633 "foo <<EOF && bar\nl1\nEOF", 634 }, 635 samePrint("a \\\n\t&& b"), 636 samePrint("a \\\n\t&& b\nc"), 637 { 638 "{\n(a \\\n&& b)\nc\n}", 639 "{\n\t(a \\\n\t\t&& b)\n\tc\n}", 640 }, 641 { 642 "a && b \\\n&& c", 643 "a && b \\\n\t&& c", 644 }, 645 { 646 "a \\\n&& $(b) && c \\\n&& d", 647 "a \\\n\t&& $(b) && c \\\n\t&& d", 648 }, 649 { 650 "a \\\n&& b\nc \\\n&& d", 651 "a \\\n\t&& b\nc \\\n\t&& d", 652 }, 653 { 654 "a | {\nb \\\n| c\n}", 655 "a | {\n\tb \\\n\t\t| c\n}", 656 }, 657 { 658 "a \\\n\t&& if foo; then\nbar\nfi", 659 "a \\\n\t&& if foo; then\n\t\tbar\n\tfi", 660 }, 661 { 662 "if foo \\\n&& bar\nthen\nbar\nfi", 663 "if foo \\\n\t&& bar; then\n\tbar\nfi", 664 }, 665 { 666 "a |\nb |\nc", 667 "a \\\n\t| b \\\n\t| c", 668 }, 669 { 670 "foo |\n# misplaced\nbar", 671 "foo \\\n\t|\n\t# misplaced\n\tbar", 672 }, 673 samePrint("{\n\tfoo\n\t#a\n\tbar\n} | etc"), 674 { 675 "foo &&\n#a1\n#a2\n$(bar)", 676 "foo \\\n\t&&\n\t#a1\n\t#a2\n\t$(bar)", 677 }, 678 { 679 "{\n\tfoo\n\t#a\n} |\n# misplaced\nbar", 680 "{\n\tfoo\n\t#a\n} \\\n\t|\n\t# misplaced\n\tbar", 681 }, 682 samePrint("foo | bar\n#after"), 683 { 684 "a |\nb | #c2\nc", 685 "a \\\n\t| b \\\n\t|\n\t#c2\n\tc", 686 }, 687 } 688 parser := NewParser(KeepComments) 689 printer := NewPrinter(BinaryNextLine) 690 for i, tc := range tests { 691 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 692 printTest(t, parser, printer, tc.in, tc.want) 693 }) 694 } 695 } 696 697 func TestPrintSwitchCaseIndent(t *testing.T) { 698 t.Parallel() 699 var tests = [...]printCase{ 700 { 701 "case $i in\n1)\nfoo\n;;\nesac", 702 "case $i in\n\t1)\n\t\tfoo\n\t\t;;\nesac", 703 }, 704 { 705 "case $i in\n1)\na\n;;\n2)\nb\n;;\nesac", 706 "case $i in\n\t1)\n\t\ta\n\t\t;;\n\t2)\n\t\tb\n\t\t;;\nesac", 707 }, 708 samePrint("case $i in\n\t#foo\nesac"), 709 } 710 parser := NewParser(KeepComments) 711 printer := NewPrinter(SwitchCaseIndent) 712 for i, tc := range tests { 713 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 714 printTest(t, parser, printer, tc.in, tc.want) 715 }) 716 } 717 } 718 719 func TestPrintSpaceRedirects(t *testing.T) { 720 t.Parallel() 721 var tests = [...]printCase{ 722 samePrint("echo foo bar > f"), 723 samePrint("echo > f foo bar"), 724 samePrint("echo >(cmd)"), 725 samePrint("echo > >(cmd)"), 726 samePrint("<< EOF\nfoo\nEOF"), 727 samePrint("echo 2> f"), 728 samePrint("echo foo bar >&1"), 729 samePrint("echo 2<&1 foo bar"), 730 } 731 parser := NewParser(KeepComments) 732 printer := NewPrinter(SpaceRedirects) 733 for i, tc := range tests { 734 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 735 printTest(t, parser, printer, tc.in, tc.want) 736 }) 737 } 738 } 739 740 func TestPrintKeepPadding(t *testing.T) { 741 t.Parallel() 742 var tests = [...]printCase{ 743 samePrint("echo foo bar"), 744 samePrint("echo foo bar"), 745 samePrint("a=b c=d bar"), 746 samePrint("echo foo >bar"), 747 samePrint("echo foo 2>bar"), 748 samePrint("{ foo; }"), 749 samePrint("a() { foo; }"), 750 samePrint("a && b"), 751 samePrint("a | b"), 752 samePrint("a | b"), 753 samePrint("{ a b c; }"), 754 samePrint("foo # x\nbaaar # y"), 755 samePrint("{ { a; }; }"), 756 samePrint("{ a; }"), 757 samePrint("( a )"), 758 samePrint("'foo\nbar' # x"), 759 {"\tfoo", "foo"}, 760 {" if foo; then bar; fi", "if foo; then bar; fi"}, 761 } 762 parser := NewParser(KeepComments) 763 printer := NewPrinter(KeepPadding) 764 for i, tc := range tests { 765 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 766 // ensure that Reset does properly reset colCounter 767 printer.WriteByte('x') 768 printer.Reset(nil) 769 printTest(t, parser, printer, tc.in, tc.want) 770 }) 771 } 772 } 773 774 func TestPrintMinify(t *testing.T) { 775 t.Parallel() 776 var tests = [...]printCase{ 777 samePrint("echo foo bar $a $(b)"), 778 { 779 "#comment", 780 "", 781 }, 782 { 783 "foo #comment", 784 "foo", 785 }, 786 { 787 "foo\n\nbar", 788 "foo\nbar", 789 }, 790 { 791 "foo &", 792 "foo&", 793 }, 794 samePrint("foo >bar 2>baz <etc"), 795 { 796 "{\n\tfoo\n}", 797 "{\nfoo\n}", 798 }, 799 { 800 "(\n\ta\n)\n(\n\tb\n\tc\n)", 801 "(a)\n(b\nc)", 802 }, 803 { 804 "$(\n\ta\n)\n$(\n\tb\n\tc\n)", 805 "$(a)\n$(b\nc)", 806 }, 807 { 808 "f() { x; }", 809 "f(){ x;}", 810 }, 811 { 812 "((1 + 2))", 813 "((1+2))", 814 }, 815 { 816 "echo $a ${b} ${c}-d ${e}f ${g}_h", 817 "echo $a $b $c-d ${e}f ${g}_h", 818 }, 819 { 820 "echo ${0} ${3} ${10} ${22}", 821 "echo $0 $3 ${10} ${22}", 822 }, 823 { 824 "case $a in\nx) c ;;\ny | z)\n\td\n\t;;\nesac", 825 "case $a in\nx)c;;\ny|z)d\nesac", 826 }, 827 { 828 "a && b | c", 829 "a&&b|c", 830 }, 831 { 832 "a &&\n\tb |\n\tc", 833 "a&&b|c", 834 }, 835 { 836 "${0/${a}\\\n}", 837 "${0/$a/}", 838 }, 839 } 840 parser := NewParser(KeepComments) 841 printer := NewPrinter(Minify) 842 for i, tc := range tests { 843 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 844 printTest(t, parser, printer, tc.in, tc.want) 845 }) 846 } 847 } 848 849 func TestPrintMinifyNotBroken(t *testing.T) { 850 t.Parallel() 851 parserBash := NewParser(KeepComments) 852 parserPosix := NewParser(KeepComments, Variant(LangPOSIX)) 853 parserMirBSD := NewParser(KeepComments, Variant(LangMirBSDKorn)) 854 printer := NewPrinter(Minify) 855 for i, tc := range fileTests { 856 t.Run(fmt.Sprintf("File%03d", i), func(t *testing.T) { 857 parser := parserPosix 858 if tc.Bash != nil { 859 parser = parserBash 860 } else if tc.MirBSDKorn != nil { 861 parser = parserMirBSD 862 } 863 in := tc.Strs[0] 864 prog, err := parser.Parse(strings.NewReader(in), "") 865 if err != nil { 866 t.Fatal(err) 867 } 868 got, err := strPrint(printer, prog) 869 if err != nil { 870 t.Fatal(err) 871 } 872 _, err = parser.Parse(strings.NewReader(got), "") 873 if err != nil { 874 t.Fatalf("minified program was broken: %v\n%s", err, got) 875 } 876 }) 877 } 878 for i, tc := range printTests { 879 t.Run(fmt.Sprintf("Print%03d", i), func(t *testing.T) { 880 prog, err := parserBash.Parse(strings.NewReader(tc.in), "") 881 if err != nil { 882 t.Fatal(err) 883 } 884 got, err := strPrint(printer, prog) 885 if err != nil { 886 t.Fatal(err) 887 } 888 _, err = parserBash.Parse(strings.NewReader(got), "") 889 if err != nil { 890 t.Fatalf("minified program was broken: %v\n%s", err, got) 891 } 892 }) 893 } 894 } 895 896 func printTest(t *testing.T, parser *Parser, printer *Printer, in, want string) { 897 prog, err := parser.Parse(strings.NewReader(in), "") 898 if err != nil { 899 t.Fatal(err) 900 } 901 wantNewl := want + "\n" 902 got, err := strPrint(printer, prog) 903 if err != nil { 904 t.Fatal(err) 905 } 906 if got != wantNewl { 907 t.Fatalf("Print mismatch:\nin:\n%s\nwant:\n%sgot:\n%s", 908 in, wantNewl, got) 909 } 910 _, err = parser.Parse(strings.NewReader(want), "") 911 if err != nil { 912 t.Fatalf("Result is not valid shell:\n%s", want) 913 } 914 } 915 916 func TestPrintNodeTypes(t *testing.T) { 917 t.Parallel() 918 919 multiline, err := NewParser().Parse(strings.NewReader(` 920 echo foo 921 `), "") 922 if err != nil { 923 t.Fatal(err) 924 } 925 926 var tests = [...]struct { 927 in Node 928 want string 929 wantErr bool 930 }{ 931 { 932 in: &File{StmtList: litStmts("foo")}, 933 want: "foo\n", 934 }, 935 { 936 in: &File{StmtList: litStmts("foo", "bar")}, 937 want: "foo\nbar\n", 938 }, 939 { 940 in: litStmt("foo", "bar"), 941 want: "foo bar", 942 }, 943 { 944 in: litCall("foo", "bar"), 945 want: "foo bar", 946 }, 947 { 948 in: litWord("foo"), 949 want: "foo", 950 }, 951 { 952 in: lit("foo"), 953 want: "foo", 954 }, 955 { 956 in: sglQuoted("foo"), 957 want: "'foo'", 958 }, 959 { 960 in: &Comment{}, 961 wantErr: true, 962 }, 963 { 964 in: multiline.Stmts[0], 965 want: "echo foo", 966 }, 967 { 968 in: multiline.Stmts[0].Cmd, 969 want: "echo foo", 970 }, 971 { 972 in: multiline.Stmts[0].Cmd.(*CallExpr).Args[0], 973 want: "echo", 974 }, 975 { 976 in: multiline.Stmts[0].Cmd.(*CallExpr).Args[0].Parts[0], 977 want: "echo", 978 }, 979 } 980 printer := NewPrinter() 981 for i, tc := range tests { 982 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 983 got, err := strPrint(printer, tc.in) 984 if err == nil && tc.wantErr { 985 t.Fatalf("wanted an error but found none") 986 } else if err != nil && !tc.wantErr { 987 t.Fatalf("didn't want an error but got %v", err) 988 } 989 if got != tc.want { 990 t.Fatalf("Print mismatch:\nwant:\n%s\ngot:\n%s", 991 tc.want, got) 992 } 993 }) 994 } 995 } 996 997 func TestPrintManyStmts(t *testing.T) { 998 t.Parallel() 999 var tests = [...]struct { 1000 in, want string 1001 }{ 1002 {"foo; bar", "foo\nbar\n"}, 1003 {"foo\nbar", "foo\nbar\n"}, 1004 {"\n\nfoo\nbar\n\n", "foo\nbar\n"}, 1005 {"foo\nbar <<EOF\nbody\nEOF\n", "foo\nbar <<EOF\nbody\nEOF\n"}, 1006 {"foo\nbar # inline", "foo\nbar # inline\n"}, 1007 {"# comment before\nfoo bar", "# comment before\nfoo bar\n"}, 1008 } 1009 parser := NewParser(KeepComments) 1010 printer := NewPrinter() 1011 for i, tc := range tests { 1012 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 1013 f, err := parser.Parse(strings.NewReader(tc.in), "") 1014 if err != nil { 1015 t.Fatal(err) 1016 } 1017 var buf bytes.Buffer 1018 for _, stmt := range f.Stmts { 1019 printer.Print(&buf, stmt) 1020 buf.WriteByte('\n') 1021 } 1022 got := buf.String() 1023 if got != tc.want { 1024 t.Fatalf("Print mismatch:\nwant:\n%s\ngot:\n%s", 1025 tc.want, got) 1026 } 1027 }) 1028 } 1029 }