1package eval_test 2 3import ( 4 "testing" 5 6 . "src.elv.sh/pkg/eval" 7 "src.elv.sh/pkg/eval/errs" 8 "src.elv.sh/pkg/eval/vals" 9 "src.elv.sh/pkg/eval/vars" 10 "src.elv.sh/pkg/parse" 11 "src.elv.sh/pkg/prog/progtest" 12 13 . "src.elv.sh/pkg/eval/evaltest" 14 . "src.elv.sh/pkg/testutil" 15) 16 17func TestPragma(t *testing.T) { 18 Test(t, 19 That("pragma unknown-command").DoesNotCompile(), 20 That("pragma unknown-command =").DoesNotCompile(), 21 That("pragma unknown-command x").DoesNotCompile(), 22 That("pragma bad-name = some-value").DoesNotCompile(), 23 That("pragma unknown-command = bad").DoesNotCompile(), 24 ) 25 // Actual effect of the unknown-command pragma is tested in TestCommand_External 26} 27 28func TestVar(t *testing.T) { 29 Test(t, 30 // Declaring one variable 31 That("var x", "put $x").Puts(nil), 32 // Declaring one variable whose name needs to be quoted 33 That("var 'a/b'", "put $'a/b'").Puts(nil), 34 // Declaring one variable whose name ends in ":". 35 That("var a:").DoesNothing(), 36 // Declaring a variable whose name ends in "~" initializes it to the 37 // builtin nop function. 38 That("var cmd~; cmd &ignored-opt ignored-arg").DoesNothing(), 39 // Declaring multiple variables 40 That("var x y", "put $x $y").Puts(nil, nil), 41 // Declaring one variable with initial value 42 That("var x = foo", "put $x").Puts("foo"), 43 // Declaring multiple variables with initial values 44 That("var x y = foo bar", "put $x $y").Puts("foo", "bar"), 45 // Declaring multiple variables with initial values, including a rest 46 // variable in the assignment LHS 47 That("var x @y z = a b c d", "put $x $y $z"). 48 Puts("a", vals.MakeList("b", "c"), "d"), 49 // An empty RHS is technically legal although rarely useful. 50 That("var @x =", "put $x").Puts(vals.EmptyList), 51 // Shadowing. 52 That("var x = old; fn f { put $x }", "var x = new; put $x; f"). 53 Puts("new", "old"), 54 // Explicit local: is allowed 55 That("var local:x = foo", "put $x").Puts("foo"), 56 57 // Variable name that must be quoted after $ must be quoted 58 That("var a/b").DoesNotCompile(), 59 // Multiple @ not allowed 60 That("var x @y @z = a b c d").DoesNotCompile(), 61 // Non-local not allowed 62 That("var ns:a").DoesNotCompile(), 63 // Index not allowed 64 That("var a[0]").DoesNotCompile(), 65 // Composite expression not allowed 66 That("var a'b'").DoesNotCompile(), 67 ) 68} 69 70func TestSet(t *testing.T) { 71 Test(t, 72 // Setting one variable 73 That("var x; set x = foo", "put $x").Puts("foo"), 74 // An empty RHS is technically legal although rarely useful. 75 That("var x; set @x =", "put $x").Puts(vals.EmptyList), 76 // Variable must already exist 77 That("set x = foo").DoesNotCompile(), 78 // Not duplicating tests with TestCommand_Assignment. 79 // 80 // TODO: After legacy assignment form is removed, transfer tests here. 81 82 // = is required. 83 That("var x; set x").DoesNotCompile(), 84 ) 85} 86 87func TestDel(t *testing.T) { 88 Setenv(t, "TEST_ENV", "test value") 89 90 Test(t, 91 // Deleting variable 92 That("x = 1; del x").DoesNothing(), 93 That("x = 1; del x; echo $x").DoesNotCompile(), 94 That("x = 1; del :x; echo $x").DoesNotCompile(), 95 That("x = 1; del local:x; echo $x").DoesNotCompile(), 96 // Deleting environment variable 97 That("has-env TEST_ENV", "del E:TEST_ENV", "has-env TEST_ENV").Puts(true, false), 98 // Deleting variable whose name contains special characters 99 That("'a/b' = foo; del 'a/b'").DoesNothing(), 100 // Deleting element 101 That("x = [&k=v &k2=v2]; del x[k2]; keys $x").Puts("k"), 102 That("x = [[&k=v &k2=v2]]; del x[0][k2]; keys $x[0]").Puts("k"), 103 104 // Error cases 105 106 // Deleting nonexistent variable 107 That("del x").DoesNotCompile(), 108 // Deleting element of nonexistent variable 109 That("del x[0]").DoesNotCompile(), 110 // Deleting variable in non-local namespace 111 That("var a: = (ns [&b=$nil])", "del a:b").DoesNotCompile(), 112 // Variable name given with $ 113 That("x = 1; del $x").DoesNotCompile(), 114 // Variable name not given as a single primary expression 115 That("ab = 1; del a'b'").DoesNotCompile(), 116 // Variable name not a string 117 That("del [a]").DoesNotCompile(), 118 // Variable name has sigil 119 That("x = []; del @x").DoesNotCompile(), 120 // Variable name not quoted when it should be 121 That("'a/b' = foo; del a/b").DoesNotCompile(), 122 123 // Index is multiple values 124 That("x = [&k1=v1 &k2=v2]", "del x[k1 k2]").Throws( 125 ErrorWithMessage("index must evaluate to a single value in argument to del"), 126 "k1 k2"), 127 // Index expression throws exception 128 That("x = [&k]", "del x[(fail x)]").Throws(FailError{"x"}, "fail x"), 129 // Value does not support element removal 130 That("x = (num 1)", "del x[k]").Throws( 131 ErrorWithMessage("value does not support element removal"), 132 // TODO: Fix the stack trace so that it is "x[k]" 133 "x[k"), 134 // Intermediate element does not exist 135 That("x = [&]", "del x[k][0]").Throws( 136 ErrorWithMessage("no such key: k"), 137 // TODO: Fix the stack trace so that it is "x[k]" 138 "x"), 139 ) 140} 141 142func TestAnd(t *testing.T) { 143 Test(t, 144 That("and $true $false").Puts(false), 145 That("and a b").Puts("b"), 146 That("and $false b").Puts(false), 147 That("and $true b").Puts("b"), 148 // short circuit 149 That("x = a; and $false (x = b); put $x").Puts(false, "a"), 150 151 // Exception 152 That("and a (fail x)").Throws(FailError{"x"}, "fail x"), 153 thatOutputErrorIsBubbled("and a"), 154 ) 155} 156 157func TestOr(t *testing.T) { 158 Test(t, 159 That("or $true $false").Puts(true), 160 That("or a b").Puts("a"), 161 That("or $false b").Puts("b"), 162 That("or $true b").Puts(true), 163 // short circuit 164 That("x = a; or $true (x = b); put $x").Puts(true, "a"), 165 166 // Exception 167 That("or $false (fail x)").Throws(FailError{"x"}, "fail x"), 168 thatOutputErrorIsBubbled("or a"), 169 ) 170} 171 172func TestCoalesce(t *testing.T) { 173 Test(t, 174 That("coalesce a b").Puts("a"), 175 That("coalesce $nil b").Puts("b"), 176 That("coalesce $nil $nil").Puts(nil), 177 That("coalesce").Puts(nil), 178 // exception propagation 179 That("coalesce $nil (fail foo)").Throws(FailError{"foo"}), 180 // short circuit 181 That("coalesce a (fail foo)").Puts("a"), 182 183 thatOutputErrorIsBubbled("coalesce a"), 184 ) 185} 186 187func TestIf(t *testing.T) { 188 Test(t, 189 That("if true { put then }").Puts("then"), 190 That("if $false { put then } else { put else }").Puts("else"), 191 That("if $false { put 1 } elif $false { put 2 } else { put 3 }"). 192 Puts("3"), 193 That("if $false { put 2 } elif true { put 2 } else { put 3 }").Puts("2"), 194 195 // Exception in condition expression 196 That("if (fail x) { }").Throws(FailError{"x"}, "fail x"), 197 ) 198} 199 200func TestTry(t *testing.T) { 201 Test(t, 202 That("try { nop } except { put bad } else { put good }").Puts("good"), 203 That("try { e:false } except - { put bad } else { put good }"). 204 Puts("bad"), 205 That("try { fail tr }").Throws(ErrorWithMessage("tr")), 206 That("try { fail tr } finally { put final }"). 207 Puts("final"). 208 Throws(ErrorWithMessage("tr")), 209 210 That("try { fail tr } except { fail ex } finally { put final }"). 211 Puts("final"). 212 Throws(ErrorWithMessage("ex")), 213 214 That("try { fail tr } except { put ex } finally { fail final }"). 215 Puts("ex"). 216 Throws(ErrorWithMessage("final")), 217 218 That("try { fail tr } except { fail ex } finally { fail final }"). 219 Throws(ErrorWithMessage("final")), 220 221 // wrong syntax 222 That("try { nop } except @a { }").DoesNotCompile(), 223 224 // A quoted var name, that would be invalid as a bareword, should be allowed as the referent 225 // in a `try...except...` block. 226 That("try { fail hard } except 'x=' { put 'x= ='(to-string $'x=') }"). 227 Puts("x= =[&reason=[&content=hard &type=fail]]"), 228 ) 229} 230 231func TestWhile(t *testing.T) { 232 Test(t, 233 That("var x = (num 0)", "while (< $x 4) { put $x; set x = (+ $x 1) }"). 234 Puts(0, 1, 2, 3), 235 // break 236 That("var x = (num 0)", "while (< $x 4) { put $x; break }").Puts(0), 237 // continue 238 That("var x = (num 0)", 239 "while (< $x 4) { put $x; set x = (+ $x 1); continue; put bad }"). 240 Puts(0, 1, 2, 3), 241 // Exception in body 242 That("var x = 0; while (< $x 4) { fail haha }").Throws(AnyError), 243 // Exception in condition 244 That("while (fail x) { }").Throws(FailError{"x"}, "fail x"), 245 246 // else branch - not taken 247 That("var x = 0; while (< $x 4) { put $x; set x = (+ $x 1) } else { put bad }"). 248 Puts("0", 1, 2, 3), 249 // else branch - taken 250 That("while $false { put bad } else { put good }").Puts("good"), 251 ) 252} 253 254func TestFor(t *testing.T) { 255 Test(t, 256 // for 257 That("for x [tempora mores] { put 'O '$x }"). 258 Puts("O tempora", "O mores"), 259 // break 260 That("for x [a] { break } else { put $x }").DoesNothing(), 261 // else 262 That("for x [a] { put $x } else { put $x }").Puts("a"), 263 // continue 264 That("for x [a b] { put $x; continue; put $x; }").Puts("a", "b"), 265 // else 266 That("for x [] { } else { put else }").Puts("else"), 267 That("for x [a] { } else { put else }").DoesNothing(), 268 // Propagating exception. 269 That("for x [a] { fail foo }").Throws(FailError{"foo"}), 270 271 // More than one iterator. 272 That("for {x,y} [] { }").DoesNotCompile(), 273 // Invalid for loop lvalue. You can't use a var in a namespace other 274 // than the local namespace as the lvalue in a for loop. 275 That("for no-such-namespace:x [a b] { }").DoesNotCompile(), 276 // Exception with the variable 277 That("var a: = (ns [&])", "for a:b [] { }").Throws( 278 ErrorWithMessage("no variable $a:b"), 279 "a:b"), 280 // Exception when evaluating iterable. 281 That("for x [][0] { }").Throws(ErrorWithType(errs.OutOfRange{}), "[][0]"), 282 // More than one iterable. 283 That("for x (put a b) { }").Throws( 284 errs.ArityMismatch{What: "value being iterated", 285 ValidLow: 1, ValidHigh: 1, Actual: 2}, 286 "(put a b)"), 287 // Non-iterable value 288 That("for x (num 0) { }").Throws(ErrorWithMessage("cannot iterate number")), 289 ) 290} 291 292func TestFn(t *testing.T) { 293 Test(t, 294 That("fn f {|x| put x=$x'.' }; f lorem; f ipsum"). 295 Puts("x=lorem.", "x=ipsum."), 296 // Recursive functions with fn. Regression test for #1206. 297 That("fn f {|n| if (== $n 0) { num 1 } else { * $n (f (- $n 1)) } }; f 3"). 298 Puts(6), 299 // Exception thrown by return is swallowed by a fn-defined function. 300 That("fn f { put a; return; put b }; f").Puts("a"), 301 302 // Error when evaluating the lambda 303 That("fn f {|&opt=(fail x)| }").Throws(FailError{"x"}, "fail x"), 304 ) 305} 306 307// Regression test for #1225 308func TestUse_SetsVariableCorrectlyIfModuleCallsExtendGlobal(t *testing.T) { 309 libdir := InTempDir(t) 310 311 ApplyDir(Dir{"a.elv": "add-var"}) 312 ev := NewEvaler() 313 ev.LibDirs = []string{libdir} 314 addVar := func() { 315 ev.ExtendGlobal(BuildNs().AddVar("b", vars.NewReadOnly("foo"))) 316 } 317 ev.ExtendBuiltin(BuildNs().AddGoFn("add-var", addVar)) 318 319 err := ev.Eval(parse.Source{Code: "use a"}, EvalCfg{}) 320 if err != nil { 321 t.Fatal(err) 322 } 323 324 g := ev.Global() 325 if g.IndexString("a:").Get().(*Ns) == nil { 326 t.Errorf("$a: is nil") 327 } 328 if g.IndexString("b").Get().(string) != "foo" { 329 t.Errorf(`$b is not "foo"`) 330 } 331} 332 333func TestUse_SupportsCircularDependency(t *testing.T) { 334 libdir := InTempDir(t) 335 ApplyDir(Dir{ 336 "a.elv": "var pre = apre; use b; put $b:pre $b:post; var post = apost", 337 "b.elv": "var pre = bpre; use a; put $a:pre $a:post; var post = bpost", 338 }) 339 340 TestWithSetup(t, func(ev *Evaler) { ev.LibDirs = []string{libdir} }, 341 That(`use a`).Puts( 342 // When b.elv is imported from a.elv, $a:pre is set but $a:post is 343 // not 344 "apre", nil, 345 // After a.elv imports b.elv, both $b:pre and $b:post are set 346 "bpre", "bpost"), 347 ) 348} 349 350func TestUse(t *testing.T) { 351 libdir1 := InTempDir(t) 352 ApplyDir(Dir{ 353 "shadow.elv": "put lib1", 354 }) 355 356 libdir2 := InTempDir(t) 357 ApplyDir(Dir{ 358 "has-init.elv": "put has-init", 359 "put-x.elv": "put $x", 360 "lorem.elv": "name = lorem; fn put-name { put $name }", 361 "d.elv": "name = d", 362 "shadow.elv": "put lib2", 363 "a": Dir{ 364 "b": Dir{ 365 "c": Dir{ 366 "d.elv": "name = a/b/c/d", 367 "x.elv": "use ./d; d = $d:name; use ../../../lorem; lorem = $lorem:name", 368 }, 369 }, 370 }, 371 }) 372 373 TestWithSetup(t, func(ev *Evaler) { ev.LibDirs = []string{libdir1, libdir2} }, 374 That(`use lorem; put $lorem:name`).Puts("lorem"), 375 // imports are lexically scoped 376 // TODO: Support testing for compilation error 377 That(`{ use lorem }; put $lorem:name`).DoesNotCompile(), 378 379 // prefers lib dir that appear earlier 380 That("use shadow").Puts("lib1"), 381 382 // use of imported variable is captured in upvalue 383 That(`use lorem; { put $lorem:name }`).Puts("lorem"), 384 That(`{ use lorem; { put $lorem:name } }`).Puts("lorem"), 385 That(`({ use lorem; put { { put $lorem:name } } })`).Puts("lorem"), 386 // use of imported function is also captured in upvalue 387 That(`{ use lorem; { lorem:put-name } }`).Puts("lorem"), 388 389 // use of a nested module 390 That(`use a/b/c/d; put $d:name`).Puts("a/b/c/d"), 391 // module is cached after first use 392 That(`use has-init; use has-init`).Puts("has-init"), 393 // repeated uses result in the same namespace being imported 394 That("use lorem; use lorem lorem2; put $lorem:name $lorem2:name"). 395 Puts("lorem", "lorem"), 396 // overriding module 397 That(`use d; put $d:name; use a/b/c/d; put $d:name`). 398 Puts("d", "a/b/c/d"), 399 // relative uses 400 That(`use a/b/c/x; put $x:d $x:lorem`).Puts("a/b/c/d", "lorem"), 401 // relative uses from top-level 402 That(`use ./d; put $d:name`).Puts("d"), 403 404 // Renaming module 405 That(`use a/b/c/d mod; put $mod:name`).Puts("a/b/c/d"), 406 407 // Variables defined in the default global scope is invisible from 408 // modules 409 That("x = foo; use put-x").Throws(AnyError), 410 411 // Using an unknown module spec fails. 412 That("use unknown").Throws(ErrorWithType(NoSuchModule{})), 413 That("use ./unknown").Throws(ErrorWithType(NoSuchModule{})), 414 That("use ../unknown").Throws(ErrorWithType(NoSuchModule{})), 415 416 // Nonexistent module 417 That("use non-existent").Throws(ErrorWithMessage("no such module: non-existent")), 418 419 // Wrong uses of "use". 420 That("use").DoesNotCompile(), 421 That("use a b c").DoesNotCompile(), 422 ) 423} 424 425// Regression test for #1072 426func TestUse_WarnsAboutDeprecatedFeatures(t *testing.T) { 427 progtest.SetDeprecationLevel(t, 17) 428 libdir := InTempDir(t) 429 MustWriteFile("dep.elv", "fn x { dir-history }") 430 431 TestWithSetup(t, func(ev *Evaler) { ev.LibDirs = []string{libdir} }, 432 // Importing module triggers check for deprecated features 433 That("use dep").PrintsStderrWith("is deprecated"), 434 ) 435} 436