github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/compile_effect_test.go (about)

     1  package eval_test
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	. "github.com/markusbkk/elvish/pkg/eval"
     8  	"github.com/markusbkk/elvish/pkg/eval/errs"
     9  	. "github.com/markusbkk/elvish/pkg/eval/evaltest"
    10  	"github.com/markusbkk/elvish/pkg/mods/file"
    11  	"github.com/markusbkk/elvish/pkg/testutil"
    12  )
    13  
    14  func TestChunk(t *testing.T) {
    15  	Test(t,
    16  		// Empty chunk
    17  		That("").DoesNothing(),
    18  		// Outputs of pipelines in a chunk are concatenated
    19  		That("put x; put y; put z").Puts("x", "y", "z"),
    20  		// A failed pipeline cause the whole chunk to fail
    21  		That("put a; e:false; put b").Puts("a").Throws(ErrorWithType(ExternalCmdExit{})),
    22  	)
    23  }
    24  
    25  func TestPipeline(t *testing.T) {
    26  	Test(t,
    27  		// Pure byte pipeline
    28  		That(`echo "Albert\nAllan\nAlbraham\nBerlin" | sed s/l/1/g | grep e`).
    29  			Prints("A1bert\nBer1in\n"),
    30  		// Pure value pipeline
    31  		That(`put 233 42 19 | each {|x|+ $x 10}`).Puts(243, 52, 29),
    32  		// Pipeline draining.
    33  		That(`range 100 | put x`).Puts("x"),
    34  		// TODO: Add a useful hybrid pipeline sample
    35  	)
    36  }
    37  
    38  func TestPipeline_BgJob(t *testing.T) {
    39  	setup := func(ev *Evaler) {
    40  		ev.ExtendGlobal(BuildNs().AddNs("file", file.Ns))
    41  	}
    42  
    43  	notes1 := make(chan string)
    44  	notes2 := make(chan string)
    45  
    46  	putNote := func(ch chan<- string) func(*Evaler) {
    47  		return func(ev *Evaler) {
    48  			ev.BgJobNotify = func(note string) { ch <- note }
    49  		}
    50  	}
    51  	verifyNote := func(notes <-chan string, wantNote string) func(t *testing.T) {
    52  		return func(t *testing.T) {
    53  			select {
    54  			case note := <-notes:
    55  				if note != wantNote {
    56  					t.Errorf("got note %q, want %q", note, wantNote)
    57  				}
    58  			case <-time.After(testutil.Scaled(100 * time.Millisecond)):
    59  				t.Errorf("timeout waiting for notification")
    60  			}
    61  		}
    62  	}
    63  
    64  	TestWithSetup(t, setup,
    65  		That(
    66  			"set notify-bg-job-success = $false",
    67  			"var p = (file:pipe)",
    68  			"{ print foo > $p; file:close $p[w] }&",
    69  			"slurp < $p; file:close $p[r]").
    70  			Puts("foo"),
    71  		// Notification
    72  		That(
    73  			"set notify-bg-job-success = $true",
    74  			"var p = (file:pipe)",
    75  			"fn f { file:close $p[w] }",
    76  			"f &",
    77  			"slurp < $p; file:close $p[r]").
    78  			Puts("").
    79  			WithSetup(putNote(notes1)).
    80  			Passes(verifyNote(notes1, "job f & finished")),
    81  		// Notification, with exception
    82  		That(
    83  			"set notify-bg-job-success = $true",
    84  			"var p = (file:pipe)",
    85  			"fn f { file:close $p[w]; fail foo }",
    86  			"f &",
    87  			"slurp < $p; file:close $p[r]").
    88  			Puts("").
    89  			WithSetup(putNote(notes2)).
    90  			Passes(verifyNote(notes2, "job f & finished, errors = foo")),
    91  	)
    92  }
    93  
    94  func TestPipeline_ReaderGone(t *testing.T) {
    95  	// See UNIX-only tests in compile_effect_unix_test.go.
    96  	Test(t,
    97  		// Internal commands writing to byte output raises ReaderGone when the
    98  		// reader is exited, which is then suppressed.
    99  		That("while $true { echo y } | nop").DoesNothing(),
   100  		That(
   101  			"var reached = $false",
   102  			"{ while $true { echo y }; reached = $true } | nop",
   103  			"put $reached",
   104  		).Puts(false),
   105  		// Similar for value output.
   106  		That("while $true { put y } | nop").DoesNothing(),
   107  		That(
   108  			"var reached = $false",
   109  			"{ while $true { put y }; reached = $true } | nop",
   110  			"put $reached",
   111  		).Puts(false),
   112  	)
   113  }
   114  
   115  func TestCommand(t *testing.T) {
   116  	Test(t,
   117  		That("put foo").Puts("foo"),
   118  		// Command errors when the head is not a single value.
   119  		That("{put put} foo").Throws(
   120  			errs.ArityMismatch{What: "command",
   121  				ValidLow: 1, ValidHigh: 1, Actual: 2},
   122  			"{put put}"),
   123  		// Command errors when the head is not callable or string containing slash.
   124  		That("[] foo").Throws(
   125  			errs.BadValue{
   126  				What:   "command",
   127  				Valid:  "callable or string containing slash",
   128  				Actual: "[]"},
   129  			"[]"),
   130  		// Command errors when when argument errors.
   131  		That("put [][1]").Throws(ErrorWithType(errs.OutOfRange{}), "[][1]"),
   132  		// Command errors when an option key is not string.
   133  		That("put &[]=[]").Throws(
   134  			errs.BadValue{What: "option key", Valid: "string", Actual: "list"},
   135  			"put &[]=[]"),
   136  		// Command errors when any optional evaluation errors.
   137  		That("put &x=[][1]").Throws(ErrorWithType(errs.OutOfRange{}), "[][1]"),
   138  	)
   139  }
   140  
   141  func TestCommand_Special(t *testing.T) {
   142  	Test(t,
   143  		// Regression test for #1204; ensures that the arguments of special
   144  		// forms are not accidentally compiled twice.
   145  		That("nop (and (use builtin)); nop $builtin:echo~").DoesNothing(),
   146  
   147  		// Behavior of individual special commands are tested in
   148  		// builtin_special_test.go.
   149  	)
   150  }
   151  
   152  func TestCommand_LegacyTemporaryAssignment(t *testing.T) {
   153  	Test(t,
   154  		That("var a b = alice bob; {a,@b}=(put amy ben) put $a $@b; put $a $b").
   155  			Puts("amy", "ben", "alice", "bob"),
   156  		// Temporary assignment of list element.
   157  		That("var l = [a]; l[0]=x put $l[0]; put $l[0]").Puts("x", "a"),
   158  		// Temporary assignment of map element.
   159  		That("var m = [&k=v]; m[k]=v2 put $m[k]; put $m[k]").Puts("v2", "v"),
   160  		// Temporary assignment before special form.
   161  		That("li=[foo bar] for x $li { put $x }").Puts("foo", "bar"),
   162  		// Multiple LHSs in temporary assignments.
   163  		That("{a b}={foo bar} put $a $b").Puts("foo", "bar"),
   164  		That("@a=(put a b) put $@a").Puts("a", "b"),
   165  		That("{a,@b}=(put a b c) put $@b").Puts("b", "c"),
   166  		// Using syntax of temporary assignment for non-temporary assignment no
   167  		// longer compiles
   168  		That("x=y").DoesNotCompile(),
   169  	)
   170  }
   171  
   172  func TestCommand_LegacyTemporaryAssignmentSyntaxIsDeprecated(t *testing.T) {
   173  	testCompileTimeDeprecation(t, "a=foo echo $a",
   174  		"the legacy temporary assignment syntax is deprecated", 18)
   175  }
   176  
   177  func TestCommand_Redir(t *testing.T) {
   178  	setup := func(ev *Evaler) {
   179  		ev.ExtendGlobal(BuildNs().AddNs("file", file.Ns))
   180  	}
   181  	testutil.InTempDir(t)
   182  
   183  	TestWithSetup(t, setup,
   184  		// Output and input redirection.
   185  		That("echo 233 > out1", " slurp < out1").Puts("233\n"),
   186  		// Append.
   187  		That("echo 1 > out; echo 2 >> out; slurp < out").Puts("1\n2\n"),
   188  		// Read and write.
   189  		// TODO: Add a meaningful use case that uses both read and write.
   190  		That("echo 233 <> out1", " slurp < out1").Puts("233\n"),
   191  
   192  		// Redirections from special form.
   193  		That(`for x [lorem ipsum] { echo $x } > out2`, `slurp < out2`).
   194  			Puts("lorem\nipsum\n"),
   195  
   196  		// Using numeric FDs as source and destination.
   197  		That(`{ echo foobar >&2 } 2> out3`, `slurp < out3`).
   198  			Puts("foobar\n"),
   199  		// Using named FDs as source and destination.
   200  		That("echo 233 stdout> out1", " slurp stdin< out1").Puts("233\n"),
   201  		That(`{ echo foobar >&stderr } stderr> out4`, `slurp < out4`).
   202  			Puts("foobar\n"),
   203  		// Using a new FD as source throws an exception.
   204  		That(`echo foo >&4`).Throws(InvalidFD{FD: 4}),
   205  		// Using a new FD as destination is OK, and makes it available.
   206  		That(`{ echo foo >&4 } 4>out5`, `slurp < out5`).Puts("foo\n"),
   207  
   208  		// Redirections from File object.
   209  		That(`echo haha > out3`, `var f = (file:open out3)`, `slurp <$f`, ` file:close $f`).
   210  			Puts("haha\n"),
   211  		// Redirections from Pipe object.
   212  		That(`var p = (file:pipe); echo haha > $p; file:close $p[w]; slurp < $p; file:close $p[r]`).
   213  			Puts("haha\n"),
   214  
   215  		// We can't read values from a file and shouldn't hang when iterating
   216  		// over input from a file.
   217  		// Regression test for https://github.com/markusbkk/elvish/issues/1010
   218  		That("echo abc > bytes", "each $echo~ < bytes").Prints("abc\n"),
   219  		That("echo def > bytes", "only-values < bytes | count").Puts(0),
   220  
   221  		// Writing value output to file throws an exception.
   222  		That("put foo >a").Throws(ErrNoValueOutput, "put foo >a"),
   223  		// Writing value output to closed port throws an exception too.
   224  		That("put foo >&-").Throws(ErrNoValueOutput, "put foo >&-"),
   225  
   226  		// Invalid redirection destination.
   227  		That("echo []> test").Throws(
   228  			errs.BadValue{
   229  				What:  "redirection destination",
   230  				Valid: "fd name or number", Actual: "[]"},
   231  			"[]"),
   232  		// Invalid fd redirection source.
   233  		That("echo >&test").Throws(
   234  			errs.BadValue{
   235  				What:  "redirection source",
   236  				Valid: "fd name or number or '-'", Actual: "test"},
   237  			"test"),
   238  		// Invalid redirection source.
   239  		That("echo > []").Throws(
   240  			errs.BadValue{
   241  				What:  "redirection source",
   242  				Valid: "string, file or pipe", Actual: "list"},
   243  			"[]"),
   244  
   245  		// Exception when evaluating source or destination.
   246  		That("echo > (fail foo)").Throws(FailError{"foo"}, "fail foo"),
   247  		That("echo (fail foo)> file").Throws(FailError{"foo"}, "fail foo"),
   248  	)
   249  }
   250  
   251  func TestCommand_Stacktrace(t *testing.T) {
   252  	oops := ErrorWithMessage("oops")
   253  	Test(t,
   254  		// Stack traces.
   255  		That("fail oops").Throws(oops, "fail oops"),
   256  		That("fn f { fail oops }", "f").Throws(oops, "fail oops ", "f"),
   257  		That("fn f { fail oops }", "fn g { f }", "g").Throws(
   258  			oops, "fail oops ", "f ", "g"),
   259  		// Error thrown before execution.
   260  		That("fn f { }", "f a").Throws(ErrorWithType(errs.ArityMismatch{}), "f a"),
   261  		// Error from builtin.
   262  		That("count 1 2 3").Throws(
   263  			ErrorWithType(errs.ArityMismatch{}), "count 1 2 3"),
   264  	)
   265  }