github.com/theclapp/sh@v2.6.4+incompatible/interp/interp_test.go (about) 1 // Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc> 2 // See LICENSE for licensing information 3 4 package interp 5 6 import ( 7 "bytes" 8 "context" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "regexp" 15 "runtime" 16 "strings" 17 "sync" 18 "testing" 19 "time" 20 21 "mvdan.cc/sh/expand" 22 "mvdan.cc/sh/syntax" 23 ) 24 25 func BenchmarkRun(b *testing.B) { 26 src := ` 27 echo a b c d 28 echo ./$foo/etc $(echo foo bar) 29 foo="bar" 30 x=y : 31 fn() { 32 local a=b 33 for i in 1 2 3; do 34 echo $i | cat 35 done 36 } 37 [[ $foo == bar ]] && fn 38 echo a{b,c}d *.go 39 let i=(2 + 3) 40 ` 41 file, err := syntax.NewParser().Parse(strings.NewReader(src), "") 42 if err != nil { 43 b.Fatal(err) 44 } 45 r, _ := New() 46 ctx := context.Background() 47 for i := 0; i < b.N; i++ { 48 r.Reset() 49 if err := r.Run(ctx, file); err != nil { 50 b.Fatal(err) 51 } 52 } 53 } 54 55 var hasBash44 bool 56 57 func TestMain(m *testing.M) { 58 os.Setenv("LANGUAGE", "en_US.UTF8") 59 os.Setenv("LC_ALL", "en_US.UTF8") 60 os.Unsetenv("CDPATH") 61 hasBash44 = checkBash() 62 os.Setenv("INTERP_GLOBAL", "value") 63 os.Setenv("MULTILINE_INTERP_GLOBAL", "\nwith\nnewlines\n\n") 64 65 // Double check that env vars on Windows are case insensitive. 66 if runtime.GOOS == "windows" { 67 os.Setenv("mixedCase_INTERP_GLOBAL", "value") 68 } else { 69 os.Setenv("MIXEDCASE_INTERP_GLOBAL", "value") 70 } 71 72 for _, s := range []string{"a", "b", "c", "d", "foo", "bar"} { 73 os.Unsetenv(s) 74 } 75 exit := m.Run() 76 os.Exit(exit) 77 } 78 79 func checkBash() bool { 80 out, err := exec.Command("bash", "-c", "echo -n $BASH_VERSION").Output() 81 if err != nil { 82 return false 83 } 84 return strings.HasPrefix(string(out), "4.4") 85 } 86 87 // concBuffer wraps a bytes.Buffer in a mutex so that concurrent writes 88 // to it don't upset the race detector. 89 type concBuffer struct { 90 buf bytes.Buffer 91 sync.Mutex 92 } 93 94 func (c *concBuffer) Write(p []byte) (int, error) { 95 c.Lock() 96 n, err := c.buf.Write(p) 97 c.Unlock() 98 return n, err 99 } 100 101 func (c *concBuffer) WriteString(s string) (int, error) { 102 c.Lock() 103 n, err := c.buf.WriteString(s) 104 c.Unlock() 105 return n, err 106 } 107 108 func (c *concBuffer) String() string { 109 c.Lock() 110 s := c.buf.String() 111 c.Unlock() 112 return s 113 } 114 115 func (c *concBuffer) Reset() { 116 c.Lock() 117 c.buf.Reset() 118 c.Unlock() 119 } 120 121 var fileCases = []struct { 122 in, want string 123 }{ 124 // no-op programs 125 {"", ""}, 126 {"true", ""}, 127 {":", ""}, 128 {"exit", ""}, 129 {"exit 0", ""}, 130 {"{ :; }", ""}, 131 {"(:)", ""}, 132 133 // exit status codes 134 {"exit 1", "exit status 1"}, 135 {"exit -1", "exit status 255"}, 136 {"exit 300", "exit status 44"}, 137 {"false", "exit status 1"}, 138 {"false foo", "exit status 1"}, 139 {"! false", ""}, 140 {"true foo", ""}, 141 {": foo", ""}, 142 {"! true", "exit status 1"}, 143 {"false; true", ""}, 144 {"false; exit", "exit status 1"}, 145 {"exit; echo foo", ""}, 146 {"exit 0; echo foo", ""}, 147 {"printf", "usage: printf format [arguments]\nexit status 2 #JUSTERR"}, 148 {"break", "break is only useful in a loop #JUSTERR"}, 149 {"continue", "continue is only useful in a loop #JUSTERR"}, 150 {"cd a b", "usage: cd [dir]\nexit status 2 #JUSTERR"}, 151 {"shift a", "usage: shift [n]\nexit status 2 #JUSTERR"}, 152 { 153 "shouldnotexist", 154 "\"shouldnotexist\": executable file not found in $PATH\nexit status 127 #JUSTERR", 155 }, 156 { 157 "for i in 1; do continue a; done", 158 "usage: continue [n]\nexit status 2 #JUSTERR", 159 }, 160 { 161 "for i in 1; do break a; done", 162 "usage: break [n]\nexit status 2 #JUSTERR", 163 }, 164 165 // we don't need to follow bash error strings 166 {"exit a", "invalid exit status code: \"a\"\nexit status 2 #JUSTERR"}, 167 {"exit 1 2", "exit cannot take multiple arguments\nexit status 1 #JUSTERR"}, 168 169 // echo 170 {"echo", "\n"}, 171 {"echo a b c", "a b c\n"}, 172 {"echo -n foo", "foo"}, 173 {`echo -e '\t'`, "\t\n"}, 174 {`echo -E '\t'`, "\\t\n"}, 175 {"echo -x foo", "-x foo\n"}, 176 {"echo -e -x -e foo", "-x -e foo\n"}, 177 178 // printf 179 {"printf foo", "foo"}, 180 {"printf %%", "%"}, 181 {"printf %", "missing format char\nexit status 1 #JUSTERR"}, 182 {"printf %; echo foo", "missing format char\nfoo\n #IGNORE"}, 183 {"printf %1", "missing format char\nexit status 1 #JUSTERR"}, 184 {"printf %+", "missing format char\nexit status 1 #JUSTERR"}, 185 {"printf %B foo", "invalid format char: B\nexit status 1 #JUSTERR"}, 186 {"printf %12-s foo", "invalid format char: -\nexit status 1 #JUSTERR"}, 187 {"printf ' %s \n' bar", " bar \n"}, 188 {"printf '\\A'", "\\A"}, 189 {"printf %s foo", "foo"}, 190 {"printf %s", ""}, 191 {"printf %d,%i 3 4", "3,4"}, 192 {"printf %d", "0"}, 193 {"printf %d,%d 010 0x10", "8,16"}, 194 {"printf %i,%u -3 -3", "-3,18446744073709551613"}, 195 {"printf %o -3", "1777777777777777777775"}, 196 {"printf %x -3", "fffffffffffffffd"}, 197 {"printf %c,%c,%c foo àa", "f,\xc3,\x00"}, // TODO: use a rune? 198 {"printf %3s a", " a"}, 199 {"printf %3i 1", " 1"}, 200 {"printf %+i%+d 1 -3", "+1-3"}, 201 {"printf %-5x 10", "a "}, 202 {"printf %02x 1", "01"}, 203 {"printf 'a% 5s' a", "a a"}, 204 {"printf 'nofmt' 1 2 3", "nofmt"}, 205 {"printf '%d_' 1 2 3", "1_2_3_"}, 206 {"printf '%02d %02d\n' 1 2 3", "01 02\n03 00\n"}, 207 208 // words and quotes 209 {"echo foo ", "foo\n"}, 210 {"echo ' foo '", " foo \n"}, 211 {`echo " foo "`, " foo \n"}, 212 {`echo a'b'c"d"e`, "abcde\n"}, 213 {`a=" b c "; echo $a`, "b c\n"}, 214 {`a=" b c "; echo "$a"`, " b c \n"}, 215 {`echo "$(echo ' b c ')"`, " b c \n"}, 216 {"echo ''", "\n"}, 217 {`$(echo)`, ""}, 218 {`echo -n '\\'`, `\\`}, 219 {`echo -n "\\"`, `\`}, 220 {`set -- a b c; x="$@"; echo "$x"`, "a b c\n"}, 221 {`set -- b c; echo a"$@"d`, "ab cd\n"}, 222 {`echo $1 $3; set -- a b c; echo $1 $3`, "\na c\n"}, 223 {`[[ $0 == "bash" || $0 == "gosh" ]]`, ""}, 224 225 // dollar quotes 226 {`echo $'foo\nbar'`, "foo\nbar\n"}, 227 {`echo $'\r\t\\'`, "\r\t\\\n"}, 228 {`echo $"foo\nbar"`, "foo\\nbar\n"}, 229 {`echo $'%s'`, "%s\n"}, 230 {`a=$'\r\t\\'; echo "$a"`, "\r\t\\\n"}, 231 {`a=$"foo\nbar"; echo "$a"`, "foo\\nbar\n"}, 232 233 // escaped chars 234 {"echo a\\b", "ab\n"}, 235 {"echo a\\ b", "a b\n"}, 236 {"echo \\$a", "$a\n"}, 237 {"echo \"a\\b\"", "a\\b\n"}, 238 {"echo 'a\\b'", "a\\b\n"}, 239 {"echo \"a\\\nb\"", "ab\n"}, 240 {"echo 'a\\\nb'", "a\\\nb\n"}, 241 {`echo "\""`, "\"\n"}, 242 {`echo \\`, "\\\n"}, 243 {`echo \\\\`, "\\\\\n"}, 244 245 // vars 246 {"foo=bar; echo $foo", "bar\n"}, 247 {"foo=bar foo=etc; echo $foo", "etc\n"}, 248 {"foo=bar; foo=etc; echo $foo", "etc\n"}, 249 {"foo=bar; foo=; echo $foo", "\n"}, 250 {"unset foo; echo $foo", "\n"}, 251 {"foo=bar; unset foo; echo $foo", "\n"}, 252 {"echo $INTERP_GLOBAL", "value\n"}, 253 {"INTERP_GLOBAL=; echo $INTERP_GLOBAL", "\n"}, 254 {"unset INTERP_GLOBAL; echo $INTERP_GLOBAL", "\n"}, 255 {"echo $MIXEDCASE_INTERP_GLOBAL", "value\n"}, 256 {"foo=bar; foo=x true; echo $foo", "bar\n"}, 257 {"foo=bar; foo=x true; echo $foo", "bar\n"}, 258 {"foo=bar; env | grep '^foo='", "exit status 1"}, 259 {"foo=bar env | grep '^foo='", "foo=bar\n"}, 260 {"foo=a foo=b env | grep '^foo='", "foo=b\n"}, 261 {"env | grep '^INTERP_GLOBAL='", "INTERP_GLOBAL=value\n"}, 262 {"INTERP_GLOBAL=new; env | grep '^INTERP_GLOBAL='", "INTERP_GLOBAL=new\n"}, 263 {"INTERP_GLOBAL=; env | grep '^INTERP_GLOBAL='", "INTERP_GLOBAL=\n"}, 264 {"a=b; a+=c x+=y; echo $a $x", "bc y\n"}, 265 {`a=" x y"; b=$a c="$a"; echo $b; echo $c`, "x y\nx y\n"}, 266 {`a=" x y"; b=$a c="$a"; echo "$b"; echo "$c"`, " x y\n x y\n"}, 267 {"env | sed -n '1 s/^$/empty/p'", ""}, // never begin with an empty element 268 269 // special vars 270 {"echo $?; false; echo $?", "0\n1\n"}, 271 {"for i in 1 2; do\necho $LINENO\necho $LINENO\ndone", "2\n3\n2\n3\n"}, 272 {"[[ -n $$ && $$ -gt 0 ]]", ""}, 273 {"[[ -n $PPID && $PPID -gt 0 ]]", ""}, 274 {"[[ $$ -eq $PPID ]]", "exit status 1"}, 275 276 // var manipulation 277 {"echo ${#a} ${#a[@]}", "0 0\n"}, 278 {"a=bar; echo ${#a} ${#a[@]}", "3 1\n"}, 279 {"a=世界; echo ${#a}", "2\n"}, 280 {"a=(a bcd); echo ${#a} ${#a[@]} ${#a[*]} ${#a[1]}", "1 2 2 3\n"}, 281 {"set -- a bc; echo ${#@} ${#*} $#", "2 2 2\n"}, 282 { 283 "echo ${!a}; a=b; echo ${!a}; b=c; echo ${!a}", 284 "\n\nc\n", 285 }, 286 { 287 "a=foo; echo ${a:1}; echo ${a: -1}; echo ${a: -10}; echo ${a:5}", 288 "oo\no\n\n\n", 289 }, 290 { 291 "a=foo; echo ${a::2}; echo ${a::-1}; echo ${a: -10}; echo ${a::5}", 292 "fo\nfo\n\nfoo\n", 293 }, 294 { 295 "a=abc; echo ${a:1:1}", 296 "b\n", 297 }, 298 { 299 "a=foo; echo ${a/no/x} ${a/o/i} ${a//o/i} ${a/fo/}", 300 "foo fio fii o\n", 301 }, 302 { 303 "a=foo; echo ${a/*/xx} ${a//?/na} ${a/o*}", 304 "xx nanana f\n", 305 }, 306 { 307 "a=12345; echo ${a//[42]} ${a//[^42]} ${a//[!42]}", 308 "135 24 24\n", 309 }, 310 {"a=0123456789; echo ${a//[1-35-8]}", "049\n"}, 311 {"a=]abc]; echo ${a//[]b]}", "ac\n"}, 312 {"a=-abc-; echo ${a//[-b]}", "ac\n"}, 313 {`a='x\y'; echo ${a//\\}`, "xy\n"}, 314 {"a=']'; echo ${a//[}", "]\n"}, 315 {"a=']'; echo ${a//[]}", "]\n"}, 316 {"a=']'; echo ${a//[]]}", "\n"}, 317 {"a='['; echo ${a//[[]}", "\n"}, 318 {"a=']'; echo ${a//[xy}", "]\n"}, 319 {"a='abc123'; echo ${a//[[:digit:]]}", "abc\n"}, 320 {"a='[[:wrong:]]'; echo ${a//[[:wrong:]]}", "[[:wrong:]]\n"}, 321 {"a='[[:wrong:]]'; echo ${a//[[:}", "[[:wrong:]]\n"}, 322 {"a='abcx1y'; echo ${a//x[[:digit:]]y}", "abc\n"}, 323 {`a=xyz; echo "${a/y/a b}"`, "xa bz\n"}, 324 {"a='foo/bar'; echo ${a//o*a/}", "fr\n"}, 325 { 326 "echo ${a:-b}; echo $a; a=; echo ${a:-b}; a=c; echo ${a:-b}", 327 "b\n\nb\nc\n", 328 }, 329 { 330 "echo ${#:-never} ${?:-never} ${LINENO:-never}", 331 "0 0 1\n", 332 }, 333 { 334 "echo ${a-b}; echo $a; a=; echo ${a-b}; a=c; echo ${a-b}", 335 "b\n\n\nc\n", 336 }, 337 { 338 "echo ${a:=b}; echo $a; a=; echo ${a:=b}; a=c; echo ${a:=b}", 339 "b\nb\nb\nc\n", 340 }, 341 { 342 "echo ${a=b}; echo $a; a=; echo ${a=b}; a=c; echo ${a=b}", 343 "b\nb\n\nc\n", 344 }, 345 { 346 "echo ${a:+b}; echo $a; a=; echo ${a:+b}; a=c; echo ${a:+b}", 347 "\n\n\nb\n", 348 }, 349 { 350 "echo ${a+b}; echo $a; a=; echo ${a+b}; a=c; echo ${a+b}", 351 "\n\nb\nb\n", 352 }, 353 { 354 "a=b; echo ${a:?err1}; a=; echo ${a:?err2}; unset a; echo ${a:?err3}", 355 "b\nerr2\nexit status 1 #JUSTERR", 356 }, 357 { 358 "a=b; echo ${a?err1}; a=; echo ${a?err2}; unset a; echo ${a?err3}", 359 "b\n\nerr3\nexit status 1 #JUSTERR", 360 }, 361 { 362 "echo ${a:?%s}", 363 "%s\nexit status 1 #JUSTERR", 364 }, 365 { 366 "x=aaabccc; echo ${x#*a}; echo ${x##*a}", 367 "aabccc\nbccc\n", 368 }, 369 { 370 "x=(__a _b c_); echo ${x[@]#_}", 371 "_a b c_\n", 372 }, 373 { 374 "x=(a__ b_ _c); echo ${x[@]%%_}", 375 "a_ b _c\n", 376 }, 377 { 378 "x=aaabccc; echo ${x%c*}; echo ${x%%c*}", 379 "aaabcc\naaab\n", 380 }, 381 { 382 "x=aaabccc; echo ${x%%[bc}", 383 "aaabccc\n", 384 }, 385 { 386 "a='àÉñ bAr'; echo ${a^}; echo ${a^^}", 387 "ÀÉñ bAr\nÀÉÑ BAR\n", 388 }, 389 { 390 "a='àÉñ bAr'; echo ${a,}; echo ${a,,}", 391 "àÉñ bAr\nàéñ bar\n", 392 }, 393 { 394 "a='àÉñ bAr'; echo ${a^?}; echo ${a^^[br]}", 395 "ÀÉñ bAr\nàÉñ BAR\n", 396 }, 397 { 398 "a='àÉñ bAr'; echo ${a,?}; echo ${a,,[br]}", 399 "àÉñ bAr\nàÉñ bAr\n", 400 }, 401 { 402 "a=(àÉñ bAr); echo ${a[@]^}; echo ${a[*],,}", 403 "ÀÉñ BAr\nàéñ bar\n", 404 }, 405 { 406 "INTERP_X_1=a INTERP_X_2=b; echo ${!INTERP_X_*}", 407 "INTERP_X_1 INTERP_X_2\n", 408 }, 409 { 410 "INTERP_X_2=b INTERP_X_1=a; echo ${!INTERP_*}", 411 "INTERP_GLOBAL INTERP_X_1 INTERP_X_2\n", 412 }, 413 { 414 `a='b c'; eval "echo -n ${a} ${a@Q}"`, 415 `b c b c`, 416 }, 417 { 418 `a='"\n'; printf "%s %s" "${a}" "${a@E}"`, 419 "\"\\n \"\n", 420 }, 421 422 // if 423 { 424 "if true; then echo foo; fi", 425 "foo\n", 426 }, 427 { 428 "if false; then echo foo; fi", 429 "", 430 }, 431 { 432 "if false; then echo foo; fi", 433 "", 434 }, 435 { 436 "if true; then echo foo; else echo bar; fi", 437 "foo\n", 438 }, 439 { 440 "if false; then echo foo; else echo bar; fi", 441 "bar\n", 442 }, 443 { 444 "if true; then false; fi", 445 "exit status 1", 446 }, 447 { 448 "if false; then :; else false; fi", 449 "exit status 1", 450 }, 451 { 452 "if false; then :; elif true; then echo foo; fi", 453 "foo\n", 454 }, 455 { 456 "if false; then :; elif false; then :; elif true; then echo foo; fi", 457 "foo\n", 458 }, 459 { 460 "if false; then :; elif false; then :; else echo foo; fi", 461 "foo\n", 462 }, 463 464 // while 465 { 466 "while false; do echo foo; done", 467 "", 468 }, 469 { 470 "while true; do exit 1; done", 471 "exit status 1", 472 }, 473 { 474 "while true; do break; done", 475 "", 476 }, 477 { 478 "while true; do while true; do break 2; done; done", 479 "", 480 }, 481 482 // until 483 { 484 "until true; do echo foo; done", 485 "", 486 }, 487 { 488 "until false; do exit 1; done", 489 "exit status 1", 490 }, 491 { 492 "until false; do break; done", 493 "", 494 }, 495 496 // for 497 { 498 "for i in 1 2 3; do echo $i; done", 499 "1\n2\n3\n", 500 }, 501 { 502 "for i in 1 2 3; do echo $i; exit; done", 503 "1\n", 504 }, 505 { 506 "for i in 1 2 3; do echo $i; false; done", 507 "1\n2\n3\nexit status 1", 508 }, 509 { 510 "for i in 1 2 3; do echo $i; break; done", 511 "1\n", 512 }, 513 { 514 "for i in 1 2 3; do echo $i; continue; echo foo; done", 515 "1\n2\n3\n", 516 }, 517 { 518 "for i in 1 2; do for j in a b; do echo $i $j; continue 2; done; done", 519 "1 a\n2 a\n", 520 }, 521 { 522 "for ((i=0; i<3; i++)); do echo $i; done", 523 "0\n1\n2\n", 524 }, 525 { 526 "for ((i=5; i>0; i--)); do echo $i; break; done", 527 "5\n", 528 }, 529 { 530 "for i in 1 2; do for j in a b; do echo $i $j; done; break; done", 531 "1 a\n1 b\n", 532 }, 533 { 534 "for i in 1 2 3; do :; done; echo $i", 535 "3\n", 536 }, 537 { 538 "for ((i=0; i<3; i++)); do :; done; echo $i", 539 "3\n", 540 }, 541 { 542 "set -- a 'b c'; for i in; do echo $i; done", 543 "", 544 }, 545 { 546 "set -- a 'b c'; for i; do echo $i; done", 547 "a\nb c\n", 548 }, 549 550 // block 551 { 552 "{ echo foo; }", 553 "foo\n", 554 }, 555 { 556 "{ false; }", 557 "exit status 1", 558 }, 559 560 // subshell 561 { 562 "(echo foo)", 563 "foo\n", 564 }, 565 { 566 "(false)", 567 "exit status 1", 568 }, 569 { 570 "(exit 1)", 571 "exit status 1", 572 }, 573 { 574 "(foo=bar; echo $foo); echo $foo", 575 "bar\n\n", 576 }, 577 { 578 "(echo() { printf 'bar\n'; }; echo); echo", 579 "bar\n\n", 580 }, 581 { 582 "unset INTERP_GLOBAL & echo $INTERP_GLOBAL", 583 "value\n", 584 }, 585 { 586 "(fn() { :; }) & pwd >/dev/null", 587 "", 588 }, 589 590 // cd/pwd 591 {"[[ fo~ == 'fo~' ]]", ""}, 592 {`[[ 'ab\c' == *\\* ]]`, ""}, 593 {`[[ foo/bar == foo* ]]`, ""}, 594 {"[[ a == [ab ]]", "exit status 1"}, 595 {`HOME='/*'; echo ~; echo "$HOME"`, "/*\n/*\n"}, 596 {`test -d ~`, ""}, 597 {`foo=~; test -d $foo`, ""}, 598 {`foo=~; test -d "$foo"`, ""}, 599 {`foo='~'; test -d $foo`, "exit status 1"}, 600 {`foo='~'; [ $foo == '~' ]`, ""}, 601 { 602 `[[ ~ == "$HOME" ]] && [[ ~/foo == "$HOME/foo" ]]`, 603 "", 604 }, 605 { 606 "[[ ~noexist == '~noexist' ]]", 607 "", 608 }, 609 { 610 "[[ ~root == '~root' ]]", 611 "exit status 1", 612 }, 613 { 614 `w="$HOME"; cd; [[ $PWD == "$w" ]]`, 615 "", 616 }, 617 { 618 `HOME=/foo; echo $HOME`, 619 "/foo\n", 620 }, 621 { 622 "cd noexist", 623 "exit status 1 #JUSTERR", 624 }, 625 { 626 "mkdir -p a/b && cd a && cd b && cd ../..", 627 "", 628 }, 629 { 630 "touch a && cd a", 631 "exit status 1 #JUSTERR", 632 }, 633 { 634 `[[ $PWD == "$(pwd)" ]]`, 635 "", 636 }, 637 { 638 "PWD=changed; [[ $PWD == changed ]]", 639 "", 640 }, 641 { 642 "PWD=changed; mkdir a; cd a; [[ $PWD == changed ]]", 643 "exit status 1", 644 }, 645 { 646 `mkdir %s; old="$PWD"; cd %s; [[ $old == "$PWD" ]]`, 647 "exit status 1", 648 }, 649 { 650 `old="$PWD"; mkdir a; cd a; cd ..; [[ $old == "$PWD" ]]`, 651 "", 652 }, 653 { 654 `[[ $PWD == "$OLDPWD" ]]`, 655 "exit status 1", 656 }, 657 { 658 `old="$PWD"; mkdir a; cd a; [[ $old == "$OLDPWD" ]]`, 659 "", 660 }, 661 { 662 `mkdir a; ln -s a b; [[ $(cd a && pwd) == "$(cd b && pwd)" ]]; echo $?`, 663 "1\n", 664 }, 665 { 666 `mkdir a; chmod 0000 a; cd a`, 667 "exit status 1 #JUSTERR", 668 }, 669 { 670 `mkdir a; chmod 0222 a; cd a`, 671 "exit status 1 #JUSTERR", 672 }, 673 { 674 `mkdir a; chmod 0444 a; cd a`, 675 "exit status 1 #JUSTERR", 676 }, 677 { 678 `mkdir a; chmod 0100 a; cd a`, 679 "", 680 }, 681 { 682 `mkdir a; chmod 0010 a; cd a`, 683 "exit status 1 #JUSTERR", 684 }, 685 { 686 `mkdir a; chmod 0001 a; cd a`, 687 "exit status 1 #JUSTERR", 688 }, 689 690 // dirs/pushd/popd 691 {"set -- $(dirs); echo $# ${#DIRSTACK[@]}", "1 1\n"}, 692 {"pushd", "pushd: no other directory\nexit status 1 #JUSTERR"}, 693 {"pushd -n", ""}, 694 {"pushd foo bar", "pushd: too many arguments\nexit status 2 #JUSTERR"}, 695 {"pushd does-not-exist; set -- $(dirs); echo $#", "1\n #IGNORE"}, 696 {"mkdir a; pushd a >/dev/null; set -- $(dirs); echo $#", "2\n"}, 697 {"mkdir a; set -- $(pushd a); echo $#", "2\n"}, 698 { 699 `mkdir a; pushd a >/dev/null; set -- $(dirs); [[ $1 == "$HOME" ]]`, 700 "exit status 1", 701 }, 702 { 703 `mkdir a; pushd a >/dev/null; [[ ${DIRSTACK[0]} == "$HOME" ]]`, 704 "exit status 1", 705 }, 706 { 707 `old=$(dirs); mkdir a; pushd a >/dev/null; pushd >/dev/null; set -- $(dirs); [[ $1 == "$old" ]]`, 708 "", 709 }, 710 { 711 `old=$(dirs); mkdir a; pushd a >/dev/null; pushd -n >/dev/null; set -- $(dirs); [[ $1 == "$old" ]]`, 712 "exit status 1", 713 }, 714 { 715 "mkdir a; pushd a >/dev/null; pushd >/dev/null; rmdir a; pushd", 716 "exit status 1 #JUSTERR", 717 }, 718 { 719 `old=$(dirs); mkdir a; pushd -n a >/dev/null; set -- $(dirs); [[ $1 == "$old" ]]`, 720 "", 721 }, 722 { 723 `old=$(dirs); mkdir a; pushd -n a >/dev/null; pushd >/dev/null; set -- $(dirs); [[ $1 == "$old" ]]`, 724 "exit status 1", 725 }, 726 {"popd", "popd: directory stack empty\nexit status 1 #JUSTERR"}, 727 {"popd -n", "popd: directory stack empty\nexit status 1 #JUSTERR"}, 728 {"popd foo", "popd: invalid argument\nexit status 2 #JUSTERR"}, 729 {"old=$(dirs); mkdir a; pushd a >/dev/null; set -- $(popd); echo $#", "1\n"}, 730 { 731 `old=$(dirs); mkdir a; pushd a >/dev/null; popd >/dev/null; [[ $(dirs) == "$old" ]]`, 732 "", 733 }, 734 {"old=$(dirs); mkdir a; pushd a >/dev/null; set -- $(popd -n); echo $#", "1\n"}, 735 { 736 `old=$(dirs); mkdir a; pushd a >/dev/null; popd -n >/dev/null; [[ $(dirs) == "$old" ]]`, 737 "exit status 1", 738 }, 739 { 740 "mkdir a; pushd a >/dev/null; pushd >/dev/null; rmdir a; popd", 741 "exit status 1 #JUSTERR", 742 }, 743 744 // binary cmd 745 { 746 "true && echo foo || echo bar", 747 "foo\n", 748 }, 749 { 750 "false && echo foo || echo bar", 751 "bar\n", 752 }, 753 754 // func 755 { 756 "foo() { echo bar; }; foo", 757 "bar\n", 758 }, 759 { 760 "foo() { echo $1; }; foo", 761 "\n", 762 }, 763 { 764 "foo() { echo $1; }; foo a b", 765 "a\n", 766 }, 767 { 768 "foo() { echo $1; bar c d; echo $2; }; bar() { echo $2; }; foo a b", 769 "a\nd\nb\n", 770 }, 771 { 772 `foo() { echo $#; }; foo; foo 1 2 3; foo "a b"; echo $#`, 773 "0\n3\n1\n0\n", 774 }, 775 { 776 `foo() { for a in $*; do echo "$a"; done }; foo 'a 1' 'b 2'`, 777 "a\n1\nb\n2\n", 778 }, 779 { 780 `foo() { for a in "$*"; do echo "$a"; done }; foo 'a 1' 'b 2'`, 781 "a 1 b 2\n", 782 }, 783 { 784 `foo() { for a in "foo$*"; do echo "$a"; done }; foo 'a 1' 'b 2'`, 785 "fooa 1 b 2\n", 786 }, 787 { 788 `foo() { for a in $@; do echo "$a"; done }; foo 'a 1' 'b 2'`, 789 "a\n1\nb\n2\n", 790 }, 791 { 792 `foo() { for a in "$@"; do echo "$a"; done }; foo 'a 1' 'b 2'`, 793 "a 1\nb 2\n", 794 }, 795 796 // case 797 { 798 "case b in x) echo foo ;; a|b) echo bar ;; esac", 799 "bar\n", 800 }, 801 { 802 "case b in x) echo foo ;; y|z) echo bar ;; esac", 803 "", 804 }, 805 { 806 "case foo in bar) echo foo ;; *) echo bar ;; esac", 807 "bar\n", 808 }, 809 { 810 "case foo in *o*) echo bar ;; esac", 811 "bar\n", 812 }, 813 { 814 "case foo in '*') echo x ;; f*) echo y ;; esac", 815 "y\n", 816 }, 817 818 // exec 819 { 820 "bash -c 'echo foo'", 821 "foo\n", 822 }, 823 { 824 "bash -c 'echo foo >&2' >/dev/null", 825 "foo\n", 826 }, 827 { 828 "echo foo | bash -c 'cat >&2' >/dev/null", 829 "foo\n", 830 }, 831 { 832 "bash -c 'exit 1'", 833 "exit status 1", 834 }, 835 { 836 "exec >/dev/null; echo foo", 837 "", 838 }, 839 840 // PATH 841 { 842 "PATH=; bash -c 'echo foo'", 843 "\"bash\": executable file not found in $PATH\nexit status 127 #JUSTERR", 844 }, 845 { 846 "echo '#!/bin/sh\necho b' >a; chmod a+x a; PATH=; a", 847 "b\n", 848 }, 849 { 850 "mkdir c; cd c; echo '#!/bin/sh\necho b' >a; chmod a+x a; PATH=; a", 851 "b\n", 852 }, 853 { 854 "c/a", 855 "\"c/a\": executable file not found in $PATH\nexit status 127 #JUSTERR", 856 }, 857 { 858 "mkdir c; echo '#!/bin/sh\necho b' >c/a; chmod a+x c/a; c/a", 859 "b\n", 860 }, 861 862 // return 863 {"return", "return: can only be done from a func or sourced script\nexit status 1 #JUSTERR"}, 864 {"f() { return; }; f", ""}, 865 {"f() { return 2; }; f", "exit status 2"}, 866 {"f() { echo foo; return; echo bar; }; f", "foo\n"}, 867 {"f1() { :; }; f2() { f1; return; }; f2", ""}, 868 {"echo 'return' >a; source a", ""}, 869 {"echo 'return' >a; source a; return", "return: can only be done from a func or sourced script\nexit status 1 #JUSTERR"}, 870 {"echo 'return 2' >a; source a", "exit status 2"}, 871 {"echo 'echo foo; return; echo bar' >a; source a", "foo\n"}, 872 873 // command 874 {"command", ""}, 875 {"command -o echo", "command: invalid option -o\nexit status 2 #JUSTERR"}, 876 {"echo() { :; }; echo foo", ""}, 877 {"echo() { :; }; command echo foo", "foo\n"}, 878 {"bash() { :; }; bash -c 'echo foo'", ""}, 879 {"bash() { :; }; command bash -c 'echo foo'", "foo\n"}, 880 {"command -v does-not-exist", "exit status 1"}, 881 {"foo() { :; }; command -v foo", "foo\n"}, 882 {"foo() { :; }; command -v does-not-exist foo", "foo\n"}, 883 {"command -v echo", "echo\n"}, 884 {"[[ $(command -v bash) == bash ]]", "exit status 1"}, 885 886 // cmd substitution 887 { 888 "echo foo $(printf bar)", 889 "foo bar\n", 890 }, 891 { 892 "echo foo $(echo bar)", 893 "foo bar\n", 894 }, 895 { 896 "$(echo echo foo bar)", 897 "foo bar\n", 898 }, 899 { 900 "for i in 1 $(echo 2 3) 4; do echo $i; done", 901 "1\n2\n3\n4\n", 902 }, 903 { 904 "echo 1$(echo 2 3)4", 905 "12 34\n", 906 }, 907 { 908 `mkdir d; [[ $(cd d && pwd) == "$(pwd)" ]]`, 909 "exit status 1", 910 }, 911 { 912 "a=sub true & { a=main env | grep '^a='; }", 913 "a=main\n", 914 }, 915 { 916 "echo foo >f; echo $(cat f); echo $(<f)", 917 "foo\nfoo\n", 918 }, 919 { 920 "echo foo >f; echo $(<f; echo bar)", 921 "bar\n", 922 }, 923 924 // pipes 925 { 926 "echo foo | sed 's/o/a/g'", 927 "faa\n", 928 }, 929 { 930 "echo foo | false | true", 931 "", 932 }, 933 { 934 "true $(true) | true", // used to panic 935 "", 936 }, 937 938 // redirects 939 { 940 "echo foo >&1 | sed 's/o/a/g'", 941 "faa\n", 942 }, 943 { 944 "echo foo >&2 | sed 's/o/a/g'", 945 "foo\n", 946 }, 947 { 948 // TODO: why does bash need a block here? 949 "{ echo foo >&2; } |& sed 's/o/a/g'", 950 "faa\n", 951 }, 952 { 953 "echo foo >/dev/null; echo bar", 954 "bar\n", 955 }, 956 // TODO: reenable once we've made a decision on 957 // https://github.com/mvdan/sh/issues/289 958 // { 959 // ">a; echo foo >>b; wc -c <a >>b; cat b", 960 // "foo\n0\n", 961 // }, 962 { 963 "echo foo >a; <a", 964 "", 965 }, 966 { 967 "echo foo >a; wc -c <a", 968 "4\n", 969 }, 970 { 971 "echo foo >>a; echo bar &>>a; wc -c <a", 972 "8\n", 973 }, 974 { 975 "{ echo a; echo b >&2; } &>/dev/null", 976 "", 977 }, 978 { 979 "sed 's/o/a/g' <<EOF\nfoo$foo\nEOF", 980 "faa\n", 981 }, 982 { 983 "sed 's/o/a/g' <<'EOF'\nfoo$foo\nEOF", 984 "faa$faa\n", 985 }, 986 { 987 "sed 's/o/a/g' <<EOF\n\tfoo\nEOF", 988 "\tfaa\n", 989 }, 990 { 991 "sed 's/o/a/g' <<EOF\nfoo\nEOF", 992 "faa\n", 993 }, 994 { 995 "cat <<EOF\n~/foo\nEOF", 996 "~/foo\n", 997 }, 998 { 999 "sed 's/o/a/g' <<<foo$foo", 1000 "faa\n", 1001 }, 1002 { 1003 "cat <<-EOF\n\tfoo\nEOF", 1004 "foo\n", 1005 }, 1006 { 1007 "cat <<-EOF\n\tfoo\n\nEOF", 1008 "foo\n\n", 1009 }, 1010 { 1011 "mkdir a; echo foo >a |& grep -q 'is a directory'", 1012 " #IGNORE", 1013 }, 1014 { 1015 "echo foo 1>&1 | sed 's/o/a/g'", 1016 "faa\n", 1017 }, 1018 { 1019 "echo foo 2>&2 |& sed 's/o/a/g'", 1020 "faa\n", 1021 }, 1022 { 1023 "printf 2>&1 | sed 's/.*usage.*/foo/'", 1024 "foo\n", 1025 }, 1026 { 1027 "mkdir a && cd a && echo foo >b && cd .. && cat a/b", 1028 "foo\n", 1029 }, 1030 1031 // background/wait 1032 {"wait", ""}, 1033 {"{ true; } & wait", ""}, 1034 {"{ exit 1; } & wait", ""}, 1035 { 1036 "{ echo foo; } & wait; echo bar", 1037 "foo\nbar\n", 1038 }, 1039 { 1040 "{ echo foo & wait; } & wait; echo bar", 1041 "foo\nbar\n", 1042 }, 1043 {`mkdir d; old=$PWD; cd d & wait; [[ $old == "$PWD" ]]`, ""}, 1044 1045 // bash test 1046 { 1047 "[[ a ]]", 1048 "", 1049 }, 1050 { 1051 "[[ '' ]]", 1052 "exit status 1", 1053 }, 1054 { 1055 "[[ '' ]]; [[ a ]]", 1056 "", 1057 }, 1058 { 1059 "[[ ! (a == b) ]]", 1060 "", 1061 }, 1062 { 1063 "[[ a != b ]]", 1064 "", 1065 }, 1066 { 1067 "[[ a && '' ]]", 1068 "exit status 1", 1069 }, 1070 { 1071 "[[ a || '' ]]", 1072 "", 1073 }, 1074 { 1075 "[[ a > 3 ]]", 1076 "", 1077 }, 1078 { 1079 "[[ a < 3 ]]", 1080 "exit status 1", 1081 }, 1082 { 1083 "[[ 3 == 03 ]]", 1084 "exit status 1", 1085 }, 1086 { 1087 "[[ a -eq b ]]", 1088 "", 1089 }, 1090 { 1091 "[[ 3 -eq 03 ]]", 1092 "", 1093 }, 1094 { 1095 "[[ 3 -ne 4 ]]", 1096 "", 1097 }, 1098 { 1099 "[[ 3 -le 4 ]]", 1100 "", 1101 }, 1102 { 1103 "[[ 3 -ge 4 ]]", 1104 "exit status 1", 1105 }, 1106 { 1107 "[[ 3 -ge 3 ]]", 1108 "", 1109 }, 1110 { 1111 "[[ 3 -lt 4 ]]", 1112 "", 1113 }, 1114 { 1115 "[[ 3 -gt 4 ]]", 1116 "exit status 1", 1117 }, 1118 { 1119 "[[ 3 -gt 3 ]]", 1120 "exit status 1", 1121 }, 1122 { 1123 "[[ a -nt a || a -ot a ]]", 1124 "exit status 1", 1125 }, 1126 { 1127 "touch -d @1 a b; [[ a -nt b || a -ot b ]]", 1128 "exit status 1", 1129 }, 1130 { 1131 "touch -d @1 a; touch -d @2 b; [[ a -nt b ]]", 1132 "exit status 1", 1133 }, 1134 { 1135 "touch -d @1 a; touch -d @2 b; [[ a -ot b ]]", 1136 "", 1137 }, 1138 { 1139 "[[ a -ef b ]]", 1140 "exit status 1", 1141 }, 1142 { 1143 "touch a b; [[ a -ef b ]]", 1144 "exit status 1", 1145 }, 1146 { 1147 "touch a; [[ a -ef a ]]", 1148 "", 1149 }, 1150 { 1151 "touch a; ln a b; [[ a -ef b ]]", 1152 "", 1153 }, 1154 { 1155 "touch a; ln -s a b; [[ a -ef b ]]", 1156 "", 1157 }, 1158 { 1159 "[[ -z 'foo' || -n '' ]]", 1160 "exit status 1", 1161 }, 1162 { 1163 "[[ -z '' && -n 'foo' ]]", 1164 "", 1165 }, 1166 { 1167 "a=x b=''; [[ -v a && -v b && ! -v c ]]", 1168 "", 1169 }, 1170 { 1171 "[[ abc == *b* ]]", 1172 "", 1173 }, 1174 { 1175 "[[ abc != *b* ]]", 1176 "exit status 1", 1177 }, 1178 { 1179 "[[ *b == '*b' ]]", 1180 "", 1181 }, 1182 { 1183 "[[ ab == a. ]]", 1184 "exit status 1", 1185 }, 1186 { 1187 `x='*b*'; [[ abc == $x ]]`, 1188 "", 1189 }, 1190 { 1191 `x='*b*'; [[ abc == "$x" ]]`, 1192 "exit status 1", 1193 }, 1194 { 1195 `[[ abc == \a\bc ]]`, 1196 "", 1197 }, 1198 { 1199 "[[ abc != *b'*' ]]", 1200 "", 1201 }, 1202 { 1203 "[[ a =~ b ]]", 1204 "exit status 1", 1205 }, 1206 { 1207 "[[ foo =~ foo && foo =~ .* && foo =~ f.o ]]", 1208 "", 1209 }, 1210 { 1211 "[[ foo =~ oo ]] && echo foo; [[ foo =~ ^oo$ ]] && echo bar || true", 1212 "foo\n", 1213 }, 1214 { 1215 "[[ a =~ [ ]]", 1216 "exit status 2", 1217 }, 1218 { 1219 "[[ -e a ]] && echo x; touch a; [[ -e a ]] && echo y", 1220 "y\n", 1221 }, 1222 { 1223 "ln -s b a; [[ -e a ]] && echo x; touch b; [[ -e a ]] && echo y", 1224 "y\n", 1225 }, 1226 { 1227 "[[ -f a ]] && echo x; touch a; [[ -f a ]] && echo y", 1228 "y\n", 1229 }, 1230 { 1231 "[[ -e a ]] && echo x; mkdir a; [[ -e a ]] && echo y", 1232 "y\n", 1233 }, 1234 { 1235 "[[ -d a ]] && echo x; mkdir a; [[ -d a ]] && echo y", 1236 "y\n", 1237 }, 1238 { 1239 "[[ -r a ]] && echo x; touch a; [[ -r a ]] && echo y", 1240 "y\n", 1241 }, 1242 { 1243 "[[ -w a ]] && echo x; touch a; [[ -w a ]] && echo y", 1244 "y\n", 1245 }, 1246 { 1247 "[[ -x a ]] && echo x; touch a; chmod +x a; [[ -x a ]] && echo y", 1248 "y\n", 1249 }, 1250 { 1251 "[[ -s a ]] && echo x; echo body >a; [[ -s a ]] && echo y", 1252 "y\n", 1253 }, 1254 { 1255 "[[ -L a ]] && echo x; ln -s b a; [[ -L a ]] && echo y;", 1256 "y\n", 1257 }, 1258 { 1259 "[[ -p a ]] && echo x; mkfifo a; [[ -p a ]] && echo y", 1260 "y\n", 1261 }, 1262 { 1263 "touch a; [[ -k a ]] && echo x; chmod +t a; [[ -k a ]] && echo y", 1264 "y\n", 1265 }, 1266 { 1267 "touch a; [[ -u a ]] && echo x; chmod u+s a; [[ -u a ]] && echo y", 1268 "y\n", 1269 }, 1270 { 1271 "touch a; [[ -g a ]] && echo x; chmod g+s a; [[ -g a ]] && echo y", 1272 "y\n", 1273 }, 1274 { 1275 "mkdir a; cd a; test -f b && echo x; touch b; test -f b && echo y", 1276 "y\n", 1277 }, 1278 { 1279 "touch a; [[ -b a ]] && echo block; [[ -c a ]] && echo char; true", 1280 "", 1281 }, 1282 { 1283 "[[ -e /dev/sda ]] || { echo block; exit; }; [[ -b /dev/sda ]] && echo block; [[ -c /dev/sda ]] && echo char; true", 1284 "block\n", 1285 }, 1286 { 1287 "[[ -e /dev/tty ]] || { echo char; exit; }; [[ -b /dev/tty ]] && echo block; [[ -c /dev/tty ]] && echo char; true", 1288 "char\n", 1289 }, 1290 {"[[ -t 1234 ]]", "exit status 1"}, // TODO: reliable way to test a positive? 1291 {"[[ -o wrong ]]", "exit status 1"}, 1292 {"[[ -o errexit ]]", "exit status 1"}, 1293 {"set -e; [[ -o errexit ]]", ""}, 1294 {"[[ -o noglob ]]", "exit status 1"}, 1295 {"set -f; [[ -o noglob ]]", ""}, 1296 {"[[ -o allexport ]]", "exit status 1"}, 1297 {"set -a; [[ -o allexport ]]", ""}, 1298 {"[[ -o nounset ]]", "exit status 1"}, 1299 {"set -u; [[ -o nounset ]]", ""}, 1300 {"[[ -o noexec ]]", "exit status 1"}, 1301 {"set -n; [[ -o noexec ]]", ""}, // actually does nothing, but oh well 1302 {"[[ -o pipefail ]]", "exit status 1"}, 1303 {"set -o pipefail; [[ -o pipefail ]]", ""}, 1304 1305 // classic test 1306 { 1307 "[", 1308 "1:1: [: missing matching ]\nexit status 2 #JUSTERR", 1309 }, 1310 { 1311 "[ a", 1312 "1:1: [: missing matching ]\nexit status 2 #JUSTERR", 1313 }, 1314 { 1315 "[ a b c ]", 1316 "1:1: not a valid test operator: b\nexit status 2 #JUSTERR", 1317 }, 1318 { 1319 "[ a -a ]", 1320 "1:1: -a must be followed by an expression\nexit status 2 #JUSTERR", 1321 }, 1322 {"[ a ]", ""}, 1323 {"[ -n ]", ""}, 1324 {"[ '-n' ]", ""}, 1325 {"[ -z ]", ""}, 1326 {"[ ! ]", ""}, 1327 {"[ a != b ]", ""}, 1328 {"[ ! a '==' a ]", "exit status 1"}, 1329 {"[ a -a 0 -gt 1 ]", "exit status 1"}, 1330 {"[ 0 -gt 1 -o 1 -gt 0 ]", ""}, 1331 {"[ 3 -gt 4 ]", "exit status 1"}, 1332 {"[ 3 -lt 4 ]", ""}, 1333 { 1334 "[ -e a ] && echo x; touch a; [ -e a ] && echo y", 1335 "y\n", 1336 }, 1337 { 1338 "test 3 -gt 4", 1339 "exit status 1", 1340 }, 1341 { 1342 "test 3 -lt 4", 1343 "", 1344 }, 1345 { 1346 "test 3 -lt", 1347 "1:1: -lt must be followed by a word\nexit status 2 #JUSTERR", 1348 }, 1349 { 1350 "touch -d @1 a; touch -d @2 b; [ a -nt b ]", 1351 "exit status 1", 1352 }, 1353 { 1354 "touch -d @1 a; touch -d @2 b; [ a -ot b ]", 1355 "", 1356 }, 1357 { 1358 "touch a; [ a -ef a ]", 1359 "", 1360 }, 1361 {"[ 3 -eq 04 ]", "exit status 1"}, 1362 {"[ 3 -eq 03 ]", ""}, 1363 {"[ 3 -ne 03 ]", "exit status 1"}, 1364 {"[ 3 -le 4 ]", ""}, 1365 {"[ 3 -ge 4 ]", "exit status 1"}, 1366 { 1367 "[ -d a ] && echo x; mkdir a; [ -d a ] && echo y", 1368 "y\n", 1369 }, 1370 { 1371 "[ -r a ] && echo x; touch a; [ -r a ] && echo y", 1372 "y\n", 1373 }, 1374 { 1375 "[ -w a ] && echo x; touch a; [ -w a ] && echo y", 1376 "y\n", 1377 }, 1378 { 1379 "[ -x a ] && echo x; touch a; chmod +x a; [ -x a ] && echo y", 1380 "y\n", 1381 }, 1382 { 1383 "[ -s a ] && echo x; echo body >a; [ -s a ] && echo y", 1384 "y\n", 1385 }, 1386 { 1387 "[ -L a ] && echo x; ln -s b a; [ -L a ] && echo y;", 1388 "y\n", 1389 }, 1390 { 1391 "[ -p a ] && echo x; mkfifo a; [ -p a ] && echo y", 1392 "y\n", 1393 }, 1394 { 1395 "touch a; [ -k a ] && echo x; chmod +t a; [ -k a ] && echo y", 1396 "y\n", 1397 }, 1398 { 1399 "touch a; [ -u a ] && echo x; chmod u+s a; [ -u a ] && echo y", 1400 "y\n", 1401 }, 1402 { 1403 "touch a; [ -g a ] && echo x; chmod g+s a; [ -g a ] && echo y", 1404 "y\n", 1405 }, 1406 { 1407 "touch a; [ -b a ] && echo block; [ -c a ] && echo char; true", 1408 "", 1409 }, 1410 {"[ -t 1234 ]", "exit status 1"}, // TODO: reliable way to test a positive? 1411 {"[ -o wrong ]", "exit status 1"}, 1412 {"[ -o errexit ]", "exit status 1"}, 1413 {"set -e; [ -o errexit ]", ""}, 1414 {"a=x b=''; [ -v a -a -v b -a ! -v c ]", ""}, 1415 {"[ a = a ]", ""}, 1416 {"[ a != a ]", "exit status 1"}, 1417 {"[ abc = ab* ]", "exit status 1"}, 1418 {"[ abc != ab* ]", ""}, 1419 1420 // arithm 1421 { 1422 "echo $((1 == +1))", 1423 "1\n", 1424 }, 1425 { 1426 "echo $((!0))", 1427 "1\n", 1428 }, 1429 { 1430 "echo $((!3))", 1431 "0\n", 1432 }, 1433 { 1434 "echo $((1 + 2 - 3))", 1435 "0\n", 1436 }, 1437 { 1438 "echo $((-1 * 6 / 2))", 1439 "-3\n", 1440 }, 1441 { 1442 "a=2; echo $(( a + $a + c ))", 1443 "4\n", 1444 }, 1445 { 1446 "a=b; b=c; c=5; echo $((a % 3))", 1447 "2\n", 1448 }, 1449 { 1450 "echo $((2 > 2 || 2 < 2))", 1451 "0\n", 1452 }, 1453 { 1454 "echo $((2 >= 2 && 2 <= 2))", 1455 "1\n", 1456 }, 1457 { 1458 "echo $(((1 & 2) != (1 | 2)))", 1459 "1\n", 1460 }, 1461 { 1462 "echo $a; echo $((a = 3 ^ 2)); echo $a", 1463 "\n1\n1\n", 1464 }, 1465 { 1466 "echo $((a += 1, a *= 2, a <<= 2, a >> 1))", 1467 "4\n", 1468 }, 1469 { 1470 "echo $((a -= 10, a /= 2, a >>= 1, a << 1))", 1471 "-6\n", 1472 }, 1473 { 1474 "echo $((a |= 3, a &= 1, a ^= 8, a %= 5, a))", 1475 "4\n", 1476 }, 1477 { 1478 "echo $((a = 3, ++a, a--))", 1479 "4\n", 1480 }, 1481 { 1482 "echo $((2 ** 3)) $((1234 ** 4567))", 1483 "8 0\n", 1484 }, 1485 { 1486 "echo $((1 ? 2 : 3)) $((0 ? 2 : 3))", 1487 "2 3\n", 1488 }, 1489 { 1490 "((1))", 1491 "", 1492 }, 1493 { 1494 "((3 == 4))", 1495 "exit status 1", 1496 }, 1497 { 1498 "let i=(3+4); let i++; echo $i; let i--; echo $i", 1499 "8\n7\n", 1500 }, 1501 { 1502 "let 3==4", 1503 "exit status 1", 1504 }, 1505 { 1506 "a=1; let a++; echo $a", 1507 "2\n", 1508 }, 1509 { 1510 "a=$((1 + 2)); echo $a", 1511 "3\n", 1512 }, 1513 { 1514 "x=3; echo $(($x)) $((x))", 1515 "3 3\n", 1516 }, 1517 { 1518 "set -- 1; echo $(($@))", 1519 "1\n", 1520 }, 1521 { 1522 "a=b b=a; echo $(($a))", 1523 "0\n #IGNORE", 1524 }, 1525 1526 // set/shift 1527 { 1528 "echo $#; set foo bar; echo $#", 1529 "0\n2\n", 1530 }, 1531 { 1532 "shift; set a b c; shift; echo $@", 1533 "b c\n", 1534 }, 1535 { 1536 "shift 2; set a b c; shift 2; echo $@", 1537 "c\n", 1538 }, 1539 { 1540 `echo $#; set '' ""; echo $#`, 1541 "0\n2\n", 1542 }, 1543 { 1544 "set -- a b; echo $#", 1545 "2\n", 1546 }, 1547 { 1548 "set -U", 1549 "set: invalid option: \"-U\"\nexit status 2 #JUSTERR", 1550 }, 1551 { 1552 "set -e; false; echo foo", 1553 "exit status 1", 1554 }, 1555 { 1556 "set -e; set +e; false; echo foo", 1557 "foo\n", 1558 }, 1559 { 1560 "set -e; ! false; echo foo", 1561 "foo\n", 1562 }, 1563 { 1564 "set -e; local; echo foo", 1565 "local: can only be used in a function\nexit status 1 #JUSTERR", 1566 }, 1567 { 1568 "false | :", 1569 "", 1570 }, 1571 { 1572 "set -o pipefail; false | :", 1573 "exit status 1", 1574 }, 1575 { 1576 "set -o pipefail; true | false | true | :", 1577 "exit status 1", 1578 }, 1579 { 1580 "set -o pipefail; set -M 2>/dev/null | false", 1581 "exit status 1", 1582 }, 1583 { 1584 "set -f; touch a.x; echo *.x;", 1585 "*.x\n", 1586 }, 1587 { 1588 "set -f; set +f; touch a.x; echo *.x;", 1589 "a.x\n", 1590 }, 1591 { 1592 "set -a; foo=bar; env | grep ^foo=", 1593 "foo=bar\n", 1594 }, 1595 { 1596 "set -a; foo=(b a r); env | grep ^foo=", 1597 "exit status 1", 1598 }, 1599 { 1600 "foo=bar; set -a; env | grep ^foo=", 1601 "exit status 1", 1602 }, 1603 { 1604 "a=b; echo $a; set -u; echo $a", 1605 "b\nb\n", 1606 }, 1607 { 1608 "echo $a; set -u; echo $a; echo extra", 1609 "\na: unbound variable\nexit status 1 #JUSTERR", 1610 }, 1611 {"set -n; echo foo", ""}, 1612 {"set -n; [ wrong", ""}, 1613 {"set -n; set +n; echo foo", ""}, 1614 { 1615 "set -o foobar", 1616 "set: invalid option: \"-o\"\nexit status 2 #JUSTERR", 1617 }, 1618 {"set -o noexec; echo foo", ""}, 1619 {"set +o noexec; echo foo", "foo\n"}, 1620 {"set -e; set -o | grep -E 'errexit|noexec' | wc -l", "2\n"}, 1621 {"set -e; set -o | grep -E 'errexit|noexec' | grep 'on$' | wc -l", "1\n"}, 1622 { 1623 "set -a; set +o", 1624 `set -o allexport 1625 set +o errexit 1626 set +o noexec 1627 set +o noglob 1628 set +o nounset 1629 set +o pipefail 1630 #IGNORE`, 1631 }, 1632 1633 // unset 1634 { 1635 "a=1; echo $a; unset a; echo $a", 1636 "1\n\n", 1637 }, 1638 { 1639 "a() { echo func; }; a; unset -f a; a", 1640 "func\n\"a\": executable file not found in $PATH\nexit status 127 #JUSTERR", 1641 }, 1642 { 1643 "a=1; a() { echo func; }; unset -f a; echo $a", 1644 "1\n", 1645 }, 1646 { 1647 "a=1; a() { echo func; }; unset -v a; a; echo $a", 1648 "func\n\n", 1649 }, 1650 { 1651 "a=1; a() { echo func; }; a; echo $a; unset a; a; echo $a; unset a; a", 1652 "func\n1\nfunc\n\n\"a\": executable file not found in $PATH\nexit status 127 #JUSTERR", 1653 }, 1654 { 1655 "unset PATH; [[ $PATH == '' ]]", 1656 "", 1657 }, 1658 { 1659 "readonly a=1; echo $a; unset a; echo $a", 1660 "1\na: readonly variable\n1\n #IGNORE", 1661 }, 1662 { 1663 "f() { local a=1; echo $a; unset a; echo $a; }; f", 1664 "1\n\n", 1665 }, 1666 { 1667 `a=b eval 'echo $a; unset a; echo $a'`, 1668 "b\n\n", 1669 }, 1670 { 1671 `$(unset INTERP_GLOBAL); echo $INTERP_GLOBAL; unset INTERP_GLOBAL; echo $INTERP_GLOBAL`, 1672 "value\n\n", 1673 }, 1674 { 1675 `x=orig; f() { local x=local; unset x; x=still_local; }; f; echo $x`, 1676 "orig\n", 1677 }, 1678 { 1679 `x=orig; f() { local x=local; unset x; [[ -v x ]] && echo set || echo unset; }; f`, 1680 "unset\n", 1681 }, 1682 1683 // shopt 1684 {"set -e; shopt -o | grep -E 'errexit|noexec' | wc -l", "2\n"}, 1685 {"set -e; shopt -o | grep -E 'errexit|noexec' | grep 'on$' | wc -l", "1\n"}, 1686 {"shopt -s -o noexec; echo foo", ""}, 1687 {"shopt -u -o noexec; echo foo", "foo\n"}, 1688 {"shopt -u globstar; shopt globstar | grep 'off$' | wc -l", "1\n"}, 1689 {"shopt -s globstar; shopt globstar | grep 'off$' | wc -l", "0\n"}, 1690 1691 // IFS 1692 {`echo -n "$IFS"`, " \t\n"}, 1693 {`a="x:y:z"; IFS=:; echo $a`, "x y z\n"}, 1694 {`a=(x y z); IFS=-; echo "${a[*]}"`, "x-y-z\n"}, 1695 {`a=(x y z); IFS=-; echo "${a[@]}"`, "x y z\n"}, 1696 {`a=" x y z"; IFS=; echo $a`, " x y z\n"}, 1697 {`a=(x y z); IFS=; echo "${a[*]}"`, "xyz\n"}, 1698 {`a=(x y z); IFS=-; echo "${!a[@]}"`, "0 1 2\n"}, 1699 1700 // builtin 1701 {"builtin", ""}, 1702 {"builtin noexist", "exit status 1 #JUSTERR"}, 1703 {"builtin echo foo", "foo\n"}, 1704 { 1705 "echo() { printf 'bar\n'; }; echo foo; builtin echo foo", 1706 "bar\nfoo\n", 1707 }, 1708 1709 // type 1710 {"type", ""}, 1711 {"type echo", "echo is a shell builtin\n"}, 1712 {"echo() { :; }; type echo | sed 1q", "echo is a function\n"}, 1713 {"type bash | grep -q -E 'bash is (/|[A-Z]:).*'", ""}, 1714 {"type noexist", "type: noexist: not found\nexit status 1 #JUSTERR"}, 1715 1716 // eval 1717 {"eval", ""}, 1718 {"eval ''", ""}, 1719 {"eval echo foo", "foo\n"}, 1720 {"eval 'echo foo'", "foo\n"}, 1721 {"eval 'exit 1'", "exit status 1"}, 1722 {"eval '('", "eval: 1:1: reached EOF without matching ( with )\nexit status 1 #JUSTERR"}, 1723 {"set a b; eval 'echo $@'", "a b\n"}, 1724 {"eval 'a=foo'; echo $a", "foo\n"}, 1725 {`a=b eval "echo $a"`, "\n"}, 1726 {`a=b eval 'echo $a'`, "b\n"}, 1727 {`eval 'echo "\$a"'`, "$a\n"}, 1728 {`a=b eval 'x=y eval "echo \$a \$x"'`, "b y\n"}, 1729 {`a=b eval 'a=y eval "echo $a \$a"'`, "b y\n"}, 1730 {"a=b eval '(echo $a)'", "b\n"}, 1731 1732 // source 1733 { 1734 "source", 1735 "1:1: source: need filename\nexit status 2 #JUSTERR", 1736 }, 1737 { 1738 "echo 'echo foo' >a; source a; . a", 1739 "foo\nfoo\n", 1740 }, 1741 { 1742 "echo 'echo $@' >a; source a; source a b c; echo $@", 1743 "\nb c\n\n", 1744 }, 1745 { 1746 "echo 'foo=bar' >a; source a; echo $foo", 1747 "bar\n", 1748 }, 1749 1750 // indexed arrays 1751 { 1752 "a=foo; echo ${a[0]} ${a[@]} ${a[x]}; echo ${a[1]}", 1753 "foo foo foo\n\n", 1754 }, 1755 { 1756 "a=(); echo ${a[0]} ${a[@]} ${a[x]} ${a[1]}", 1757 "\n", 1758 }, 1759 { 1760 "a=(b c); echo $a; echo ${a[0]}; echo ${a[1]}; echo ${a[x]}", 1761 "b\nb\nc\nb\n", 1762 }, 1763 { 1764 "a=(b c); echo ${a[@]}; echo ${a[*]}", 1765 "b c\nb c\n", 1766 }, 1767 { 1768 "a=(1 2 3); echo ${a[2-1]}; echo $((a[1+1]))", 1769 "2\n3\n", 1770 }, 1771 { 1772 "a=(1 2) x=(); a+=b x+=c; echo ${a[@]}; echo ${x[@]}", 1773 "1b 2\nc\n", 1774 }, 1775 { 1776 "a=(1 2) x=(); a+=(b c) x+=(d e); echo ${a[@]}; echo ${x[@]}", 1777 "1 2 b c\nd e\n", 1778 }, 1779 { 1780 "a=bbb; a+=(c d); echo ${a[@]}", 1781 "bbb c d\n", 1782 }, 1783 { 1784 `a=('a 1' 'b 2'); for e in ${a[@]}; do echo "$e"; done`, 1785 "a\n1\nb\n2\n", 1786 }, 1787 { 1788 `a=('a 1' 'b 2'); for e in "${a[*]}"; do echo "$e"; done`, 1789 "a 1 b 2\n", 1790 }, 1791 { 1792 `a=('a 1' 'b 2'); for e in "${a[@]}"; do echo "$e"; done`, 1793 "a 1\nb 2\n", 1794 }, 1795 { 1796 `a=([1]=y [0]=x); echo ${a[0]}`, 1797 "x\n", 1798 }, 1799 { 1800 `a=(y); a[2]=x; echo ${a[2]}`, 1801 "x\n", 1802 }, 1803 { 1804 `a="y"; a[2]=x; echo ${a[2]}`, 1805 "x\n", 1806 }, 1807 { 1808 `declare -a a=(x y); echo ${a[1]}`, 1809 "y\n", 1810 }, 1811 { 1812 `a=b; echo "${a[@]}"`, 1813 "b\n", 1814 }, 1815 1816 // associative arrays 1817 { 1818 `a=foo; echo ${a[""]} ${a["x"]}`, 1819 "foo foo\n", 1820 }, 1821 { 1822 `declare -A a=(); echo ${a[0]} ${a[@]} ${a[1]} ${a["x"]}`, 1823 "\n", 1824 }, 1825 { 1826 `declare -A a=([x]=b [y]=c); echo $a; echo ${a[0]}; echo ${a["x"]}; echo ${a["_"]}`, 1827 "\n\nb\n\n", 1828 }, 1829 { 1830 `declare -A a=([x]=b [y]=c); echo ${a[@]}; echo ${a[*]}`, 1831 "b c\nb c\n", 1832 }, 1833 { 1834 `declare -A a=([y]=b [x]=c); echo ${a[@]}; echo ${a[*]}`, 1835 "c b\nc b\n", 1836 }, 1837 { 1838 `declare -A a=([x]=a); a["y"]=d; a["x"]=c; echo ${a[@]}`, 1839 "c d\n", 1840 }, 1841 { 1842 `declare -A a=([x]=a); a[y]=d; a[x]=c; echo ${a[@]}`, 1843 "c d\n", 1844 }, 1845 { 1846 // cheating a little; bash just did a=c 1847 `a=(["x"]=b ["y"]=c); echo ${a["y"]}`, 1848 "c\n", 1849 }, 1850 { 1851 `declare -A a=(['x']=b); echo ${a['x']} ${a[$'x']} ${a[$"x"]}`, 1852 "b b b\n", 1853 }, 1854 { 1855 `a=(['x']=b); echo ${a['y']}`, 1856 "\n #IGNORE bash requires -A", 1857 }, 1858 1859 // weird assignments 1860 {"a=b; a=(c d); echo ${a[@]}", "c d\n"}, 1861 {"a=(b c); a=d; echo ${a[@]}", "d c\n"}, 1862 {"declare -A a=([x]=b [y]=c); a=d; echo ${a[@]}", "d b c\n"}, 1863 {"i=3; a=b; a[i]=x; echo ${a[@]}", "b x\n"}, 1864 {"i=3; declare a=(b); a[i]=x; echo ${!a[@]}", "0 3\n"}, 1865 {"i=3; declare -A a=(['x']=b); a[i]=x; echo ${!a[@]}", "i x\n"}, 1866 1867 // declare 1868 {"declare -B foo", "declare: invalid option \"-B\"\nexit status 2 #JUSTERR"}, 1869 {"a=b; declare a; echo $a; declare a=; echo $a", "b\n\n"}, 1870 {"a=b; declare a; echo $a", "b\n"}, 1871 { 1872 "declare a=b c=(1 2); echo $a; echo ${c[@]}", 1873 "b\n1 2\n", 1874 }, 1875 {"a=x; declare $a; echo $a $x", "x\n"}, 1876 {"a=x=y; declare $a; echo $a $x", "x=y y\n"}, 1877 {"a='x=(y)'; declare $a; echo $a $x", "x=(y) (y)\n"}, 1878 {"a='x=b y=c'; declare $a; echo $x $y", "b c\n"}, 1879 {"declare =bar", "declare: invalid name \"=bar\"\nexit status 1 #JUSTERR"}, 1880 {"declare $unset=$unset", "declare: invalid name \"\"\nexit status 1 #JUSTERR"}, 1881 1882 // export 1883 {"declare foo=bar; env | grep '^foo='", "exit status 1"}, 1884 {"declare -x foo=bar; env | grep '^foo='", "foo=bar\n"}, 1885 {"export foo=bar; env | grep '^foo='", "foo=bar\n"}, 1886 {"foo=bar; export foo; env | grep '^foo='", "foo=bar\n"}, 1887 {"export foo=bar; foo=baz; env | grep '^foo='", "foo=baz\n"}, 1888 {"export foo=bar; readonly foo=baz; env | grep '^foo='", "foo=baz\n"}, 1889 {"export foo=(1 2); env | grep '^foo='", "exit status 1"}, 1890 {"declare -A foo=([a]=b); export foo; env | grep '^foo='", "exit status 1"}, 1891 {"export foo=(b c); foo=x; env | grep '^foo='", "exit status 1"}, 1892 1893 // local 1894 { 1895 "local a=b", 1896 "local: can only be used in a function\nexit status 1 #JUSTERR", 1897 }, 1898 { 1899 "local a=b 2>/dev/null; echo $a", 1900 "\n", 1901 }, 1902 { 1903 "{ local a=b; }", 1904 "local: can only be used in a function\nexit status 1 #JUSTERR", 1905 }, 1906 { 1907 "echo 'local a=b' >a; source a", 1908 "local: can only be used in a function\nexit status 1 #JUSTERR", 1909 }, 1910 { 1911 "echo 'local a=b' >a; f() { source a; }; f; echo $a", 1912 "\n", 1913 }, 1914 { 1915 "f() { local a=b; }; f; echo $a", 1916 "\n", 1917 }, 1918 { 1919 "a=x; f() { local a=b; }; f; echo $a", 1920 "x\n", 1921 }, 1922 { 1923 "a=x; f() { echo $a; local a=b; echo $a; }; f", 1924 "x\nb\n", 1925 }, 1926 { 1927 "f1() { local a=b; }; f2() { f1; echo $a; }; f2", 1928 "\n", 1929 }, 1930 { 1931 "f() { a=1; declare b=2; export c=3; readonly d=4; declare -g e=5; }; f; echo $a $b $c $d $e", 1932 "1 3 4 5\n", 1933 }, 1934 { 1935 `f() { local x; [[ -v x ]] && echo set || echo unset; }; f`, 1936 "unset\n", 1937 }, 1938 { 1939 `f() { local x=; [[ -v x ]] && echo set || echo unset; }; f`, 1940 "set\n", 1941 }, 1942 { 1943 `export x=before; f() { local x; export x=after; env | grep '^x='; }; f; echo $x`, 1944 "x=after\nbefore\n", 1945 }, 1946 1947 // name references 1948 {"declare -n foo=bar; bar=etc; [[ -R foo ]]", ""}, 1949 {"declare -n foo=bar; bar=etc; [ -R foo ]", ""}, 1950 {"nameref foo=bar; bar=etc; [[ -R foo ]]", " #IGNORE"}, 1951 {"declare foo=bar; bar=etc; [[ -R foo ]]", "exit status 1"}, 1952 { 1953 "declare -n foo=bar; bar=etc; echo $foo; bar=zzz; echo $foo", 1954 "etc\nzzz\n", 1955 }, 1956 { 1957 "declare -n foo=bar; bar=(x y); echo ${foo[1]}; bar=(a b); echo ${foo[1]}", 1958 "y\nb\n", 1959 }, 1960 { 1961 "declare -n foo=bar; bar=etc; echo $foo; unset bar; echo $foo", 1962 "etc\n\n", 1963 }, 1964 { 1965 "declare -n a1=a2 a2=a3 a3=a4; a4=x; echo $a1 $a3", 1966 "x x\n", 1967 }, 1968 { 1969 "declare -n foo=bar bar=foo; echo $foo", 1970 "\n #IGNORE", 1971 }, 1972 { 1973 "declare -n foo=bar; echo $foo", 1974 "\n", 1975 }, 1976 { 1977 "declare -n foo=bar; echo ${!foo}", 1978 "bar\n", 1979 }, 1980 { 1981 "declare -n foo=bar; bar=etc; echo $foo; echo ${!foo}", 1982 "etc\nbar\n", 1983 }, 1984 { 1985 "declare -n foo=bar; bar=etc; foo=xxx; echo $foo $bar", 1986 "xxx xxx\n", 1987 }, 1988 { 1989 "declare -n foo=bar; foo=xxx; echo $foo $bar", 1990 "xxx xxx\n", 1991 }, 1992 // TODO: figure this one out 1993 //{ 1994 // "declare -n foo=bar bar=baz; foo=xxx; echo $foo $bar; echo $baz", 1995 // "xxx xxx\nxxx\n", 1996 //}, 1997 1998 // read-only vars 1999 {"declare -r foo=bar; echo $foo", "bar\n"}, 2000 {"readonly foo=bar; echo $foo", "bar\n"}, 2001 { 2002 "a=b; a=c; echo $a; readonly a; a=d", 2003 "c\na: readonly variable\nexit status 1 #JUSTERR", 2004 }, 2005 { 2006 "declare -r foo=bar; foo=etc", 2007 "foo: readonly variable\nexit status 1 #JUSTERR", 2008 }, 2009 { 2010 "readonly foo=bar; foo=etc", 2011 "foo: readonly variable\nexit status 1 #JUSTERR", 2012 }, 2013 2014 // multiple var modes at once 2015 { 2016 "declare -r -x foo=bar; env | grep '^foo='", 2017 "foo=bar\n", 2018 }, 2019 { 2020 "declare -r -x foo=bar; foo=x", 2021 "foo: readonly variable\nexit status 1 #JUSTERR", 2022 }, 2023 2024 // globbing 2025 {"echo .", ".\n"}, 2026 {"echo ..", "..\n"}, 2027 {"echo ./.", "./.\n"}, 2028 { 2029 "touch a.x b.x c.x; echo *.x; rm a.x b.x c.x", 2030 "a.x b.x c.x\n", 2031 }, 2032 { 2033 `touch a.x; echo '*.x' "*.x"; rm a.x`, 2034 "*.x *.x\n", 2035 }, 2036 { 2037 `touch a.x b.y; echo *'.'x; rm a.x`, 2038 "a.x\n", 2039 }, 2040 { 2041 `touch a.x; echo *'.x' "a."* '*'.x; rm a.x`, 2042 "a.x a.x *.x\n", 2043 }, 2044 { 2045 "echo *.x; echo foo *.y bar", 2046 "*.x\nfoo *.y bar\n", 2047 }, 2048 { 2049 "mkdir a; touch a/b.x; echo */*.x | sed 's@\\\\@/@g'; cd a; echo *.x", 2050 "a/b.x\nb.x\n", 2051 }, 2052 { 2053 "mkdir -p a/b/c; echo a/* | sed 's@\\\\@/@g'", 2054 "a/b\n", 2055 }, 2056 { 2057 "mkdir -p '*/a.z' 'b/a.z'; cd '*'; set -- *.z; echo $#", 2058 "1\n", 2059 }, 2060 { 2061 "touch .hidden a; echo *; echo .h*; rm .hidden a", 2062 "a\n.hidden\n", 2063 }, 2064 { 2065 `mkdir d; touch d/.hidden d/a; set -- "$(echo d/*)" "$(echo d/.h*)"; echo ${#1} ${#2}; rm -r d`, 2066 "3 9\n", 2067 }, 2068 { 2069 "mkdir -p a/b/c; echo a/** | sed 's@\\\\@/@g'", 2070 "a/b\n", 2071 }, 2072 { 2073 "shopt -s globstar; mkdir -p a/b/c; echo a/** | sed 's@\\\\@/@g'", 2074 "a/ a/b a/b/c\n", 2075 }, 2076 { 2077 "shopt -s globstar; mkdir -p a/b/c; echo **/c | sed 's@\\\\@/@g'", 2078 "a/b/c\n", 2079 }, 2080 { 2081 "cat <<EOF\n{foo,bar}\nEOF", 2082 "{foo,bar}\n", 2083 }, 2084 { 2085 "cat <<EOF\n*.go\nEOF", 2086 "*.go\n", 2087 }, 2088 { 2089 "mkdir -p a/b a/c; echo ./a/* | sed 's@\\\\@/@g'", 2090 "./a/b ./a/c\n", 2091 }, 2092 { 2093 "mkdir -p a/b a/c d; cd d; echo ../a/* | sed 's@\\\\@/@g'", 2094 "../a/b ../a/c\n", 2095 }, 2096 { 2097 "mkdir x-d1 x-d2; touch x-f; echo x-*/ | sed -e 's@\\\\@/@g'", 2098 "x-d1/ x-d2/\n", 2099 }, 2100 { 2101 "mkdir x-d1 x-d2; touch x-f; echo ././x-*/// | sed -e 's@\\\\@/@g'", 2102 "././x-d1/ ././x-d2/\n", 2103 }, 2104 { 2105 "mkdir -p x-d1/a x-d2/b; touch x-f; echo x-*/* | sed -e 's@\\\\@/@g'", 2106 "x-d1/a x-d2/b\n", 2107 }, 2108 { 2109 "mkdir x-d; touch x-f; test -d $PWD/x-*/", 2110 "", 2111 }, 2112 2113 // brace expansion; more exhaustive tests in the syntax package 2114 {"echo a}b", "a}b\n"}, 2115 {"echo {a,b{c,d}", "{a,bc {a,bd\n"}, 2116 {"echo a{b}", "a{b}\n"}, 2117 {"echo a{à,世界}", "aà a世界\n"}, 2118 {"echo a{b,c}d{e,f}g", "abdeg abdfg acdeg acdfg\n"}, 2119 {"echo a{b{x,y},c}d", "abxd abyd acd\n"}, 2120 {"echo a{1..", "a{1..\n"}, 2121 {"echo a{1..2}b{4..5}c", "a1b4c a1b5c a2b4c a2b5c\n"}, 2122 {"echo a{c..f}", "ac ad ae af\n"}, 2123 {"echo a{4..1..1}", "a4 a3 a2 a1\n"}, 2124 2125 // tilde expansion 2126 { 2127 "[[ '~/foo' == ~/foo ]] || [[ ~/foo == '~/foo' ]]", 2128 "exit status 1", 2129 }, 2130 { 2131 "case '~/foo' in ~/foo) echo match ;; esac", 2132 "", 2133 }, 2134 { 2135 "a=~/foo; [[ $a == '~/foo' ]]", 2136 "exit status 1", 2137 }, 2138 { 2139 `a=$(echo "~/foo"); [[ $a == '~/foo' ]]`, 2140 "", 2141 }, 2142 2143 // /dev/null 2144 {"echo foo >/dev/null", ""}, 2145 {"cat </dev/null", ""}, 2146 2147 // time - real would be slow and flaky; see TestElapsedString 2148 {"{ time; } |& wc", " 4 6 42\n"}, 2149 {"{ time echo -n; } |& wc", " 4 6 42\n"}, 2150 {"{ time -p; } |& wc", " 3 6 29\n"}, 2151 {"{ time -p echo -n; } |& wc", " 3 6 29\n"}, 2152 2153 // exec 2154 {"exec", ""}, 2155 { 2156 "exec builtin echo foo", 2157 "\"builtin\": executable file not found in $PATH\nexit status 127 #JUSTERR", 2158 }, 2159 { 2160 "exec echo foo; echo bar", 2161 "foo\n", 2162 }, 2163 2164 // read 2165 { 2166 "read </dev/null", 2167 "exit status 1", 2168 }, 2169 { 2170 "read -X", 2171 "read: invalid option \"-X\"\nexit status 2 #JUSTERR", 2172 }, 2173 { 2174 "read 0ab", 2175 "read: invalid identifier \"0ab\"\nexit status 2 #JUSTERR", 2176 }, 2177 { 2178 "read <<< foo; echo $REPLY", 2179 "foo\n", 2180 }, 2181 { 2182 "read <<<' a b c '; echo \"$REPLY\"", 2183 " a b c \n", 2184 }, 2185 { 2186 "read <<< 'y\nn\n'; echo $REPLY", 2187 "y\n", 2188 }, 2189 { 2190 "read a_0 <<< foo; echo $a_0", 2191 "foo\n", 2192 }, 2193 { 2194 "read a b <<< 'foo bar baz '; echo \"$a\"; echo \"$b\"", 2195 "foo\nbar baz\n", 2196 }, 2197 { 2198 "while read a; do echo $a; done <<< 'a\nb\nc'", 2199 "a\nb\nc\n", 2200 }, 2201 { 2202 "while read a b; do echo -e \"$a\n$b\"; done <<< '1 2\n3'", 2203 "1\n2\n3\n\n", 2204 }, 2205 { 2206 `read a <<< '\\'; echo "$a"`, 2207 "\\\n", 2208 }, 2209 { 2210 `read a <<< '\a\b\c'; echo "$a"`, 2211 "abc\n", 2212 }, 2213 { 2214 "read -r a b <<< '1\\\t2'; echo $a; echo $b;", 2215 "1\\\n2\n", 2216 }, 2217 { 2218 "echo line\\\ncontinuation | while read a; do echo $a; done", 2219 "linecontinuation\n", 2220 }, 2221 { 2222 `read -r a <<< '\\'; echo "$a"`, 2223 "\\\\\n", 2224 }, 2225 { 2226 "read -r a <<< '\\a\\b\\c'; echo $a", 2227 "\\a\\b\\c\n", 2228 }, 2229 { 2230 "IFS=: read a b c <<< '1:2:3'; echo $a; echo $b; echo $c", 2231 "1\n2\n3\n", 2232 }, 2233 { 2234 "IFS=: read a b c <<< '1\\:2:3'; echo \"$a\"; echo $b; echo $c", 2235 "1:2\n3\n\n", 2236 }, 2237 2238 // getopts 2239 { 2240 "getopts", 2241 "getopts: usage: getopts optstring name [arg]\nexit status 2", 2242 }, 2243 { 2244 "getopts a a:b", 2245 "getopts: invalid identifier: \"a:b\"\nexit status 2 #JUSTERR", 2246 }, 2247 { 2248 "getopts abc opt -a; echo $opt; $optarg", 2249 "a\n", 2250 }, 2251 { 2252 "getopts abc opt -z", 2253 "getopts: illegal option -- \"z\"\n #IGNORE", 2254 }, 2255 { 2256 "getopts a: opt -a", 2257 "getopts: option requires an argument -- \"a\"\n #IGNORE", 2258 }, 2259 { 2260 "getopts :abc opt -z; echo $opt; echo $OPTARG", 2261 "?\nz\n", 2262 }, 2263 { 2264 "getopts :a: opt -a; echo $opt; echo $OPTARG", 2265 ":\na\n", 2266 }, 2267 { 2268 "getopts abc opt foo -a; echo $opt; echo $OPTIND", 2269 "?\n1\n", 2270 }, 2271 { 2272 "getopts abc opt -a foo; echo $opt; echo $OPTIND", 2273 "a\n2\n", 2274 }, 2275 { 2276 "OPTIND=3; getopts abc opt -a -b -c; echo $opt;", 2277 "c\n", 2278 }, 2279 { 2280 "OPTIND=100; getopts abc opt -a -b -c; echo $opt;", 2281 "?\n", 2282 }, 2283 { 2284 "OPTIND=foo; getopts abc opt -a -b -c; echo $opt;", 2285 "a\n", 2286 }, 2287 { 2288 "while getopts ab:c opt -c -b arg -a foo; do echo $opt $OPTARG $OPTIND; done", 2289 "c 2\nb arg 4\na 5\n", 2290 }, 2291 { 2292 "while getopts abc opt -ba -c foo; do echo $opt $OPTARG $OPTIND; done", 2293 "b 1\na 2\nc 3\n", 2294 }, 2295 { 2296 "a() { while getopts abc: opt; do echo $opt $OPTARG; done }; a -a -b -c arg", 2297 "a\nb\nc arg\n", 2298 }, 2299 } 2300 2301 // wc: leading whitespace padding 2302 // touch -d @: no way to set unix timestamps 2303 var skipOnDarwin = regexp.MustCompile(`\bwc\b|touch -d @`) 2304 2305 // chmod: very different by design 2306 // mkfifo: very different by design 2307 // ln -s: requires linked path to exist, stat does not work well 2308 // ~root: username does not exist 2309 // env: missing on Travis? TODO: investigate 2310 var skipOnWindows = regexp.MustCompile(`chmod|mkfifo|ln -s|~root|env`) 2311 2312 func skipIfUnsupported(tb testing.TB, src string) { 2313 switch { 2314 case runtime.GOOS == "darwin" && skipOnDarwin.MatchString(src): 2315 tb.Skipf("skipping non-portable test on darwin") 2316 case runtime.GOOS == "windows" && skipOnWindows.MatchString(src): 2317 tb.Skipf("skipping non-portable test on windows") 2318 } 2319 } 2320 2321 func TestFile(t *testing.T) { 2322 p := syntax.NewParser() 2323 for i := range fileCases { 2324 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 2325 c := fileCases[i] 2326 skipIfUnsupported(t, c.in) 2327 file, err := p.Parse(strings.NewReader(c.in), "") 2328 if err != nil { 2329 t.Fatalf("could not parse: %v", err) 2330 } 2331 t.Parallel() 2332 dir, err := ioutil.TempDir("", "interp-test") 2333 if err != nil { 2334 t.Fatal(err) 2335 } 2336 defer os.RemoveAll(dir) 2337 var cb concBuffer 2338 r, err := New(Dir(dir), StdIO(nil, &cb, &cb), 2339 Module(OpenDevImpls(DefaultOpen))) 2340 if err != nil { 2341 t.Fatal(err) 2342 } 2343 ctx := context.Background() 2344 if err := r.Run(ctx, file); err != nil && err != ShellExitStatus(0) { 2345 cb.WriteString(err.Error()) 2346 } 2347 want := c.want 2348 if i := strings.Index(want, " #"); i >= 0 { 2349 want = want[:i] 2350 } 2351 if got := cb.String(); got != want { 2352 t.Fatalf("wrong output in %q:\nwant: %q\ngot: %q", 2353 c.in, want, got) 2354 } 2355 }) 2356 } 2357 } 2358 2359 func TestFileConfirm(t *testing.T) { 2360 if testing.Short() { 2361 t.Skip("calling bash is slow") 2362 } 2363 if !hasBash44 { 2364 t.Skip("bash 4.4 required to run") 2365 } 2366 if runtime.GOOS == "windows" { 2367 // For example, it seems to treat environment variables as 2368 // case-sensitive, which isn't how Windows works. 2369 t.Skip("bash on Windows emulates Unix-y behavior") 2370 } 2371 for i := range fileCases { 2372 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 2373 c := fileCases[i] 2374 if strings.Contains(c.want, " #IGNORE") { 2375 return 2376 } 2377 skipIfUnsupported(t, c.in) 2378 t.Parallel() 2379 dir, err := ioutil.TempDir("", "interp-test") 2380 if err != nil { 2381 t.Fatal(err) 2382 } 2383 defer os.RemoveAll(dir) 2384 cmd := exec.Command("bash") 2385 cmd.Dir = dir 2386 cmd.Stdin = strings.NewReader(c.in) 2387 out, err := cmd.CombinedOutput() 2388 if strings.Contains(c.want, " #JUSTERR") { 2389 // bash sometimes exits with status code 0 and 2390 // stderr "bash: ..." for an error 2391 fauxErr := bytes.HasPrefix(out, []byte("bash:")) 2392 if err == nil && !fauxErr { 2393 t.Fatalf("wanted bash to error in %q", c.in) 2394 } 2395 return 2396 } 2397 got := string(out) 2398 if err != nil { 2399 got += err.Error() 2400 } 2401 if got != c.want { 2402 t.Fatalf("wrong bash output in %q:\nwant: %q\ngot: %q", 2403 c.in, c.want, got) 2404 } 2405 }) 2406 } 2407 } 2408 2409 func TestRunnerOpts(t *testing.T) { 2410 t.Parallel() 2411 withPath := func(strs ...string) func(*Runner) error { 2412 prefix := []string{"PATH=" + os.Getenv("PATH")} 2413 return Env(expand.ListEnviron(append(prefix, strs...)...)) 2414 } 2415 opts := func(list ...func(*Runner) error) []func(*Runner) error { 2416 return list 2417 } 2418 cases := []struct { 2419 opts []func(*Runner) error 2420 in, want string 2421 }{ 2422 { 2423 nil, 2424 "env | grep '^INTERP_GLOBAL='", 2425 "INTERP_GLOBAL=value\n", 2426 }, 2427 { 2428 opts(withPath()), 2429 "env | grep '^INTERP_GLOBAL='", 2430 "exit status 1", 2431 }, 2432 { 2433 opts(withPath("INTERP_GLOBAL=bar")), 2434 "env | grep '^INTERP_GLOBAL='", 2435 "INTERP_GLOBAL=bar\n", 2436 }, 2437 { 2438 opts(withPath("a=b")), 2439 "echo $a", 2440 "b\n", 2441 }, 2442 { 2443 opts(withPath("A=b")), 2444 "env | grep '^A='; echo $A", 2445 "A=b\nb\n", 2446 }, 2447 { 2448 opts(withPath("A=b", "A=c")), 2449 "env | grep '^A='; echo $A", 2450 "A=c\nc\n", 2451 }, 2452 { 2453 opts(withPath("HOME=")), 2454 "echo $HOME", 2455 "\n", 2456 }, 2457 { 2458 opts(withPath("PWD=foo")), 2459 "[[ $PWD == foo ]]", 2460 "exit status 1", 2461 }, 2462 { 2463 opts(Params("foo")), 2464 "echo $@", 2465 "foo\n", 2466 }, 2467 { 2468 opts(Params("-u", "--", "foo")), 2469 "echo $@; echo $unset", 2470 "foo\nunset: unbound variable\nexit status 1", 2471 }, 2472 } 2473 p := syntax.NewParser() 2474 for i, c := range cases { 2475 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 2476 skipIfUnsupported(t, c.in) 2477 file, err := p.Parse(strings.NewReader(c.in), "") 2478 if err != nil { 2479 t.Fatalf("could not parse: %v", err) 2480 } 2481 var cb concBuffer 2482 r, err := New(append(c.opts, StdIO(nil, &cb, &cb))...) 2483 if err != nil { 2484 t.Fatal(err) 2485 } 2486 ctx := context.Background() 2487 if err := r.Run(ctx, file); err != nil && err != ShellExitStatus(0) { 2488 cb.WriteString(err.Error()) 2489 } 2490 if got := cb.String(); got != c.want { 2491 t.Fatalf("wrong output in %q:\nwant: %q\ngot: %q", 2492 c.in, c.want, got) 2493 } 2494 }) 2495 } 2496 } 2497 2498 func TestRunnerContext(t *testing.T) { 2499 t.Parallel() 2500 cases := []string{ 2501 "", 2502 "while true; do true; done", 2503 "until false; do true; done", 2504 "sleep 1000", 2505 "while true; do true; done & wait", 2506 "sleep 1000 & wait", 2507 "(while true; do true; done)", 2508 "$(while true; do true; done)", 2509 "while true; do true; done | while true; do true; done", 2510 } 2511 p := syntax.NewParser() 2512 for i, in := range cases { 2513 t.Run(fmt.Sprintf("%03d", i), func(t *testing.T) { 2514 file, err := p.Parse(strings.NewReader(in), "") 2515 if err != nil { 2516 t.Fatalf("could not parse: %v", err) 2517 } 2518 ctx, cancel := context.WithCancel(context.Background()) 2519 cancel() 2520 r, _ := New() 2521 errChan := make(chan error) 2522 go func() { 2523 errChan <- r.Run(ctx, file) 2524 }() 2525 2526 select { 2527 case err := <-errChan: 2528 if err != nil && err != ctx.Err() { 2529 t.Fatal("Runner did not use ctx.Err()") 2530 } 2531 case <-time.After(time.Millisecond * 100): 2532 t.Fatal("program was not killed in 0.1s") 2533 } 2534 }) 2535 } 2536 } 2537 2538 func TestRunnerAltNodes(t *testing.T) { 2539 t.Parallel() 2540 in := "echo foo" 2541 want := "foo\n" 2542 file, err := syntax.NewParser().Parse(strings.NewReader(in), "") 2543 if err != nil { 2544 t.Fatalf("could not parse: %v", err) 2545 } 2546 nodes := []syntax.Node{ 2547 file, 2548 file.Stmts[0], 2549 file.Stmts[0].Cmd, 2550 } 2551 for _, node := range nodes { 2552 var cb concBuffer 2553 r, _ := New(StdIO(nil, &cb, &cb)) 2554 ctx := context.Background() 2555 if err := r.Run(ctx, node); err != nil && err != ShellExitStatus(0) { 2556 cb.WriteString(err.Error()) 2557 } 2558 if got := cb.String(); got != want { 2559 t.Fatalf("wrong output in %q:\nwant: %q\ngot: %q", 2560 in, want, got) 2561 } 2562 } 2563 } 2564 2565 func TestElapsedString(t *testing.T) { 2566 t.Parallel() 2567 tests := []struct { 2568 in time.Duration 2569 posix bool 2570 want string 2571 }{ 2572 {time.Nanosecond, false, "0m0.000s"}, 2573 {time.Millisecond, false, "0m0.001s"}, 2574 {time.Millisecond, true, "0.00"}, 2575 {2500 * time.Millisecond, false, "0m2.500s"}, 2576 {2500 * time.Millisecond, true, "2.50"}, 2577 { 2578 10*time.Minute + 10*time.Second, 2579 false, 2580 "10m10.000s", 2581 }, 2582 { 2583 10*time.Minute + 10*time.Second, 2584 true, 2585 "610.00", 2586 }, 2587 } 2588 for _, tc := range tests { 2589 t.Run(tc.in.String(), func(t *testing.T) { 2590 got := elapsedString(tc.in, tc.posix) 2591 if got != tc.want { 2592 t.Fatalf("wanted %q, got %q", tc.want, got) 2593 } 2594 }) 2595 } 2596 } 2597 2598 func TestRunnerDir(t *testing.T) { 2599 t.Parallel() 2600 dir, err := ioutil.TempDir("", "interp") 2601 if err != nil { 2602 t.Fatal(err) 2603 } 2604 defer os.Remove(dir) 2605 wd, err := os.Getwd() 2606 if err != nil { 2607 t.Fatal(err) 2608 } 2609 t.Run("Missing", func(t *testing.T) { 2610 _, err := New(Dir("missing")) 2611 if err == nil { 2612 t.Fatal("expected New to error when Dir is missing") 2613 } 2614 }) 2615 t.Run("NoDir", func(t *testing.T) { 2616 _, err := New(Dir("interp_test.go")) 2617 if err == nil { 2618 t.Fatal("expected New to error when Dir is not a dir") 2619 } 2620 }) 2621 t.Run("NoDirAbs", func(t *testing.T) { 2622 _, err := New(Dir(filepath.Join(wd, "interp_test.go"))) 2623 if err == nil { 2624 t.Fatal("expected New to error when Dir is not a dir") 2625 } 2626 }) 2627 t.Run("Relative", func(t *testing.T) { 2628 rel, err := filepath.Rel(wd, dir) 2629 if err != nil { 2630 t.Fatal(err) 2631 } 2632 r, err := New(Dir(rel)) 2633 if err != nil { 2634 t.Fatal(err) 2635 } 2636 if !filepath.IsAbs(r.Dir) { 2637 t.Errorf("Runner.Dir is not absolute") 2638 } 2639 }) 2640 } 2641 2642 func TestRunnerIncremental(t *testing.T) { 2643 t.Parallel() 2644 in := "echo foo; false; echo bar; exit 0; echo baz" 2645 want := "foo\nbar\n" 2646 file, err := syntax.NewParser().Parse(strings.NewReader(in), "") 2647 if err != nil { 2648 t.Fatalf("could not parse: %v", err) 2649 } 2650 var b bytes.Buffer 2651 r, _ := New(StdIO(nil, &b, &b)) 2652 ctx := context.Background() 2653 StmtLoop: 2654 for _, stmt := range file.Stmts { 2655 err := r.Run(ctx, stmt) 2656 switch err.(type) { 2657 case nil: 2658 case ExitStatus: 2659 case ShellExitStatus: 2660 break StmtLoop 2661 default: 2662 b.WriteString(err.Error()) 2663 } 2664 } 2665 if got := b.String(); got != want { 2666 t.Fatalf("\nwant: %q\ngot: %q", want, got) 2667 } 2668 } 2669 2670 func TestRunnerManyResets(t *testing.T) { 2671 t.Parallel() 2672 r, _ := New() 2673 for i := 0; i < 5; i++ { 2674 r.Reset() 2675 } 2676 } 2677 2678 func TestRunnerFilename(t *testing.T) { 2679 t.Parallel() 2680 in := "echo $0" 2681 want := "f.sh\n" 2682 file, err := syntax.NewParser().Parse(strings.NewReader(in), "f.sh") 2683 if err != nil { 2684 t.Fatalf("could not parse: %v", err) 2685 } 2686 var b bytes.Buffer 2687 r, _ := New(StdIO(nil, &b, &b)) 2688 ctx := context.Background() 2689 if err := r.Run(ctx, file); err != nil { 2690 t.Fatal(err) 2691 } 2692 if got := b.String(); got != want { 2693 t.Fatalf("\nwant: %q\ngot: %q", want, got) 2694 } 2695 } 2696 2697 func TestRunnerEnvNoModify(t *testing.T) { 2698 t.Parallel() 2699 env := expand.ListEnviron("one=1", "two=2") 2700 in := `echo -n "$one $two; "; one=x; unset two` 2701 file, err := syntax.NewParser().Parse(strings.NewReader(in), "") 2702 if err != nil { 2703 t.Fatalf("could not parse: %v", err) 2704 } 2705 2706 var b bytes.Buffer 2707 r, _ := New(Env(env), StdIO(nil, &b, &b)) 2708 ctx := context.Background() 2709 for i := 0; i < 3; i++ { 2710 r.Reset() 2711 err := r.Run(ctx, file) 2712 if err != nil { 2713 t.Fatal(err) 2714 } 2715 } 2716 2717 want := "1 2; 1 2; 1 2; " 2718 if got := b.String(); got != want { 2719 t.Fatalf("\nwant: %q\ngot: %q", want, got) 2720 } 2721 } 2722 2723 func TestMalformedPathOnWindows(t *testing.T) { 2724 if runtime.GOOS != "windows" { 2725 t.Skip("Skipping windows test on non-windows GOOS") 2726 } 2727 dir, err := ioutil.TempDir("", "interp-test") 2728 if err != nil { 2729 t.Fatal(err) 2730 } 2731 defer os.RemoveAll(dir) 2732 2733 path := filepath.Join(dir, "test.cmd") 2734 script := []byte("@echo foo") 2735 if err := ioutil.WriteFile(path, script, 0777); err != nil { 2736 t.Fatal(err) 2737 } 2738 2739 // set PATH to c:\tmp\dir instead of C:\tmp\dir 2740 volume := filepath.VolumeName(dir) 2741 pathList := strings.ToLower(volume) + dir[len(volume):] 2742 2743 file, _ := syntax.NewParser().Parse(strings.NewReader("test.cmd"), "") 2744 var cb concBuffer 2745 r, _ := New(Env(expand.ListEnviron("PATH="+pathList)), StdIO(nil, &cb, &cb)) 2746 if err := r.Run(context.Background(), file); err != nil { 2747 t.Fatal(err) 2748 } 2749 want := "foo\r\n" 2750 if got := cb.String(); got != want { 2751 t.Fatalf("wrong output:\nwant: %q\ngot: %q", want, got) 2752 } 2753 }