github.com/theclapp/sh@v2.6.4+incompatible/interp/interp_test.go (about)

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