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  }