github.com/NeowayLabs/nash@v0.2.2-0.20200127205349-a227041ffd50/internal/sh/shell_test.go (about) 1 package sh_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "net" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "runtime" 12 "strconv" 13 "strings" 14 "testing" 15 "time" 16 17 // FIXME: depending on other sh package on the internal sh tests seems very odd 18 shtypes "github.com/madlambda/nash/sh" 19 20 "github.com/madlambda/nash/internal/sh" 21 "github.com/madlambda/nash/internal/sh/internal/fixture" 22 "github.com/madlambda/nash/tests" 23 ) 24 25 type ( 26 execTestCase struct { 27 desc string 28 code string 29 expectedStdout string 30 expectedStderr string 31 expectedErr string 32 expectedPrefixErr string 33 } 34 35 testFixture struct { 36 shell *sh.Shell 37 shellOut *bytes.Buffer 38 dir string 39 envDirs fixture.NashDirs 40 nashdPath string 41 } 42 ) 43 44 func TestInitEnv(t *testing.T) { 45 os.Setenv("TEST", "abc=123=") 46 47 f, teardown := setup(t) 48 defer teardown() 49 50 testEnv, ok := f.shell.Getenv("TEST") 51 if !ok { 52 t.Fatal("environment TEST not found") 53 } 54 expectedTestEnv := "abc=123=" 55 56 if testEnv.String() != expectedTestEnv { 57 t.Fatalf("Expected TEST Env differs: '%s' != '%s'", testEnv, expectedTestEnv) 58 } 59 } 60 61 func TestExecuteFile(t *testing.T) { 62 type fileTests struct { 63 path string 64 expected string 65 execBefore string 66 } 67 f, teardown := setup(t) 68 defer teardown() 69 70 for _, ftest := range []fileTests{ 71 {path: "/ex1.sh", expected: "hello world\n"}, 72 73 {path: "/sieve.sh", expected: "\n", execBefore: `var ARGS = ("" "0")`}, 74 {path: "/sieve.sh", expected: "\n", execBefore: `var ARGS = ("" "1")`}, 75 {path: "/sieve.sh", expected: "2 \n", execBefore: `var ARGS = ("" "2")`}, 76 {path: "/sieve.sh", expected: "2 3 \n", execBefore: `var ARGS = ("" "3")`}, 77 {path: "/sieve.sh", expected: "2 3 \n", execBefore: `var ARGS = ("" "4")`}, 78 {path: "/sieve.sh", expected: "2 3 5 \n", execBefore: `var ARGS = ("" "5")`}, 79 {path: "/sieve.sh", expected: "2 3 5 7 \n", execBefore: `var ARGS = ("" "10")`}, 80 81 {path: "/fibonacci.sh", expected: "1 \n", execBefore: `var ARGS = ("" "1")`}, 82 {path: "/fibonacci.sh", expected: "1 2 \n", execBefore: `var ARGS = ("" "2")`}, 83 {path: "/fibonacci.sh", expected: "1 2 3 \n", execBefore: `var ARGS = ("" "3")`}, 84 {path: "/fibonacci.sh", expected: "1 2 3 5 8 \n", execBefore: `var ARGS = ("" "5")`}, 85 } { 86 testExecuteFile(t, f.dir+ftest.path, ftest.expected, ftest.execBefore) 87 } 88 } 89 90 func TestExecuteCommand(t *testing.T) { 91 echopath, err := exec.LookPath("echo") 92 if err != nil { 93 t.Fatal(err) 94 } 95 96 echodir := filepath.Dir(echopath) 97 98 for _, test := range []execTestCase{ 99 { 100 desc: "command failed", 101 code: `non-existing-program`, 102 expectedStdout: "", 103 expectedStderr: "", 104 expectedPrefixErr: `exec: "non-existing-program": executable file not found in `, 105 }, 106 { 107 desc: "err ignored", 108 code: `-non-existing-program`, 109 expectedStdout: "", 110 expectedStderr: "", 111 expectedErr: "", 112 }, 113 { 114 desc: "hello world", 115 code: "echo -n hello world", 116 expectedStdout: "hello world", 117 expectedStderr: "", 118 expectedErr: "", 119 }, 120 { 121 desc: "cmd with concat", 122 code: `echo -n "hello " + "world"`, 123 expectedStdout: "hello world", 124 expectedStderr: "", 125 expectedErr: "", 126 }, 127 { 128 desc: "local command", 129 code: fmt.Sprintf(`var echodir = "%s" 130 chdir($echodir) 131 ./echo -n hello 132 `, strings.Replace(echodir, "\\", "\\\\", -1)), 133 expectedStdout: "hello", 134 expectedStderr: "", 135 expectedErr: "", 136 }, 137 } { 138 testExec(t, test) 139 } 140 } 141 142 func TestExecuteAssignment(t *testing.T) { 143 for _, test := range []execTestCase{ 144 { // wrong assignment 145 desc: "wrong assignment", 146 code: `var name=i4k`, 147 expectedStdout: "", 148 expectedStderr: "", 149 expectedErr: "wrong assignment:1:9: Unexpected token IDENT. Expecting VARIABLE, STRING or (", 150 }, 151 { 152 desc: "assignment", 153 code: `var name="i4k" 154 echo $name`, 155 expectedStdout: "i4k\n", 156 expectedStderr: "", 157 expectedErr: "", 158 }, 159 { 160 desc: "list assignment", 161 code: `var name=(honda civic) 162 echo -n $name`, 163 expectedStdout: "honda civic", 164 expectedStderr: "", 165 expectedErr: "", 166 }, 167 { 168 desc: "list of lists", 169 code: `var l = ( 170 (name Archlinux) 171 (arch amd64) 172 (kernel 4.7.1) 173 ) 174 175 echo $l[0] 176 echo $l[1] 177 echo -n $l[2]`, 178 expectedStdout: `name Archlinux 179 arch amd64 180 kernel 4.7.1`, 181 expectedStderr: "", 182 expectedErr: "", 183 }, 184 { 185 desc: "list assignment", 186 code: `var l = (0 1 2 3) 187 l[0] = "666" 188 echo -n $l`, 189 expectedStdout: `666 1 2 3`, 190 expectedStderr: "", 191 expectedErr: "", 192 }, 193 { 194 desc: "list assignment", 195 code: `var l = (0 1 2 3) 196 var a = "2" 197 l[$a] = "666" 198 echo -n $l`, 199 expectedStdout: `0 1 666 3`, 200 expectedStderr: "", 201 expectedErr: "", 202 }, 203 } { 204 testExec(t, test) 205 } 206 } 207 208 func TestExecuteMultipleAssignment(t *testing.T) { 209 for _, test := range []execTestCase{ 210 { 211 desc: "multiple assignment", 212 code: `var _1, _2 = "1", "2" 213 echo -n $_1 $_2`, 214 expectedStdout: "1 2", 215 expectedStderr: "", 216 expectedErr: "", 217 }, 218 { 219 desc: "multiple assignment", 220 code: `var _1, _2, _3 = "1", "2", "3" 221 echo -n $_1 $_2 $_3`, 222 expectedStdout: "1 2 3", 223 expectedStderr: "", 224 expectedErr: "", 225 }, 226 { 227 desc: "multiple assignment", 228 code: `var _1, _2 = (), () 229 echo -n $_1 $_2`, 230 expectedStdout: "", 231 expectedStderr: "", 232 expectedErr: "", 233 }, 234 { 235 desc: "multiple assignment", 236 code: `var _1, _2 = (1 2 3 4 5), (6 7 8 9 10) 237 echo -n $_1 $_2`, 238 expectedStdout: "1 2 3 4 5 6 7 8 9 10", 239 expectedStderr: "", 240 expectedErr: "", 241 }, 242 { 243 desc: "multiple assignment", 244 code: `var _1, _2, _3, _4, _5, _6, _7, _8, _9, _10 = "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" 245 echo -n $_1 $_2 $_3 $_4 $_5 $_6 $_7 $_8 $_9 $_10`, 246 expectedStdout: "1 2 3 4 5 6 7 8 9 10", 247 expectedStderr: "", 248 expectedErr: "", 249 }, 250 { 251 desc: "multiple assignment", 252 code: `var _1, _2 = (a b c), "d" 253 echo -n $_1 $_2`, 254 expectedStdout: "a b c d", 255 expectedStderr: "", 256 expectedErr: "", 257 }, 258 { 259 desc: "multiple assignment", 260 code: `fn a() { echo -n "a" } 261 fn b() { echo -n "b" } 262 var _a, _b = $a, $b 263 $_a(); $_b()`, 264 expectedStdout: "ab", 265 expectedStderr: "", 266 expectedErr: "", 267 }, 268 } { 269 testExec(t, test) 270 } 271 } 272 273 func TestExecuteCmdAssignment(t *testing.T) { 274 for _, test := range []execTestCase{ 275 { 276 desc: "cmd assignment", 277 code: `var name <= echo -n i4k 278 echo -n $name`, 279 expectedStdout: "i4k", 280 expectedStderr: "", 281 expectedErr: "", 282 }, 283 { 284 desc: "list cmd assignment", 285 code: `var name <= echo "honda civic" 286 echo -n $name`, 287 expectedStdout: "honda civic", 288 expectedStderr: "", 289 expectedErr: "", 290 }, 291 { 292 desc: "wrong cmd assignment", 293 code: `var name <= ""`, 294 expectedStdout: "", 295 expectedStderr: "", 296 expectedErr: "wrong cmd assignment:1:13: Invalid token STRING. Expected command or function invocation", 297 }, 298 { 299 desc: "fn must return value", 300 code: `fn e() {} 301 var v <= e()`, 302 expectedStdout: "", 303 expectedStderr: "", 304 expectedErr: "<interactive>:2:29: Functions returns 0 objects, but statement expects 1", 305 }, 306 { 307 desc: "list assignment", 308 code: `var l = (0 1 2 3) 309 l[0] <= echo -n 666 310 echo -n $l`, 311 expectedStdout: `666 1 2 3`, 312 expectedStderr: "", 313 expectedErr: "", 314 }, 315 { 316 desc: "list assignment", 317 code: `var l = (0 1 2 3) 318 var a = "2" 319 l[$a] <= echo -n "666" 320 echo -n $l`, 321 expectedStdout: `0 1 666 3`, 322 expectedStderr: "", 323 expectedErr: "", 324 }, 325 } { 326 testExec(t, test) 327 } 328 } 329 330 func TestExecuteCmdMultipleAssignment(t *testing.T) { 331 for _, test := range []execTestCase{ 332 { 333 desc: "cmd assignment", 334 code: `var name, err <= echo -n i4k 335 if $err == "0" { 336 echo -n $name 337 }`, 338 expectedStdout: "i4k", 339 expectedStderr: "", 340 expectedErr: "", 341 }, 342 { 343 desc: "list cmd assignment", 344 code: `var name, err2 <= echo "honda civic" 345 if $err2 == "0" { 346 echo -n $name 347 }`, 348 expectedStdout: "honda civic", 349 expectedStderr: "", 350 expectedErr: "", 351 }, 352 { 353 desc: "wrong cmd assignment", 354 code: `var name, err <= ""`, 355 expectedStdout: "", 356 expectedStderr: "", 357 expectedErr: "wrong cmd assignment:1:18: Invalid token STRING. Expected command or function invocation", 358 }, 359 { 360 desc: "fn must return value", 361 code: `fn e() {} 362 var v, err <= e()`, 363 expectedStdout: "", 364 expectedStderr: "", 365 expectedErr: "<interactive>:2:29: Functions returns 0 objects, but statement expects 2", 366 }, 367 { 368 desc: "list assignment", 369 code: `var l = (0 1 2 3) 370 var l[0], err <= echo -n 666 371 if $err == "0" { 372 echo -n $l 373 }`, 374 expectedStdout: `666 1 2 3`, 375 expectedStderr: "", 376 expectedErr: "", 377 }, 378 { 379 desc: "list assignment", 380 code: `var l = (0 1 2 3) 381 var a = "2" 382 var l[$a], err <= echo -n "666" 383 if $err == "0" { 384 echo -n $l 385 }`, 386 expectedStdout: `0 1 666 3`, 387 expectedStderr: "", 388 expectedErr: "", 389 }, 390 { 391 desc: "cmd assignment works with 1 or 2 variables", 392 code: "var out, err, status <= echo something", 393 expectedStdout: "", 394 expectedStderr: "", 395 expectedErr: "", 396 }, 397 { 398 desc: "ignore error", 399 code: `var out, _ <= cat /file-not-found/test >[2=] 400 echo -n $out`, 401 expectedStdout: "", 402 expectedStderr: "", 403 expectedErr: "", 404 }, 405 { 406 desc: "exec without '-' and getting status still fails", 407 code: `var out <= cat /file-not-found/test >[2=] 408 echo $out`, 409 expectedStdout: "", 410 expectedStderr: "", 411 expectedErr: "exit status 1", 412 }, 413 { 414 desc: "check status", 415 code: `var out, status <= cat /file-not-found/test >[2=] 416 if $status == "0" { 417 echo -n "must fail.. sniff" 418 } else if $status == "1" { 419 echo -n "it works" 420 } else { 421 echo -n "unexpected status:" $status 422 } 423 `, 424 expectedStdout: "it works", 425 expectedStderr: "", 426 expectedErr: "", 427 }, 428 { 429 desc: "multiple return in functions", 430 code: `fn fun() { 431 return "1", "2" 432 } 433 434 var a, b <= fun() 435 echo -n $a $b`, 436 expectedStdout: "1 2", 437 expectedStderr: "", 438 expectedErr: "", 439 }, 440 } { 441 testExec(t, test) 442 } 443 } 444 445 func TestExecuteRedirection(t *testing.T) { 446 f, teardown := setup(t) 447 defer teardown() 448 449 shell := f.shell 450 451 pathobj, err := ioutil.TempFile("", "nash-redir") 452 if err != nil { 453 t.Fatal(err) 454 } 455 path := strings.Replace(pathobj.Name(), "\\", "\\\\", -1) 456 defer os.Remove(path) 457 458 err = shell.Exec("redirect", fmt.Sprintf(` 459 echo -n "hello world" > %s 460 `, path)) 461 if err != nil { 462 t.Fatal(err) 463 } 464 465 content, err := ioutil.ReadFile(path) 466 if err != nil { 467 t.Fatal(err) 468 } 469 470 if string(content) != "hello world" { 471 t.Fatalf("File differ: '%s' != '%s'", string(content), "hello world") 472 } 473 474 // Test redirection truncate the file 475 err = shell.Exec("redirect", fmt.Sprintf(` 476 echo -n "a" > %s 477 `, path)) 478 if err != nil { 479 t.Fatal(err) 480 } 481 482 content, err = ioutil.ReadFile(path) 483 if err != nil { 484 t.Fatal(err) 485 } 486 487 if string(content) != "a" { 488 t.Fatalf("File differ: '%s' != '%s'", string(content), "a") 489 } 490 491 // Test redirection to variable 492 err = shell.Exec("redirect", ` 493 var location = "`+path+`" 494 echo -n "hello world" > $location 495 `) 496 497 if err != nil { 498 t.Fatal(err) 499 } 500 501 content, err = ioutil.ReadFile(path) 502 if err != nil { 503 t.Error(err) 504 return 505 } 506 507 if string(content) != "hello world" { 508 t.Errorf("File differ: '%s' != '%s'", string(content), "hello world") 509 return 510 } 511 512 // Test redirection to concat 513 err = shell.Exec("redirect", fmt.Sprintf(` 514 location = "%s" 515 var a = ".2" 516 echo -n "hello world" > $location+$a 517 `, path)) 518 if err != nil { 519 t.Fatal(err) 520 } 521 defer os.Remove(path + ".2") 522 content, err = ioutil.ReadFile(path + ".2") 523 if err != nil { 524 t.Fatal(err) 525 } 526 527 if string(content) != "hello world" { 528 t.Fatalf("File differ: '%s' != '%s'", string(content), "hello world") 529 } 530 } 531 532 func TestExecuteRedirectionMap(t *testing.T) { 533 f, teardown := setup(t) 534 defer teardown() 535 536 shell := f.shell 537 538 tmpfile, err := ioutil.TempFile("", "nash-redir-map") 539 if err != nil { 540 t.Fatal(err) 541 } 542 543 //path := strings.Replace(tmpfile.Name(), "\\", "\\\\", -1) 544 defer os.Remove(tmpfile.Name()) 545 546 err = shell.Exec("redirect map", fmt.Sprintf(` 547 echo -n "hello world" > %s 548 `, tmpfile.Name())) 549 if err != nil { 550 t.Error(err) 551 return 552 } 553 554 content, err := ioutil.ReadFile(tmpfile.Name()) 555 if err != nil { 556 t.Fatal(err) 557 } 558 559 if string(content) != "hello world" { 560 t.Fatalf("File differ: '%s' != '%s'", string(content), "hello world") 561 } 562 } 563 564 func TestExecuteSetenv(t *testing.T) { 565 f, teardown := setup(t) 566 defer teardown() 567 568 for _, test := range []execTestCase{ 569 { 570 desc: "test setenv basic", 571 code: `var setenvtest = "hello" 572 setenv setenvtest 573 ` + f.nashdPath + ` -c "echo $setenvtest"`, 574 expectedStdout: "hello\n", 575 expectedStderr: "", 576 expectedErr: "", 577 }, 578 { 579 desc: "test setenv assignment", 580 code: `setenv setenvtest = "hello" 581 ` + f.nashdPath + ` -c "echo $setenvtest"`, 582 expectedStdout: "hello\n", 583 expectedStderr: "", 584 expectedErr: "", 585 }, 586 { 587 desc: "test setenv exec cmd", 588 code: `setenv setenvtest <= echo -n "hello" 589 ` + f.nashdPath + ` -c "echo $setenvtest"`, 590 expectedStdout: "hello\n", 591 expectedStderr: "", 592 expectedErr: "", 593 }, 594 { 595 desc: "test setenv semicolon", 596 code: `setenv a setenv b`, 597 expectedStdout: "", 598 expectedStderr: "", 599 expectedErr: "test setenv semicolon:1:9: Unexpected token setenv, expected semicolon (;) or EOL", 600 }, 601 } { 602 testExec(t, test) 603 } 604 } 605 606 func TestExecuteCd(t *testing.T) { 607 tmpdir, err := ioutil.TempDir("", "nash-cd") 608 if err != nil { 609 t.Fatal(err) 610 } 611 tmpdir, err = filepath.EvalSymlinks(tmpdir) 612 if err != nil { 613 t.Fatal(err) 614 } 615 616 tmpdirEscaped := strings.Replace(tmpdir, "\\", "\\\\", -1) 617 homeEnvVar := "HOME" 618 if runtime.GOOS == "windows" { 619 homeEnvVar = "HOMEPATH" 620 621 // hack to use nash's pwd instead of gnu on windows 622 projectDir := filepath.FromSlash(tests.Projectpath) 623 pwdDir := filepath.Join(projectDir, "stdbin", "pwd") 624 path := os.Getenv("Path") 625 defer os.Setenv("Path", path) // TODO(i4k): very unsafe 626 os.Setenv("Path", pwdDir+";"+path) 627 } 628 629 for _, test := range []execTestCase{ 630 { 631 desc: "test cd 1", 632 code: fmt.Sprintf(`cd %s 633 pwd`, tmpdir), 634 expectedStdout: tmpdir + "\n", 635 expectedStderr: "", 636 expectedErr: "", 637 }, 638 { 639 desc: "test cd 2", 640 code: fmt.Sprintf(`%s = "%s" 641 setenv %s 642 cd 643 pwd`, homeEnvVar, tmpdirEscaped, homeEnvVar), 644 expectedStdout: tmpdir + "\n", 645 expectedStderr: "", 646 expectedErr: "", 647 }, 648 { 649 desc: "test cd into $var", 650 code: fmt.Sprintf(` 651 var v = "%s" 652 cd $v 653 pwd`, tmpdirEscaped), 654 expectedStdout: tmpdir + "\n", 655 expectedStderr: "", 656 expectedErr: "", 657 }, 658 } { 659 t.Run(test.desc, func(t *testing.T) { 660 test := test 661 testInteractiveExec(t, test) 662 }) 663 } 664 } 665 666 func TestExecuteImport(t *testing.T) { 667 f, teardown := setup(t) 668 defer teardown() 669 670 shell := f.shell 671 out := f.shellOut 672 673 tmpfile, err := ioutil.TempFile("", "nash-import") 674 if err != nil { 675 t.Fatal(err) 676 } 677 678 defer os.Remove(tmpfile.Name()) 679 680 _, err = tmpfile.Write([]byte(`var TESTE="teste"`)) 681 if err != nil { 682 t.Fatal(err) 683 } 684 685 fnameEscaped := strings.Replace(tmpfile.Name(), "\\", "\\\\", -1) 686 687 err = shell.Exec("test import", fmt.Sprintf(`import %s 688 echo $TESTE 689 `, fnameEscaped)) 690 if err != nil { 691 t.Error(err) 692 return 693 } 694 695 if strings.TrimSpace(string(out.Bytes())) != "teste" { 696 t.Error("Import does not work") 697 return 698 } 699 } 700 701 func TestExecuteIfEqual(t *testing.T) { 702 for _, test := range []execTestCase{ 703 { 704 desc: "if equal", 705 code: ` 706 if "" == "" { 707 echo "empty string works" 708 }`, 709 expectedStdout: "empty string works\n", 710 expectedStderr: "", 711 expectedErr: "", 712 }, 713 { 714 desc: "if equal", 715 code: ` 716 if "i4k" == "_i4k_" { 717 echo "do not print" 718 }`, 719 expectedStdout: "", 720 expectedStderr: "", 721 expectedErr: "", 722 }, 723 { 724 desc: "if lvalue concat", 725 code: ` 726 if "i4"+"k" == "i4k" { 727 echo -n "ok" 728 }`, 729 expectedStdout: "ok", 730 expectedStderr: "", 731 expectedErr: "", 732 }, 733 { 734 desc: "if lvalue concat", 735 code: `var name = "something" 736 if $name+"k" == "somethingk" { 737 echo -n "ok" 738 }`, 739 expectedStdout: "ok", 740 expectedStderr: "", 741 expectedErr: "", 742 }, 743 { 744 desc: "if lvalue concat", 745 code: `var name = "something" 746 if $name+"k"+"k" == "somethingkk" { 747 echo -n "ok" 748 }`, 749 expectedStdout: "ok", 750 expectedStderr: "", 751 expectedErr: "", 752 }, 753 { 754 desc: "if rvalue concat", 755 code: ` 756 if "i4k" == "i4"+"k" { 757 echo -n "ok" 758 }`, 759 expectedStdout: "ok", 760 expectedStderr: "", 761 expectedErr: "", 762 }, 763 { 764 desc: "if lvalue funcall", 765 code: `var a = () 766 if len($a) == "0" { 767 echo -n "ok" 768 }`, 769 expectedStdout: "ok", 770 expectedStderr: "", 771 expectedErr: "", 772 }, 773 { 774 desc: "if rvalue funcall", 775 code: `var a = ("1") 776 if "1" == len($a) { 777 echo -n "ok" 778 }`, 779 expectedStdout: "ok", 780 expectedStderr: "", 781 expectedErr: "", 782 }, 783 { 784 desc: "if lvalue funcall with concat", 785 code: `var a = () 786 if len($a)+"1" == "01" { 787 echo -n "ok" 788 }`, 789 expectedStdout: "ok", 790 expectedStderr: "", 791 expectedErr: "", 792 }, 793 } { 794 testExec(t, test) 795 } 796 } 797 798 func TestExecuteIfElse(t *testing.T) { 799 f, teardown := setup(t) 800 defer teardown() 801 802 shell := f.shell 803 out := f.shellOut 804 805 err := shell.Exec("test if else", ` 806 if "" == "" { 807 echo "if still works" 808 } else { 809 echo "nop" 810 }`) 811 if err != nil { 812 t.Error(err) 813 return 814 } 815 816 if strings.TrimSpace(string(out.Bytes())) != "if still works" { 817 t.Errorf("'%s' != 'if still works'", strings.TrimSpace(string(out.Bytes()))) 818 return 819 } 820 821 out.Reset() 822 823 err = shell.Exec("test if equal 2", ` 824 if "i4k" == "_i4k_" { 825 echo "do not print" 826 } else { 827 echo "print this" 828 }`) 829 830 if err != nil { 831 t.Error(err) 832 return 833 } 834 835 if strings.TrimSpace(string(out.Bytes())) != "print this" { 836 t.Errorf("Error: '%s' != 'print this'", strings.TrimSpace(string(out.Bytes()))) 837 return 838 } 839 } 840 841 func TestExecuteIfElseIf(t *testing.T) { 842 f, teardown := setup(t) 843 defer teardown() 844 845 shell := f.shell 846 out := f.shellOut 847 848 err := shell.Exec("test if else", ` 849 if "" == "" { 850 echo "if still works" 851 } else if "bleh" == "bloh" { 852 echo "nop" 853 }`) 854 855 if err != nil { 856 t.Error(err) 857 return 858 } 859 860 if strings.TrimSpace(string(out.Bytes())) != "if still works" { 861 t.Errorf("'%s' != 'if still works'", strings.TrimSpace(string(out.Bytes()))) 862 return 863 } 864 865 out.Reset() 866 867 err = shell.Exec("test if equal 2", ` 868 if "i4k" == "_i4k_" { 869 echo "do not print" 870 } else if "a" != "b" { 871 echo "print this" 872 }`) 873 874 if err != nil { 875 t.Error(err) 876 return 877 } 878 879 if strings.TrimSpace(string(out.Bytes())) != "print this" { 880 t.Errorf("Error: '%s' != 'print this'", strings.TrimSpace(string(out.Bytes()))) 881 return 882 } 883 } 884 885 func TestExecuteFnDecl(t *testing.T) { 886 f, teardown := setup(t) 887 defer teardown() 888 889 err := f.shell.Exec("test fnDecl", ` 890 fn build(image, debug) { 891 ls 892 }`) 893 if err != nil { 894 t.Error(err) 895 return 896 } 897 } 898 899 func TestExecuteFnInv(t *testing.T) { 900 f, teardown := setup(t) 901 defer teardown() 902 903 shell := f.shell 904 out := f.shellOut 905 906 err := shell.Exec("test fn inv", ` 907 fn getints() { 908 return ("1" "2" "3" "4" "5" "6" "7" "8" "9" "0") 909 } 910 911 var integers <= getints() 912 echo -n $integers 913 `) 914 if err != nil { 915 t.Error(err) 916 return 917 } 918 919 if string(out.Bytes()) != "1 2 3 4 5 6 7 8 9 0" { 920 t.Errorf("'%s' != '%s'", string(out.Bytes()), "1 2 3 4 5 6 7 8 9 0") 921 return 922 } 923 924 out.Reset() 925 926 // Test fn scope 927 err = shell.Exec("test fn inv", ` 928 var OUTSIDE = "some value" 929 930 fn getOUTSIDE() { 931 return $OUTSIDE 932 } 933 934 var val <= getOUTSIDE() 935 echo -n $val 936 `) 937 if err != nil { 938 t.Error(err) 939 return 940 } 941 942 if string(out.Bytes()) != "some value" { 943 t.Errorf("'%s' != '%s'", string(out.Bytes()), "some value") 944 return 945 } 946 947 err = shell.Exec("test fn inv", ` 948 fn notset() { 949 var INSIDE = "camshaft" 950 } 951 952 notset() 953 echo -n $INSIDE 954 `) 955 if err == nil { 956 t.Error("Must fail") 957 return 958 } 959 960 out.Reset() 961 962 // test variables shadow the global ones 963 err = shell.Exec("test shadow", `var _path="AAA" 964 fn test(_path) { 965 echo -n $_path 966 } 967 test("BBB") 968 `) 969 970 if string(out.Bytes()) != "BBB" { 971 t.Errorf("String differs: '%s' != '%s'", string(out.Bytes()), "BBB") 972 return 973 } 974 975 out.Reset() 976 977 err = shell.Exec("test shadow", ` 978 fn test(_path) { 979 echo -n $_path 980 } 981 982 _path="AAA" 983 test("BBB") 984 `) 985 986 if string(out.Bytes()) != "BBB" { 987 t.Errorf("String differs: '%s' != '%s'", string(out.Bytes()), "BBB") 988 return 989 } 990 991 out.Reset() 992 err = shell.Exec("test fn list arg", ` 993 var ids_luns = () 994 var id = "1" 995 var lun = "lunar" 996 var ids_luns <= append($ids_luns, ($id $lun)) 997 print(len($ids_luns))`) 998 if err != nil { 999 t.Error(err) 1000 return 1001 } 1002 1003 got := string(out.Bytes()) 1004 expected := "1" 1005 if got != expected { 1006 t.Fatalf("String differs: '%s' != '%s'", got, expected) 1007 } 1008 1009 } 1010 1011 func TestFnComposition(t *testing.T) { 1012 for _, test := range []execTestCase{ 1013 { 1014 desc: "composition", 1015 code: ` 1016 fn a(b) { echo -n $b } 1017 fn b() { return "hello" } 1018 a(b()) 1019 `, 1020 expectedStdout: "hello", 1021 expectedStderr: "", 1022 expectedErr: "", 1023 }, 1024 { 1025 desc: "composition", 1026 code: ` 1027 fn a(b, c) { echo -n $b $c } 1028 fn b() { return "hello" } 1029 fn c() { return "world" } 1030 a(b(), c()) 1031 `, 1032 expectedStdout: "hello world", 1033 expectedStderr: "", 1034 expectedErr: "", 1035 }, 1036 } { 1037 testExec(t, test) 1038 } 1039 } 1040 1041 func TestExecuteFnInvOthers(t *testing.T) { 1042 f, teardown := setup(t) 1043 defer teardown() 1044 1045 shell := f.shell 1046 out := f.shellOut 1047 1048 err := shell.Exec("test fn inv", ` 1049 fn _getints() { 1050 return ("1" "2" "3" "4" "5" "6" "7" "8" "9" "0") 1051 } 1052 1053 fn getints() { 1054 var values <= _getints() 1055 1056 return $values 1057 } 1058 1059 var integers <= getints() 1060 echo -n $integers 1061 `) 1062 if err != nil { 1063 t.Error(err) 1064 return 1065 } 1066 1067 if string(out.Bytes()) != "1 2 3 4 5 6 7 8 9 0" { 1068 t.Errorf("'%s' != '%s'", string(out.Bytes()), "1 2 3 4 5 6 7 8 9 0") 1069 return 1070 } 1071 } 1072 1073 func TestNonInteractive(t *testing.T) { 1074 f, teardown := setup(t) 1075 defer teardown() 1076 1077 shell := f.shell 1078 1079 shell.SetInteractive(true) 1080 1081 testShellExec(t, shell, execTestCase{ 1082 desc: "test bindfn interactive", 1083 code: ` 1084 fn greeting() { 1085 echo "Hello" 1086 } 1087 1088 bindfn greeting hello`, 1089 }) 1090 1091 shell.SetInteractive(false) 1092 // FIXME: using private stuff on tests ? 1093 // shell.filename = "<non-interactive>" 1094 t.Skip("FIXME: TEST USES PRIVATE STUFF") 1095 1096 expectedErr := "<non-interactive>:1:0: " + 1097 "'hello' is a bind to 'greeting'." + 1098 " No binds allowed in non-interactive mode." 1099 1100 testShellExec(t, shell, execTestCase{ 1101 desc: "test 'binded' function non-interactive", 1102 code: `hello`, 1103 expectedStdout: "", 1104 expectedStderr: "", 1105 expectedErr: expectedErr, 1106 }) 1107 1108 expectedErr = "<non-interactive>:6:8: 'bindfn' is not allowed in" + 1109 " non-interactive mode." 1110 1111 testShellExec(t, shell, 1112 execTestCase{ 1113 desc: "test bindfn non-interactive", 1114 code: ` 1115 fn goodbye() { 1116 echo "Ciao" 1117 } 1118 1119 bindfn goodbye ciao`, 1120 expectedStdout: "", 1121 expectedStderr: "", 1122 expectedErr: expectedErr, 1123 }) 1124 } 1125 1126 func TestExecuteBindFn(t *testing.T) { 1127 for _, test := range []execTestCase{ 1128 { 1129 desc: "test bindfn", 1130 code: ` 1131 fn cd() { 1132 echo "override builtin cd" 1133 } 1134 1135 bindfn cd cd 1136 cd 1137 `, 1138 expectedStdout: "override builtin cd\n", 1139 }, 1140 { 1141 desc: "test bindfn vargs", 1142 code: ` 1143 fn echoargs(args...) { 1144 for a in $args { 1145 echo $a 1146 } 1147 } 1148 1149 bindfn echoargs echoargs 1150 echoargs 1151 echoargs "a" 1152 echoargs "b" "c" 1153 `, 1154 expectedStdout: "a\nb\nc\n", 1155 }, 1156 { 1157 desc: "test empty bindfn vargs len", 1158 code: ` 1159 fn echoargs(args...) { 1160 var l <= len($args) 1161 echo $l 1162 } 1163 1164 bindfn echoargs echoargs 1165 echoargs 1166 `, 1167 expectedStdout: "0\n", 1168 }, 1169 { 1170 desc: "test bindfn args", 1171 code: ` 1172 fn foo(line) { 1173 echo $line 1174 } 1175 1176 bindfn foo bar 1177 bar test test 1178 `, 1179 expectedErr: "Wrong number of arguments for function foo. Expected 1 but found 2", 1180 }, 1181 } { 1182 t.Run(test.desc, func(t *testing.T) { 1183 testInteractiveExec(t, test) 1184 }) 1185 } 1186 } 1187 1188 func TestExecutePipe(t *testing.T) { 1189 var stderr bytes.Buffer 1190 var stdout bytes.Buffer 1191 1192 f, teardown := setup(t) 1193 defer teardown() 1194 1195 // Case 1 1196 cmd := exec.Command(f.nashdPath, "-c", `echo hello | tr -d "[:space:]"`) 1197 1198 cmd.Stderr = &stderr 1199 cmd.Stdout = &stdout 1200 1201 err := cmd.Run() 1202 1203 if err != nil { 1204 t.Errorf("Unexpected error: %s", err.Error()) 1205 } 1206 1207 expectedOutput := "hello" 1208 actualOutput := string(stdout.Bytes()) 1209 1210 if actualOutput != expectedOutput { 1211 t.Errorf("'%s' != '%s'", actualOutput, expectedOutput) 1212 return 1213 } 1214 stdout.Reset() 1215 stderr.Reset() 1216 1217 // Case 2 1218 cmd = exec.Command(f.nashdPath, "-c", `echo hello | wc -l | tr -d "[:space:]"`) 1219 1220 cmd.Stderr = &stderr 1221 cmd.Stdout = &stdout 1222 1223 err = cmd.Run() 1224 1225 if err != nil { 1226 t.Errorf("Unexpected error: %s", err.Error()) 1227 } 1228 1229 expectedOutput = "1" 1230 actualOutput = string(stdout.Bytes()) 1231 1232 if actualOutput != expectedOutput { 1233 t.Errorf("'%s' != '%s'", actualOutput, expectedOutput) 1234 return 1235 } 1236 } 1237 1238 func TestExecuteRedirectionPipe(t *testing.T) { 1239 f, teardown := setup(t) 1240 defer teardown() 1241 1242 err := f.shell.Exec("test", `cat stuff >[2=] | grep file`) 1243 expectedErr := "<interactive>:1:16: exit status 1|success" 1244 1245 if err == nil { 1246 t.Fatalf("expected err[%s]", expectedErr) 1247 } 1248 1249 if err.Error() != expectedErr { 1250 t.Errorf("Expected stderr to be '%s' but got '%s'", 1251 expectedErr, 1252 err.Error()) 1253 return 1254 } 1255 } 1256 1257 func testTCPRedirection(t *testing.T, port, command string) { 1258 message := "hello world" 1259 done := make(chan error) 1260 1261 l, err := net.Listen("tcp", port) 1262 if err != nil { 1263 t.Fatal(err) 1264 } 1265 defer l.Close() 1266 1267 go func() { 1268 f, teardown := setup(t) 1269 defer teardown() 1270 1271 err := <-done 1272 if err != nil { 1273 t.Fatal(err) 1274 } 1275 1276 done <- f.shell.Exec("test net redirection", command) 1277 }() 1278 1279 done <- nil // synchronize peers 1280 conn, err := l.Accept() 1281 if err != nil { 1282 done <- err 1283 t.Fatal(err) 1284 } 1285 1286 defer conn.Close() 1287 err = <-done 1288 if err != nil { 1289 t.Fatal(err) 1290 } 1291 1292 buf, err := ioutil.ReadAll(conn) 1293 if err != nil { 1294 t.Fatal(err) 1295 } 1296 1297 if msg := string(buf[:]); msg != message { 1298 t.Fatalf("Unexpected message:\nGot:\t\t%s\nExpected:\t%s\n", msg, message) 1299 } 1300 } 1301 1302 func TestTCPRedirection(t *testing.T) { 1303 testTCPRedirection(t, ":4666", `echo -n "hello world" >[1] "tcp://localhost:4666"`) 1304 testTCPRedirection(t, ":4667", `echo -n "hello world" > "tcp://localhost:4667"`) 1305 } 1306 1307 func TestExecuteUnixRedirection(t *testing.T) { 1308 if runtime.GOOS == "windows" { 1309 t.Skip("windows does not support unix socket") 1310 return 1311 } 1312 message := "hello world" 1313 1314 sockDir, err := ioutil.TempDir("", "nash-tests") 1315 if err != nil { 1316 t.Error(err) 1317 return 1318 } 1319 1320 sockFile := sockDir + "/listen.sock" 1321 1322 defer func() { 1323 os.Remove(sockFile) 1324 os.RemoveAll(sockDir) 1325 }() 1326 1327 done := make(chan bool) 1328 writeDone := make(chan bool) 1329 1330 go func() { 1331 1332 f, teardown := setup(t) 1333 defer teardown() 1334 1335 defer func() { 1336 writeDone <- true 1337 }() 1338 1339 <-done 1340 1341 err = f.shell.Exec("test net redirection", `echo -n "`+message+`" >[1] "unix://`+sockFile+`"`) 1342 1343 if err != nil { 1344 t.Error(err) 1345 return 1346 } 1347 }() 1348 1349 l, err := net.Listen("unix", sockFile) 1350 1351 if err != nil { 1352 t.Error(err) 1353 return 1354 } 1355 1356 defer l.Close() 1357 1358 go func() { 1359 conn, err := l.Accept() 1360 if err != nil { 1361 return 1362 } 1363 1364 defer conn.Close() 1365 1366 buf, err := ioutil.ReadAll(conn) 1367 1368 if err != nil { 1369 t.Fatal(err) 1370 } 1371 1372 fmt.Println(string(buf[:])) 1373 1374 if msg := string(buf[:]); msg != message { 1375 t.Fatalf("Unexpected message:\nGot:\t\t%s\nExpected:\t%s\n", msg, message) 1376 } 1377 1378 return // Done 1379 }() 1380 1381 done <- true 1382 <-writeDone 1383 } 1384 1385 func TestExecuteUDPRedirection(t *testing.T) { 1386 message := "hello world" 1387 1388 done := make(chan bool) 1389 writeDone := make(chan bool) 1390 1391 go func() { 1392 f, teardown := setup(t) 1393 defer teardown() 1394 1395 defer func() { 1396 writeDone <- true 1397 }() 1398 1399 <-done 1400 1401 err := f.shell.Exec("test net redirection", `echo -n "`+message+`" >[1] "udp://localhost:6667"`) 1402 1403 if err != nil { 1404 t.Error(err) 1405 return 1406 } 1407 }() 1408 1409 serverAddr, err := net.ResolveUDPAddr("udp", ":6667") 1410 1411 if err != nil { 1412 t.Error(err) 1413 return 1414 } 1415 1416 l, err := net.ListenUDP("udp", serverAddr) 1417 1418 if err != nil { 1419 t.Fatal(err) 1420 } 1421 1422 go func() { 1423 defer l.Close() 1424 1425 buf := make([]byte, 1024) 1426 1427 nb, _, err := l.ReadFromUDP(buf) 1428 1429 if err != nil { 1430 t.Error(err) 1431 return 1432 } 1433 1434 received := string(buf[:nb]) 1435 1436 if received != message { 1437 t.Errorf("Unexpected message:\nGot:\t\t'%s'\nExpected:\t'%s'\n", received, message) 1438 } 1439 }() 1440 1441 time.Sleep(time.Second * 1) 1442 1443 done <- true 1444 <-writeDone 1445 } 1446 1447 func TestExecuteReturn(t *testing.T) { 1448 for _, test := range []execTestCase{ 1449 { 1450 desc: "return invalid", 1451 code: `return`, 1452 expectedStdout: "", 1453 expectedStderr: "", 1454 expectedErr: "<interactive>:1:0: Unexpected return outside of function declaration.", 1455 }, 1456 { 1457 desc: "test simple return", 1458 code: `fn test() { return } 1459 test()`, 1460 expectedStdout: "", 1461 expectedStderr: "", 1462 expectedErr: "", 1463 }, 1464 { 1465 desc: "return must finish func evaluation", 1466 code: `fn test() { 1467 if "1" == "1" { 1468 return "1" 1469 } 1470 1471 return "0" 1472 } 1473 1474 var res <= test() 1475 echo -n $res`, 1476 expectedStdout: "1", 1477 expectedStderr: "", 1478 expectedErr: "", 1479 }, 1480 { 1481 desc: "ret from for", 1482 code: `fn test() { 1483 var values = (0 1 2 3 4 5 6 7 8 9) 1484 1485 for i in $values { 1486 if $i == "5" { 1487 return $i 1488 } 1489 } 1490 1491 return "0" 1492 } 1493 var a <= test() 1494 echo -n $a`, 1495 expectedStdout: "5", 1496 expectedStderr: "", 1497 expectedErr: "", 1498 }, 1499 { 1500 desc: "inf loop ret", 1501 code: `fn test() { 1502 for { 1503 if "1" == "1" { 1504 return "1" 1505 } 1506 } 1507 1508 # never happen 1509 return "bleh" 1510 } 1511 var a <= test() 1512 echo -n $a`, 1513 expectedStdout: "1", 1514 expectedStderr: "", 1515 expectedErr: "", 1516 }, 1517 { 1518 desc: "test returning funcall", 1519 code: `fn a() { return "1" } 1520 fn b() { return a() } 1521 var c <= b() 1522 echo -n $c`, 1523 expectedStdout: "1", 1524 expectedStderr: "", 1525 expectedErr: "", 1526 }, 1527 } { 1528 testExec(t, test) 1529 } 1530 } 1531 1532 func TestExecuteFnAsFirstClass(t *testing.T) { 1533 1534 f, teardown := setup(t) 1535 defer teardown() 1536 1537 shell := f.shell 1538 out := f.shellOut 1539 1540 err := shell.Exec("test fn by arg", ` 1541 fn printer(val) { 1542 echo -n $val 1543 } 1544 1545 fn success(print, val) { 1546 $print("[SUCCESS] " + $val) 1547 } 1548 1549 success($printer, "Command executed!") 1550 `) 1551 1552 if err != nil { 1553 t.Error(err) 1554 return 1555 } 1556 1557 expected := `[SUCCESS] Command executed!` 1558 1559 if expected != string(out.Bytes()) { 1560 t.Errorf("Differs: '%s' != '%s'", expected, string(out.Bytes())) 1561 return 1562 } 1563 } 1564 1565 func TestExecuteConcat(t *testing.T) { 1566 f, teardown := setup(t) 1567 defer teardown() 1568 1569 shell := f.shell 1570 out := f.shellOut 1571 1572 err := shell.Exec("", `var a = "A" 1573 var b = "B" 1574 var c = $a + $b + "C" 1575 echo -n $c`) 1576 1577 if err != nil { 1578 t.Error(err) 1579 return 1580 } 1581 1582 if string(out.Bytes()) != "ABC" { 1583 t.Errorf("Must be equal. '%s' != '%s'", string(out.Bytes()), "ABC") 1584 return 1585 } 1586 1587 out.Reset() 1588 1589 err = shell.Exec("concat indexed var", `var tag = (Name some) 1590 echo -n "Key="+$tag[0]+",Value="+$tag[1]`) 1591 1592 if err != nil { 1593 t.Error(err) 1594 return 1595 } 1596 1597 expected := "Key=Name,Value=some" 1598 1599 if expected != string(out.Bytes()) { 1600 t.Errorf("String differs: '%s' != '%s'", expected, string(out.Bytes())) 1601 return 1602 } 1603 } 1604 1605 func TestExecuteFor(t *testing.T) { 1606 f, teardown := setup(t) 1607 defer teardown() 1608 1609 shell := f.shell 1610 out := f.shellOut 1611 1612 err := shell.Exec("simple loop", `var files = (/etc/passwd /etc/shells) 1613 for f in $files { 1614 echo $f 1615 echo "loop" 1616 }`) 1617 1618 if err != nil { 1619 t.Error(err) 1620 return 1621 } 1622 1623 expected := `/etc/passwd 1624 loop 1625 /etc/shells 1626 loop` 1627 value := strings.TrimSpace(string(out.Bytes())) 1628 1629 if value != expected { 1630 t.Errorf("String differs: '%s' != '%s'", expected, value) 1631 return 1632 } 1633 1634 } 1635 1636 func TestExecuteInfiniteLoop(t *testing.T) { 1637 f, teardown := setup(t) 1638 defer teardown() 1639 1640 shell := f.shell 1641 1642 doneCtrlc := make(chan bool) 1643 doneLoop := make(chan bool) 1644 1645 go func() { 1646 fmt.Printf("Waiting 2 second to abort infinite loop") 1647 time.Sleep(2 * time.Second) 1648 1649 err := shell.TriggerCTRLC() 1650 if err != nil { 1651 t.Fatal(err) 1652 } 1653 doneCtrlc <- true 1654 }() 1655 1656 go func() { 1657 err := shell.Exec("simple loop", `for { 1658 echo "infinite loop" >[1=] 1659 sleep 1 1660 }`) 1661 doneLoop <- true 1662 1663 if err == nil { 1664 t.Errorf("Must fail with interrupted error") 1665 return 1666 } 1667 1668 type interrupted interface { 1669 Interrupted() bool 1670 } 1671 1672 if errInterrupted, ok := err.(interrupted); !ok || !errInterrupted.Interrupted() { 1673 t.Errorf("Loop not interrupted properly") 1674 return 1675 } 1676 }() 1677 1678 for i := 0; i < 2; i++ { 1679 select { 1680 case <-doneCtrlc: 1681 fmt.Printf("CTRL-C Sent to subshell\n") 1682 case <-doneLoop: 1683 fmt.Printf("Loop finished.\n") 1684 case <-time.After(5 * time.Second): 1685 t.Errorf("Failed to stop infinite loop") 1686 return 1687 } 1688 } 1689 } 1690 1691 func TestExecuteVariableIndexing(t *testing.T) { 1692 f, teardown := setup(t) 1693 defer teardown() 1694 1695 shell := f.shell 1696 out := f.shellOut 1697 1698 err := shell.Exec("indexing", `var list = ("1" "2" "3") 1699 echo -n $list[0]`) 1700 if err != nil { 1701 t.Error(err) 1702 return 1703 } 1704 1705 result := strings.TrimSpace(string(out.Bytes())) 1706 expected := "1" 1707 1708 if expected != result { 1709 t.Errorf("Fail: '%s' != '%s'", expected, result) 1710 return 1711 } 1712 1713 out.Reset() 1714 1715 err = shell.Exec("indexing", `var i = "0" 1716 echo -n $list[$i]`) 1717 1718 if err != nil { 1719 t.Error(err) 1720 return 1721 } 1722 1723 result = strings.TrimSpace(string(out.Bytes())) 1724 expected = "1" 1725 1726 if expected != result { 1727 t.Errorf("Fail: '%s' != '%s'", expected, result) 1728 return 1729 } 1730 1731 out.Reset() 1732 1733 err = shell.Exec("indexing", `var tmp <= seq 0 2 1734 var seq <= split($tmp, "\n") 1735 1736 for i in $seq { 1737 echo -n $list[$i] 1738 }`) 1739 if err != nil { 1740 t.Error(err) 1741 return 1742 } 1743 1744 result = strings.TrimSpace(string(out.Bytes())) 1745 expected = "123" 1746 1747 if expected != result { 1748 t.Errorf("Fail: '%s' != '%s'", expected, result) 1749 return 1750 } 1751 1752 out.Reset() 1753 1754 err = shell.Exec("indexing", `echo -n $list[5]`) 1755 if err == nil { 1756 t.Error("Must fail. Out of bounds") 1757 return 1758 } 1759 1760 out.Reset() 1761 1762 err = shell.Exec("indexing", `var a = ("0") 1763 echo -n $list[$a[0]]`) 1764 if err != nil { 1765 t.Error(err) 1766 return 1767 } 1768 1769 result = strings.TrimSpace(string(out.Bytes())) 1770 expected = "1" 1771 1772 if expected != result { 1773 t.Errorf("Fail: '%s' != '%s'", expected, result) 1774 return 1775 } 1776 } 1777 1778 func TestExecuteSubShellDoesNotOverwriteparentEnv(t *testing.T) { 1779 f, teardown := setup(t) 1780 defer teardown() 1781 1782 shell := f.shell 1783 out := f.shellOut 1784 1785 err := shell.Exec("set env", `setenv SHELL = "bleh"`) 1786 1787 if err != nil { 1788 t.Error(err) 1789 return 1790 } 1791 1792 err = shell.Exec("set env from fn", `fn test() { 1793 # test() should not call the setup func in Nash 1794 } 1795 1796 test() 1797 1798 echo -n $SHELL`) 1799 1800 if err != nil { 1801 t.Error(err) 1802 return 1803 } 1804 1805 if string(out.Bytes()) != "bleh" { 1806 t.Errorf("Differ: '%s' != '%s'", "bleh", string(out.Bytes())) 1807 return 1808 } 1809 } 1810 1811 func TestExecuteInterruptDoesNotCancelLoop(t *testing.T) { 1812 f, teardown := setup(t) 1813 defer teardown() 1814 1815 shell := f.shell 1816 shell.TriggerCTRLC() 1817 1818 time.Sleep(time.Second * 1) 1819 1820 err := shell.Exec("interrupting loop", `var seq = (1 2 3 4 5) 1821 for i in $seq {}`) 1822 1823 if err != nil { 1824 t.Error(err) 1825 return 1826 } 1827 } 1828 1829 func TestExecuteErrorSuppressionAll(t *testing.T) { 1830 f, teardown := setup(t) 1831 defer teardown() 1832 1833 shell := f.shell 1834 1835 err := shell.Exec("-input-", `var _, status <= command-not-exists`) 1836 if err != nil { 1837 t.Errorf("Expected to not fail...: %s", err.Error()) 1838 return 1839 } 1840 1841 // FIXME: depending on other sh package on the internal sh tests seems very odd 1842 scode, ok := shell.GetLocalvar("status") 1843 if !ok || scode.Type() != shtypes.StringType || scode.String() != strconv.Itoa(sh.ENotFound) { 1844 t.Errorf("Invalid status code %v", scode) 1845 return 1846 } 1847 1848 err = shell.Exec("-input-", `var _, status <= echo works`) 1849 if err != nil { 1850 t.Error(err) 1851 return 1852 } 1853 1854 // FIXME: depending on other sh package on the internal sh tests seems very odd 1855 scode, ok = shell.GetLocalvar("status") 1856 if !ok || scode.Type() != shtypes.StringType || scode.String() != "0" { 1857 t.Errorf("Invalid status code %v", scode) 1858 return 1859 } 1860 1861 err = shell.Exec("-input-", `echo works | cmd-does-not-exists`) 1862 if err == nil { 1863 t.Errorf("Must fail") 1864 return 1865 } 1866 1867 expectedError := `<interactive>:1:11: not started|exec: "cmd-does-not-exists": executable file not found in` 1868 1869 if !strings.HasPrefix(err.Error(), expectedError) { 1870 t.Errorf("Unexpected error: %s", err.Error()) 1871 return 1872 } 1873 } 1874 1875 func TestExecuteGracefullyError(t *testing.T) { 1876 f, teardown := setup(t) 1877 defer teardown() 1878 1879 shell := f.shell 1880 1881 err := shell.Exec("someinput.sh", "(") 1882 if err == nil { 1883 t.Errorf("Must fail...") 1884 return 1885 } 1886 1887 expectErr := "someinput.sh:1:1: Multi-line command not finished. Found EOF but expect ')'" 1888 1889 if err.Error() != expectErr { 1890 t.Errorf("Expect error: %s, but got: %s", expectErr, err.Error()) 1891 return 1892 } 1893 1894 err = shell.Exec("input", "echo(") 1895 if err == nil { 1896 t.Errorf("Must fail...") 1897 return 1898 } 1899 1900 if err.Error() != "input:1:5: Unexpected token EOF. Expecting STRING, VARIABLE or )" { 1901 t.Errorf("Unexpected error: %s", err.Error()) 1902 return 1903 } 1904 1905 } 1906 1907 func TestExecuteMultilineCmd(t *testing.T) { 1908 f, teardown := setup(t) 1909 defer teardown() 1910 1911 shell := f.shell 1912 out := f.shellOut 1913 1914 err := shell.Exec("test", `(echo -n 1915 hello 1916 world)`) 1917 1918 if err != nil { 1919 t.Error(err) 1920 return 1921 } 1922 1923 expected := "hello world" 1924 1925 if expected != string(out.Bytes()) { 1926 t.Errorf("Expected '%s' but got '%s'", expected, string(out.Bytes())) 1927 return 1928 } 1929 1930 out.Reset() 1931 1932 err = shell.Exec("test", `( 1933 echo -n 1 2 3 4 5 6 7 8 9 10 1934 11 12 13 14 15 16 17 18 19 20 1935 )`) 1936 1937 if err != nil { 1938 t.Error(err) 1939 return 1940 } 1941 1942 expected = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" 1943 1944 if expected != string(out.Bytes()) { 1945 t.Errorf("Expected '%s' but got '%s'", expected, string(out.Bytes())) 1946 return 1947 } 1948 } 1949 1950 func TestExecuteMultilineCmdAssign(t *testing.T) { 1951 f, teardown := setup(t) 1952 defer teardown() 1953 1954 shell := f.shell 1955 out := f.shellOut 1956 1957 err := shell.Exec("test", `var val <= (echo -n 1958 hello 1959 world) 1960 1961 echo -n $val`) 1962 1963 if err != nil { 1964 t.Error(err) 1965 return 1966 } 1967 1968 expected := "hello world" 1969 1970 if expected != string(out.Bytes()) { 1971 t.Errorf("Expected '%s' but got '%s'", expected, string(out.Bytes())) 1972 return 1973 } 1974 1975 out.Reset() 1976 1977 err = shell.Exec("test", `val <= ( 1978 echo -n 1 2 3 4 5 6 7 8 9 10 1979 11 12 13 14 15 16 17 18 19 20 1980 ) 1981 echo -n $val`) 1982 1983 if err != nil { 1984 t.Error(err) 1985 return 1986 } 1987 1988 expected = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" 1989 1990 if expected != string(out.Bytes()) { 1991 t.Errorf("Expected '%s' but got '%s'", expected, string(out.Bytes())) 1992 return 1993 } 1994 } 1995 1996 func TestExecuteMultiReturnUnfinished(t *testing.T) { 1997 f, teardown := setup(t) 1998 defer teardown() 1999 2000 shell := f.shell 2001 2002 err := shell.Exec("test", "(") 2003 2004 if err == nil { 2005 t.Errorf("Must fail... Must return an unfinished paren error") 2006 return 2007 } 2008 2009 type unfinished interface { 2010 Unfinished() bool 2011 } 2012 2013 if e, ok := err.(unfinished); !ok || !e.Unfinished() { 2014 t.Errorf("Must fail with unfinished paren error. Got %s", err.Error()) 2015 return 2016 } 2017 2018 err = shell.Exec("test", `( 2019 echo`) 2020 2021 if err == nil { 2022 t.Errorf("Must fail... Must return an unfinished paren error") 2023 return 2024 } 2025 2026 if e, ok := err.(unfinished); !ok || !e.Unfinished() { 2027 t.Errorf("Must fail with unfinished paren error. Got %s", err.Error()) 2028 return 2029 } 2030 2031 err = shell.Exec("test", `( 2032 echo hello 2033 world`) 2034 2035 if err == nil { 2036 t.Errorf("Must fail... Must return an unfinished paren error") 2037 return 2038 } 2039 2040 if e, ok := err.(unfinished); !ok || !e.Unfinished() { 2041 t.Errorf("Must fail with unfinished paren error. Got %s", err.Error()) 2042 return 2043 } 2044 } 2045 2046 func TestExecuteVariadicFn(t *testing.T) { 2047 for _, test := range []execTestCase{ 2048 { 2049 desc: "println", 2050 code: `fn println(fmt, arg...) { 2051 print($fmt+"\n", $arg...) 2052 } 2053 println("%s %s", "test", "test")`, 2054 expectedStdout: "test test\n", 2055 expectedStderr: "", 2056 expectedErr: "", 2057 }, 2058 { 2059 desc: "lots of args", 2060 code: `fn println(fmt, arg...) { 2061 print($fmt+"\n", $arg...) 2062 } 2063 println("%s%s%s%s%s%s%s%s%s%s", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")`, 2064 expectedStdout: "12345678910\n", 2065 expectedStderr: "", 2066 expectedErr: "", 2067 }, 2068 { 2069 desc: "passing list to var arg fn", 2070 code: `fn puts(arg...) { for a in $arg { echo $a } } 2071 var a = ("1" "2" "3" "4" "5") 2072 puts($a...)`, 2073 expectedErr: "", 2074 expectedStdout: "1\n2\n3\n4\n5\n", 2075 expectedStderr: "", 2076 }, 2077 { 2078 desc: "passing empty list to var arg fn", 2079 code: `fn puts(arg...) { for a in $arg { echo $a } } 2080 var a = () 2081 puts($a...)`, 2082 expectedErr: "", 2083 expectedStdout: "", 2084 expectedStderr: "", 2085 }, 2086 { 2087 desc: "... expansion", 2088 code: `var args = ("plan9" "from" "outer" "space") 2089 print("%s %s %s %s", $args...)`, 2090 expectedStdout: "plan9 from outer space", 2091 }, 2092 { 2093 desc: "literal ... expansion", 2094 code: `print("%s:%s:%s", ("a" "b" "c")...)`, 2095 expectedStdout: "a:b:c", 2096 }, 2097 { 2098 desc: "varargs only as last argument", 2099 code: `fn println(arg..., fmt) {}`, 2100 expectedErr: "<interactive>:1:11: Vararg 'arg...' isn't the last argument", 2101 }, 2102 { 2103 desc: "variadic argument are optional", 2104 code: `fn println(b...) { 2105 for v in $b { 2106 print($v) 2107 } 2108 print("\n") 2109 } 2110 println()`, 2111 expectedStdout: "\n", 2112 }, 2113 { 2114 desc: "the first argument isn't optional", 2115 code: `fn a(b, c...) { 2116 print($b, $c...) 2117 } 2118 a("test")`, 2119 expectedStdout: "test", 2120 }, 2121 { 2122 desc: "the first argument isn't optional", 2123 code: `fn a(b, c...) { 2124 print($b, $c...) 2125 } 2126 a()`, 2127 expectedErr: "<interactive>:4:0: Wrong number of arguments for function a. Expected at least 1 arguments but found 0", 2128 }, 2129 } { 2130 testExec(t, test) 2131 } 2132 } 2133 2134 func setup(t *testing.T) (testFixture, func()) { 2135 dirs := fixture.SetupNashDirs(t) 2136 shell, err := sh.NewAbortShell(dirs.Path, dirs.Root) 2137 if err != nil { 2138 t.Fatal(err) 2139 } 2140 2141 var out bytes.Buffer 2142 shell.SetStdout(&out) 2143 2144 return testFixture{ 2145 shell: shell, 2146 shellOut: &out, 2147 dir: tests.Testdir, 2148 envDirs: dirs, 2149 nashdPath: tests.Nashcmd, 2150 }, dirs.Cleanup 2151 } 2152 2153 func testExecuteFile(t *testing.T, path, expected string, before string) { 2154 f, teardown := setup(t) 2155 defer teardown() 2156 2157 if before != "" { 2158 f.shell.Exec("", before) 2159 } 2160 2161 err := f.shell.ExecFile(path) 2162 2163 if err != nil { 2164 t.Error(err) 2165 return 2166 } 2167 2168 if string(f.shellOut.Bytes()) != expected { 2169 t.Errorf("Wrong command output: '%s' != '%s'", 2170 string(f.shellOut.Bytes()), expected) 2171 return 2172 } 2173 } 2174 2175 func testShellExec(t *testing.T, shell *sh.Shell, testcase execTestCase) { 2176 t.Helper() 2177 2178 var bout bytes.Buffer 2179 var berr bytes.Buffer 2180 shell.SetStderr(&berr) 2181 shell.SetStdout(&bout) 2182 2183 err := shell.Exec(testcase.desc, testcase.code) 2184 if err != nil { 2185 if testcase.expectedPrefixErr != "" { 2186 if !strings.HasPrefix(err.Error(), testcase.expectedPrefixErr) { 2187 t.Errorf("[%s] Prefix of error differs: Expected prefix '%s' in '%s'", 2188 testcase.desc, 2189 testcase.expectedPrefixErr, 2190 err.Error()) 2191 } 2192 } else if err.Error() != testcase.expectedErr { 2193 t.Errorf("[%s] Error differs: Expected '%s' but got '%s'", 2194 testcase.desc, 2195 testcase.expectedErr, 2196 err.Error()) 2197 } 2198 } else if testcase.expectedErr != "" { 2199 t.Fatalf("Expected error[%s] but got nil", testcase.expectedErr) 2200 } 2201 2202 if testcase.expectedStdout != string(bout.Bytes()) { 2203 t.Errorf("[%s] Stdout differs: '%s' != '%s'", 2204 testcase.desc, 2205 testcase.expectedStdout, 2206 string(bout.Bytes())) 2207 return 2208 } 2209 2210 if testcase.expectedStderr != string(berr.Bytes()) { 2211 t.Errorf("[%s] Stderr differs: '%s' != '%s'", 2212 testcase.desc, 2213 testcase.expectedStderr, 2214 string(berr.Bytes())) 2215 return 2216 } 2217 bout.Reset() 2218 berr.Reset() 2219 } 2220 2221 func testExec(t *testing.T, testcase execTestCase) { 2222 t.Helper() 2223 f, teardown := setup(t) 2224 defer teardown() 2225 2226 testShellExec(t, f.shell, testcase) 2227 } 2228 2229 func testInteractiveExec(t *testing.T, testcase execTestCase) { 2230 t.Helper() 2231 2232 f, teardown := setup(t) 2233 defer teardown() 2234 2235 f.shell.SetInteractive(true) 2236 testShellExec(t, f.shell, testcase) 2237 }