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 }