github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/builtin_special_test.go (about) 1 package eval_test 2 3 import ( 4 "path/filepath" 5 "strings" 6 "testing" 7 8 . "src.elv.sh/pkg/eval" 9 "src.elv.sh/pkg/eval/errs" 10 "src.elv.sh/pkg/eval/vals" 11 "src.elv.sh/pkg/eval/vars" 12 "src.elv.sh/pkg/parse" 13 14 . "src.elv.sh/pkg/eval/evaltest" 15 "src.elv.sh/pkg/prog" 16 . "src.elv.sh/pkg/testutil" 17 ) 18 19 func TestVar(t *testing.T) { 20 Test(t, 21 // Declaring one variable 22 That("var x", "put $x").Puts(nil), 23 // Declaring one variable whose name needs to be quoted 24 That("var 'a/b'", "put $'a/b'").Puts(nil), 25 // Declaring one variable whose name ends in ":". 26 That("var a:").DoesNothing(), 27 // Declaring a variable whose name ends in "~" initializes it to the 28 // builtin nop function. 29 That("var cmd~; cmd &ignored-opt ignored-arg").DoesNothing(), 30 // Declaring multiple variables 31 That("var x y", "put $x $y").Puts(nil, nil), 32 // Declaring one variable with initial value 33 That("var x = foo", "put $x").Puts("foo"), 34 // Declaring multiple variables with initial values 35 That("var x y = foo bar", "put $x $y").Puts("foo", "bar"), 36 // Declaring multiple variables with initial values, including a rest 37 // variable in the assignment LHS 38 That("var x @y z = a b c d", "put $x $y $z"). 39 Puts("a", vals.MakeList("b", "c"), "d"), 40 // An empty RHS is technically legal although rarely useful. 41 That("var @x =", "put $x").Puts(vals.EmptyList), 42 // Shadowing. 43 That("var x = old; fn f { put $x }", "var x = new; put $x; f"). 44 Puts("new", "old"), 45 46 // Variable name that must be quoted after $ must be quoted 47 That("var a/b").DoesNotCompile(), 48 // Multiple @ not allowed 49 That("var x @y @z = a b c d").DoesNotCompile(), 50 // Namespace not allowed 51 That("var local:a").DoesNotCompile(), 52 // Index not allowed 53 That("var a[0]").DoesNotCompile(), 54 // Composite expression not allowed 55 That("var a'b'").DoesNotCompile(), 56 ) 57 } 58 59 func TestSet(t *testing.T) { 60 Test(t, 61 // Setting one variable 62 That("var x; set x = foo", "put $x").Puts("foo"), 63 // An empty RHS is technically legal although rarely useful. 64 That("var x; set @x =", "put $x").Puts(vals.EmptyList), 65 // Not duplicating tests with TestCommand_Assignment. 66 // 67 // TODO: After legacy assignment form is removed, transfer tests here. 68 69 // = is required. 70 That("var x; set x").DoesNotCompile(), 71 ) 72 } 73 74 func TestDel(t *testing.T) { 75 Test(t, 76 // Deleting variable 77 That("x = 1; del x").DoesNothing(), 78 That("x = 1; del x; echo $x").DoesNotCompile(), 79 That("x = 1; del :x; echo $x").DoesNotCompile(), 80 That("x = 1; del local:x; echo $x").DoesNotCompile(), 81 // Deleting variable whose name contains special characters 82 That("'a/b' = foo; del 'a/b'").DoesNothing(), 83 // Deleting element 84 That("x = [&k=v &k2=v2]; del x[k2]; keys $x").Puts("k"), 85 That("x = [[&k=v &k2=v2]]; del x[0][k2]; keys $x[0]").Puts("k"), 86 87 // Error cases 88 89 // Deleting nonexistent variable 90 That("del x").DoesNotCompile(), 91 // Deleting element of nonexistent variable 92 That("del x[0]").DoesNotCompile(), 93 // Deleting variable in non-local namespace 94 That("del a:b").DoesNotCompile(), 95 // Variable name given with $ 96 That("x = 1; del $x").DoesNotCompile(), 97 // Variable name not given as a single primary expression 98 That("ab = 1; del a'b'").DoesNotCompile(), 99 // Variable name not a string 100 That("del [a]").DoesNotCompile(), 101 // Variable name has sigil 102 That("x = []; del @x").DoesNotCompile(), 103 // Variable name not quoted when it should be 104 That("'a/b' = foo; del a/b").DoesNotCompile(), 105 ) 106 } 107 108 func TestAnd(t *testing.T) { 109 Test(t, 110 That("and $true $false").Puts(false), 111 That("and a b").Puts("b"), 112 That("and $false b").Puts(false), 113 That("and $true b").Puts("b"), 114 // short circuit 115 That("x = a; and $false (x = b); put $x").Puts(false, "a"), 116 ) 117 } 118 119 func TestOr(t *testing.T) { 120 Test(t, 121 That("or $true $false").Puts(true), 122 That("or a b").Puts("a"), 123 That("or $false b").Puts("b"), 124 That("or $true b").Puts(true), 125 // short circuit 126 That("x = a; or $true (x = b); put $x").Puts(true, "a"), 127 ) 128 } 129 130 func TestIf(t *testing.T) { 131 Test(t, 132 That("if true { put then }").Puts("then"), 133 That("if $false { put then } else { put else }").Puts("else"), 134 That("if $false { put 1 } elif $false { put 2 } else { put 3 }"). 135 Puts("3"), 136 That("if $false { put 2 } elif true { put 2 } else { put 3 }").Puts("2"), 137 ) 138 } 139 140 func TestTry(t *testing.T) { 141 Test(t, 142 That("try { nop } except { put bad } else { put good }").Puts("good"), 143 That("try { e:false } except - { put bad } else { put good }"). 144 Puts("bad"), 145 That("try { fail tr }").Throws(ErrorWithMessage("tr")), 146 That("try { fail tr } finally { put final }"). 147 Puts("final"). 148 Throws(ErrorWithMessage("tr")), 149 150 That("try { fail tr } except { fail ex } finally { put final }"). 151 Puts("final"). 152 Throws(ErrorWithMessage("ex")), 153 154 That("try { fail tr } except { put ex } finally { fail final }"). 155 Puts("ex"). 156 Throws(ErrorWithMessage("final")), 157 158 That("try { fail tr } except { fail ex } finally { fail final }"). 159 Throws(ErrorWithMessage("final")), 160 161 // wrong syntax 162 That("try { nop } except @a { }").DoesNotCompile(), 163 164 // A quoted var name, that would be invalid as a bareword, should be allowed as the referent 165 // in a `try...except...` block. 166 That("try { fail hard } except 'x=' { put 'x= ='(to-string $'x=') }"). 167 Puts("x= =[&reason=[&content=hard &type=fail]]"), 168 ) 169 } 170 171 func TestWhile(t *testing.T) { 172 Test(t, 173 // while 174 That("var x = 0; while (< $x 4) { put $x; set x = (+ $x 1) }"). 175 Puts("0", 1, 2, 3), 176 That("var x = 0; while (< $x 4) { put $x; break }").Puts("0"), 177 That("var x = 0; while (< $x 4) { fail haha }").Throws(AnyError), 178 That("var x = 0; while (< $x 4) { put $x; set x = (+ $x 1) } else { put bad }"). 179 Puts("0", 1, 2, 3), 180 That("while $false { put bad } else { put good }").Puts("good"), 181 ) 182 } 183 184 func TestFor(t *testing.T) { 185 Test(t, 186 // for 187 That("for x [tempora mores] { put 'O '$x }"). 188 Puts("O tempora", "O mores"), 189 // break 190 That("for x [a] { break } else { put $x }").DoesNothing(), 191 // else 192 That("for x [a] { put $x } else { put $x }").Puts("a"), 193 // continue 194 That("for x [a b] { put $x; continue; put $x; }").Puts("a", "b"), 195 // More than one iterator. 196 That("for {x,y} [] { }").DoesNotCompile(), 197 // Invalid for loop lvalue. You can't use a var in a namespace other 198 // than the local namespace as the lvalue in a for loop. 199 That("for no-such-namespace:x [a b] { }").DoesNotCompile(), 200 // Exception when evaluating iterable. 201 That("for x [][0] { }").Throws(ErrorWithType(errs.OutOfRange{}), "[][0]"), 202 // More than one iterable. 203 That("for x (put a b) { }").Throws( 204 errs.ArityMismatch{ 205 What: "value being iterated", 206 ValidLow: 1, ValidHigh: 1, Actual: 2}, 207 "(put a b)"), 208 ) 209 } 210 211 func TestFn(t *testing.T) { 212 Test(t, 213 That("fn f [x]{ put x=$x'.' }; f lorem; f ipsum"). 214 Puts("x=lorem.", "x=ipsum."), 215 // Recursive functions with fn. Regression test for #1206. 216 That("fn f [n]{ if (== $n 0) { num 1 } else { * $n (f (- $n 1)) } }; f 3"). 217 Puts(6), 218 // Exception thrown by return is swallowed by a fn-defined function. 219 That("fn f []{ put a; return; put b }; f").Puts("a"), 220 ) 221 } 222 223 // Regression test for #1225 224 func TestUse_SetsVariableCorrectlyIfModuleCallsAddGlobal(t *testing.T) { 225 libdir, cleanup := InTestDir() 226 defer cleanup() 227 228 ApplyDir(Dir{"a.elv": "add-var"}) 229 ev := NewEvaler() 230 ev.SetLibDir(libdir) 231 addVar := func() { 232 ev.AddGlobal(NsBuilder{"b": vars.NewReadOnly("foo")}.Ns()) 233 } 234 ev.AddBuiltin(NsBuilder{}.AddGoFn("", "add-var", addVar).Ns()) 235 236 err := ev.Eval(parse.Source{Code: "use a"}, EvalCfg{}) 237 if err != nil { 238 t.Fatal(err) 239 } 240 241 g := ev.Global() 242 if g.IndexName("a:").Get().(*Ns) == nil { 243 t.Errorf("$a: is nil") 244 } 245 if g.IndexName("b").Get().(string) != "foo" { 246 t.Errorf(`$b is not "foo"`) 247 } 248 } 249 250 func TestUse_SupportsCircularDependency(t *testing.T) { 251 libdir, cleanup := InTestDir() 252 defer cleanup() 253 254 ApplyDir(Dir{ 255 "a.elv": "var pre = apre; use b; put $b:pre $b:post; var post = apost", 256 "b.elv": "var pre = bpre; use a; put $a:pre $a:post; var post = bpost", 257 }) 258 259 TestWithSetup(t, func(ev *Evaler) { ev.SetLibDir(libdir) }, 260 That(`use a`).Puts( 261 // When b.elv is imported from a.elv, $a:pre is set but $a:post is 262 // not 263 "apre", nil, 264 // After a.elv imports b.elv, both $b:pre and $b:post are set 265 "bpre", "bpost"), 266 ) 267 } 268 269 func TestUse(t *testing.T) { 270 libdir, cleanup := InTestDir() 271 defer cleanup() 272 273 MustMkdirAll(filepath.Join("a", "b", "c")) 274 275 writeMod := func(name, content string) { 276 fname := filepath.Join(strings.Split(name, "/")...) + ".elv" 277 MustWriteFile(fname, []byte(content), 0600) 278 } 279 writeMod("has-init", "put has-init") 280 writeMod("put-x", "put $x") 281 writeMod("lorem", "name = lorem; fn put-name { put $name }") 282 writeMod("d", "name = d") 283 writeMod("a/b/c/d", "name = a/b/c/d") 284 writeMod("a/b/c/x", 285 "use ./d; d = $d:name; use ../../../lorem; lorem = $lorem:name") 286 287 TestWithSetup(t, func(ev *Evaler) { ev.SetLibDir(libdir) }, 288 That(`use lorem; put $lorem:name`).Puts("lorem"), 289 // imports are lexically scoped 290 // TODO: Support testing for compilation error 291 // That(`{ use lorem }; put $lorem:name`).ErrorsAny(), 292 293 // use of imported variable is captured in upvalue 294 That(`use lorem; { put $lorem:name }`).Puts("lorem"), 295 That(`{ use lorem; { put $lorem:name } }`).Puts("lorem"), 296 That(`({ use lorem; put { { put $lorem:name } } })`).Puts("lorem"), 297 // use of imported function is also captured in upvalue 298 That(`{ use lorem; { lorem:put-name } }`).Puts("lorem"), 299 300 // use of a nested module 301 That(`use a/b/c/d; put $d:name`).Puts("a/b/c/d"), 302 // module is cached after first use 303 That(`use has-init; use has-init`).Puts("has-init"), 304 // repeated uses result in the same namespace being imported 305 That("use lorem; use lorem lorem2; put $lorem:name $lorem2:name"). 306 Puts("lorem", "lorem"), 307 // overriding module 308 That(`use d; put $d:name; use a/b/c/d; put $d:name`). 309 Puts("d", "a/b/c/d"), 310 // relative uses 311 That(`use a/b/c/x; put $x:d $x:lorem`).Puts("a/b/c/d", "lorem"), 312 // relative uses from top-level 313 That(`use ./d; put $d:name`).Puts("d"), 314 315 // Renaming module 316 That(`use a/b/c/d mod; put $mod:name`).Puts("a/b/c/d"), 317 318 // Variables defined in the default global scope is invisible from 319 // modules 320 That("x = foo; use put-x").Throws(AnyError), 321 322 // TODO: Test module namespace 323 324 // Wrong uses of "use". 325 That("use").DoesNotCompile(), 326 That("use a b c").DoesNotCompile(), 327 ) 328 } 329 330 // Regression test for #1072 331 func TestUse_WarnsAboutDeprecatedFeatures(t *testing.T) { 332 restore := prog.SetDeprecationLevel(16) 333 defer restore() 334 libdir, cleanup := InTestDir() 335 defer cleanup() 336 MustWriteFile("dep.elv", []byte("fn x { fopen x }"), 0600) 337 338 TestWithSetup(t, func(ev *Evaler) { ev.SetLibDir(libdir) }, 339 // Importing module triggers check for deprecated features 340 That("use dep").PrintsStderrWith("is deprecated"), 341 ) 342 }