123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555 |
- package eval_test
- import (
- "errors"
- "testing"
- "src.elv.sh/pkg/diag"
- . "src.elv.sh/pkg/eval"
- "src.elv.sh/pkg/eval/errs"
- "src.elv.sh/pkg/eval/vals"
- "src.elv.sh/pkg/eval/vars"
- "src.elv.sh/pkg/parse"
- "src.elv.sh/pkg/prog/progtest"
- . "src.elv.sh/pkg/eval/evaltest"
- . "src.elv.sh/pkg/testutil"
- )
- func TestPragma(t *testing.T) {
- Test(t,
- That("pragma unknown-command").DoesNotCompile(),
- That("pragma unknown-command =").DoesNotCompile(),
- That("pragma unknown-command x").DoesNotCompile(),
- That("pragma bad-name = some-value").DoesNotCompile(),
- That("pragma unknown-command = bad").DoesNotCompile(),
- )
- // Actual effect of the unknown-command pragma is tested in TestCommand_External
- }
- func TestVar(t *testing.T) {
- // NOTE: TestClosure has more tests for the interaction between assignment
- // and variable scoping.
- Test(t,
- // Declaring one variable
- That("var x", "put $x").Puts(nil),
- // Declaring one variable whose name needs to be quoted
- That("var 'a/b'", "put $'a/b'").Puts(nil),
- // Declaring one variable whose name ends in ":".
- That("var a:").DoesNothing(),
- // Declaring a variable whose name ends in "~" initializes it to the
- // builtin nop function.
- That("var cmd~; cmd &ignored-opt ignored-arg").DoesNothing(),
- // Declaring multiple variables
- That("var x y", "put $x $y").Puts(nil, nil),
- // Declaring one variable with initial value
- That("var x = foo", "put $x").Puts("foo"),
- // Declaring multiple variables with initial values
- That("var x y = foo bar", "put $x $y").Puts("foo", "bar"),
- // Declaring multiple variables with initial values, including a rest
- // variable in the assignment LHS
- That("var x @y z = a b c d", "put $x $y $z").
- Puts("a", vals.MakeList("b", "c"), "d"),
- // An empty RHS is technically legal although rarely useful.
- That("var @x =", "put $x").Puts(vals.EmptyList),
- // Shadowing.
- That("var x = old; fn f { put $x }", "var x = new; put $x; f").
- Puts("new", "old"),
- // Concurrently creating a new variable and accessing existing variable.
- // Run with "go test -race".
- That("var x = 1", "put $x | var y = (all)").DoesNothing(),
- That("nop (var x = 1) | nop").DoesNothing(),
- // Assignment errors when the RHS errors.
- That("var x = [][1]").Throws(ErrorWithType(errs.OutOfRange{}), "[][1]"),
- // Arity mismatch.
- That("var x = 1 2").Throws(
- errs.ArityMismatch{What: "assignment right-hand-side",
- ValidLow: 1, ValidHigh: 1, Actual: 2},
- "var x = 1 2"),
- That("var x y = 1").Throws(
- errs.ArityMismatch{What: "assignment right-hand-side",
- ValidLow: 2, ValidHigh: 2, Actual: 1},
- "var x y = 1"),
- That("var x y @z = 1").Throws(
- errs.ArityMismatch{What: "assignment right-hand-side",
- ValidLow: 2, ValidHigh: -1, Actual: 1},
- "var x y @z = 1"),
- // Variable name that must be quoted after $ must be quoted
- That("var a/b").DoesNotCompile(),
- // Multiple @ not allowed
- That("var x @y @z = a b c d").DoesNotCompile(),
- // Non-local not allowed
- That("var ns:a").DoesNotCompile(),
- // Index not allowed
- That("var a[0]").DoesNotCompile(),
- // Composite expression not allowed
- That("var a'b'").DoesNotCompile(),
- )
- }
- func TestSet(t *testing.T) {
- Test(t,
- // Setting one variable
- That("var x; set x = foo", "put $x").Puts("foo"),
- // An empty RHS is technically legal although rarely useful.
- That("var x; set @x =", "put $x").Puts(vals.EmptyList),
- // Variable must already exist
- That("set x = foo").DoesNotCompile(),
- // List element assignment
- That("var li = [foo bar]; set li[0] = 233; put $@li").Puts("233", "bar"),
- // Variable in list assignment must already be defined. Regression test
- // for b.elv.sh/889.
- That("set foobarlorem[0] = a").DoesNotCompile(),
- // Map element assignment
- That("var di = [&k=v]; set di[k] = lorem; set di[k2] = ipsum",
- "put $di[k] $di[k2]").Puts("lorem", "ipsum"),
- That("var d = [&a=[&b=v]]; put $d[a][b]; set d[a][b] = u; put $d[a][b]").
- Puts("v", "u"),
- That("var li = [foo]; set li[(fail foo)] = bar").Throws(FailError{"foo"}),
- That("var li = [foo]; set li[0 1] = foo bar").
- Throws(ErrorWithMessage("multi indexing not implemented")),
- That("var li = [[]]; set li[1][2] = bar").
- Throws(errs.OutOfRange{What: "index",
- ValidLow: "0", ValidHigh: "0", Actual: "1"}, "li[1][2]"),
- // Assignment to read-only var is a compile-time error.
- That("set nil = 1").DoesNotCompile(),
- That("var a b; set a true b = 1 2 3").DoesNotCompile(),
- That("set @true = 1").DoesNotCompile(),
- That("var r; set true @r = 1").DoesNotCompile(),
- That("var r; set @r true = 1").DoesNotCompile(),
- // Error conditions already covered by TestVar are not repeated.
- // = is required.
- That("var x; set x").DoesNotCompile(),
- )
- }
- func TestSet_ErrorInSetMethod(t *testing.T) {
- TestWithSetup(t, func(ev *Evaler) { addBadVar(ev, 0) },
- That("set bad = foo").Throws(errBadVar, "bad"),
- That("var a; set bad @a = foo").Throws(errBadVar, "bad"),
- That("var a; set a @bad = foo").Throws(errBadVar, "@bad"),
- That("var a; set @a bad = foo").Throws(errBadVar, "bad"),
- )
- }
- func TestTmp(t *testing.T) {
- Test(t,
- That("var x = foo; put $x; { tmp x = bar; put $x }; put $x").
- Puts("foo", "bar", "foo"),
- That("var x; tmp x = y").DoesNotCompile(),
- That("{ tmp x = y }").DoesNotCompile(),
- )
- }
- func TestTmp_ErrorSetting(t *testing.T) {
- TestWithSetup(t, func(ev *Evaler) { addBadVar(ev, 0) },
- That("{ tmp bad = foo }").Throws(errBadVar, "bad", "{ tmp bad = foo }"),
- )
- }
- func TestTmp_ErrorRestoring(t *testing.T) {
- TestWithSetup(t, func(ev *Evaler) { addBadVar(ev, 1) },
- That("{ tmp bad = foo; put after }").
- Puts("after").
- Throws(ErrorWithMessage("restore variable: bad var"),
- "bad", "{ tmp bad = foo; put after }"),
- )
- }
- func addBadVar(ev *Evaler, allowedSets int) {
- ev.ExtendGlobal(BuildNs().AddVar("bad", &badVar{allowedSets}))
- }
- var errBadVar = errors.New("bad var")
- type badVar struct{ allowedSets int }
- func (v *badVar) Get() interface{} { return nil }
- func (v *badVar) Set(interface{}) error {
- if v.allowedSets == 0 {
- return errBadVar
- }
- v.allowedSets--
- return nil
- }
- func TestDel(t *testing.T) {
- Setenv(t, "TEST_ENV", "test value")
- Test(t,
- // Deleting variable
- That("var x = 1; del x").DoesNothing(),
- That("var x = 1; del x; echo $x").DoesNotCompile(),
- // Deleting environment variable
- That("has-env TEST_ENV", "del E:TEST_ENV", "has-env TEST_ENV").Puts(true, false),
- // Deleting variable whose name contains special characters
- That("var 'a/b' = foo; del 'a/b'").DoesNothing(),
- // Deleting element
- That("var x = [&k=v &k2=v2]; del x[k2]; keys $x").Puts("k"),
- That("var x = [[&k=v &k2=v2]]; del x[0][k2]; keys $x[0]").Puts("k"),
- // Error cases
- // Deleting nonexistent variable
- That("del x").DoesNotCompile(),
- // Deleting element of nonexistent variable
- That("del x[0]").DoesNotCompile(),
- // Deleting variable in non-local namespace
- That("var a: = (ns [&b=$nil])", "del a:b").DoesNotCompile(),
- // Variable name given with $
- That("var x = 1; del $x").DoesNotCompile(),
- // Variable name not given as a single primary expression
- That("var ab = 1; del a'b'").DoesNotCompile(),
- // Variable name not a string
- That("del [a]").DoesNotCompile(),
- // Variable name has sigil
- That("var x = []; del @x").DoesNotCompile(),
- // Variable name not quoted when it should be
- That("var 'a/b' = foo; del a/b").DoesNotCompile(),
- // Index is multiple values
- That("var x = [&k1=v1 &k2=v2]", "del x[k1 k2]").Throws(
- ErrorWithMessage("index must evaluate to a single value in argument to del"),
- "k1 k2"),
- // Index expression throws exception
- That("var x = [&k]", "del x[(fail x)]").Throws(FailError{"x"}, "fail x"),
- // Value does not support element removal
- That("var x = (num 1)", "del x[k]").Throws(
- ErrorWithMessage("value does not support element removal"),
- // TODO: Fix the stack trace so that it is "x[k]"
- "x[k"),
- // Intermediate element does not exist
- That("var x = [&]", "del x[k][0]").Throws(
- ErrorWithMessage("no such key: k"),
- // TODO: Fix the stack trace so that it is "x[k]"
- "x"),
- )
- }
- func TestAnd(t *testing.T) {
- Test(t,
- That("and $true $false").Puts(false),
- That("and a b").Puts("b"),
- That("and $false b").Puts(false),
- That("and $true b").Puts("b"),
- // short circuit
- That("var x = a; and $false (x = b); put $x").Puts(false, "a"),
- // Exception
- That("and a (fail x)").Throws(FailError{"x"}, "fail x"),
- thatOutputErrorIsBubbled("and a"),
- )
- }
- func TestOr(t *testing.T) {
- Test(t,
- That("or $true $false").Puts(true),
- That("or a b").Puts("a"),
- That("or $false b").Puts("b"),
- That("or $true b").Puts(true),
- // short circuit
- That("var x = a; or $true (x = b); put $x").Puts(true, "a"),
- // Exception
- That("or $false (fail x)").Throws(FailError{"x"}, "fail x"),
- thatOutputErrorIsBubbled("or a"),
- )
- }
- func TestCoalesce(t *testing.T) {
- Test(t,
- That("coalesce a b").Puts("a"),
- That("coalesce $nil b").Puts("b"),
- That("coalesce $nil $nil").Puts(nil),
- That("coalesce").Puts(nil),
- // exception propagation
- That("coalesce $nil (fail foo)").Throws(FailError{"foo"}),
- // short circuit
- That("coalesce a (fail foo)").Puts("a"),
- thatOutputErrorIsBubbled("coalesce a"),
- )
- }
- func TestSpecialFormThunks(t *testing.T) {
- // Regression test for b.elv.sh/1456
- Test(t,
- That("for x [] {|arg| }").DoesNotCompile(),
- That("for x [] {|&opt=val| }").DoesNotCompile(),
- // The other special forms use the same utility under the hood and are
- // not repeated
- )
- }
- func TestIf(t *testing.T) {
- Test(t,
- That("if true { put then }").Puts("then"),
- That("if $false { put then } else { put else }").Puts("else"),
- That("if $false { put 1 } elif $false { put 2 } else { put 3 }").
- Puts("3"),
- That("if $false { put 2 } elif true { put 2 } else { put 3 }").Puts("2"),
- // Exception in condition expression
- That("if (fail x) { }").Throws(FailError{"x"}, "fail x"),
- )
- }
- func TestTry(t *testing.T) {
- Test(t,
- That("try { nop } catch { put bad } else { put good }").Puts("good"),
- That("try { fail tr } catch - { put bad } else { put good }").
- Puts("bad"),
- That("try { fail tr } finally { put final }").
- Puts("final").
- Throws(ErrorWithMessage("tr")),
- That("try { fail tr } catch { fail ex } finally { put final }").
- Puts("final").
- Throws(ErrorWithMessage("ex")),
- That("try { fail tr } catch { put ex } finally { fail final }").
- Puts("ex").
- Throws(ErrorWithMessage("final")),
- That("try { fail tr } catch { fail ex } finally { fail final }").
- Throws(ErrorWithMessage("final")),
- // except is a deprecated synonym for catch
- That("try { fail tr } except { put bad }").
- Puts("bad").PrintsStderrWith("deprecated"),
- // Must have catch or finally
- That("try { fail tr }").DoesNotCompile(),
- // Rest variable not allowed
- That("try { nop } catch @a { }").DoesNotCompile(),
- // A readonly var as a target for the "catch" clause is a compile-time
- // error.
- That("try { fail reason } catch nil { }").DoesNotCompile(),
- That("try { fail reason } catch x { }").DoesNothing(),
- // A quoted var name, that would be invalid as a bareword, should be allowed as the referent
- // in a `try...except...` block.
- That("try { fail hard } catch 'x=' { put 'x= ='(to-string $'x=') }").
- Puts("x= =[&reason=[&content=hard &type=fail]]"),
- )
- }
- func TestTry_ExceptIsDeprecated(t *testing.T) {
- testCompileTimeDeprecation(t, "try { } except { }",
- `"except" is deprecated; use "catch" instead`, 18)
- }
- func TestWhile(t *testing.T) {
- Test(t,
- That("var x = (num 0)", "while (< $x 4) { put $x; set x = (+ $x 1) }").
- Puts(0, 1, 2, 3),
- // break
- That("var x = (num 0)", "while (< $x 4) { put $x; break }").Puts(0),
- // continue
- That("var x = (num 0)",
- "while (< $x 4) { put $x; set x = (+ $x 1); continue; put bad }").
- Puts(0, 1, 2, 3),
- // Exception in body
- That("var x = 0; while (< $x 4) { fail haha }").Throws(FailError{"haha"}),
- // Exception in condition
- That("while (fail x) { }").Throws(FailError{"x"}, "fail x"),
- // else branch - not taken
- That("var x = 0; while (< $x 4) { put $x; set x = (+ $x 1) } else { put bad }").
- Puts("0", 1, 2, 3),
- // else branch - taken
- That("while $false { put bad } else { put good }").Puts("good"),
- )
- }
- func TestFor(t *testing.T) {
- Test(t,
- // for
- That("for x [tempora mores] { put 'O '$x }").
- Puts("O tempora", "O mores"),
- // break
- That("for x [a] { break } else { put $x }").DoesNothing(),
- // else
- That("for x [a] { put $x } else { put $x }").Puts("a"),
- // continue
- That("for x [a b] { put $x; continue; put $x; }").Puts("a", "b"),
- // else
- That("for x [] { } else { put else }").Puts("else"),
- That("for x [a] { } else { put else }").DoesNothing(),
- // Propagating exception.
- That("for x [a] { fail foo }").Throws(FailError{"foo"}),
- // More than one iterator.
- That("for {x,y} [] { }").DoesNotCompile(),
- // Invalid for loop lvalue. You can't use a var in a namespace other
- // than the local namespace as the lvalue in a for loop.
- That("for no-such-namespace:x [a b] { }").DoesNotCompile(),
- // Exception with the variable
- That("var a: = (ns [&])", "for a:b [] { }").Throws(
- ErrorWithMessage("no variable $a:b"),
- "a:b"),
- // Exception when evaluating iterable.
- That("for x [][0] { }").Throws(ErrorWithType(errs.OutOfRange{}), "[][0]"),
- // More than one iterable.
- That("for x (put a b) { }").Throws(
- errs.ArityMismatch{What: "value being iterated",
- ValidLow: 1, ValidHigh: 1, Actual: 2},
- "(put a b)"),
- // Non-iterable value
- That("for x (num 0) { }").Throws(ErrorWithMessage("cannot iterate number")),
- )
- }
- func TestFn(t *testing.T) {
- Test(t,
- That("fn f {|x| put x=$x'.' }; f lorem; f ipsum").
- Puts("x=lorem.", "x=ipsum."),
- // Recursive functions with fn. Regression test for #1206.
- That("fn f {|n| if (== $n 0) { num 1 } else { * $n (f (- $n 1)) } }; f 3").
- Puts(6),
- // Exception thrown by return is swallowed by a fn-defined function.
- That("fn f { put a; return; put b }; f").Puts("a"),
- // Error when evaluating the lambda
- That("fn f {|&opt=(fail x)| }").Throws(FailError{"x"}, "fail x"),
- )
- }
- // Regression test for #1225
- func TestUse_SetsVariableCorrectlyIfModuleCallsExtendGlobal(t *testing.T) {
- libdir := InTempDir(t)
- ApplyDir(Dir{"a.elv": "add-var"})
- ev := NewEvaler()
- ev.LibDirs = []string{libdir}
- addVar := func() {
- ev.ExtendGlobal(BuildNs().AddVar("b", vars.NewReadOnly("foo")))
- }
- ev.ExtendBuiltin(BuildNs().AddGoFn("add-var", addVar))
- err := ev.Eval(parse.Source{Code: "use a"}, EvalCfg{})
- if err != nil {
- t.Fatal(err)
- }
- g := ev.Global()
- if g.IndexString("a:").Get().(*Ns) == nil {
- t.Errorf("$a: is nil")
- }
- if g.IndexString("b").Get().(string) != "foo" {
- t.Errorf(`$b is not "foo"`)
- }
- }
- func TestUse_SupportsCircularDependency(t *testing.T) {
- libdir := InTempDir(t)
- ApplyDir(Dir{
- "a.elv": "var pre = apre; use b; put $b:pre $b:post; var post = apost",
- "b.elv": "var pre = bpre; use a; put $a:pre $a:post; var post = bpost",
- })
- TestWithSetup(t, func(ev *Evaler) { ev.LibDirs = []string{libdir} },
- That(`use a`).Puts(
- // When b.elv is imported from a.elv, $a:pre is set but $a:post is
- // not
- "apre", nil,
- // After a.elv imports b.elv, both $b:pre and $b:post are set
- "bpre", "bpost"),
- )
- }
- func TestUse(t *testing.T) {
- libdir1 := InTempDir(t)
- ApplyDir(Dir{
- "shadow.elv": "put lib1",
- })
- libdir2 := InTempDir(t)
- ApplyDir(Dir{
- "has-init.elv": "put has-init",
- "put-x.elv": "put $x",
- "lorem.elv": "var name = lorem; fn put-name { put $name }",
- "d.elv": "var name = d",
- "shadow.elv": "put lib2",
- "a": Dir{
- "b": Dir{
- "c": Dir{
- "d.elv": "var name = a/b/c/d",
- "x.elv": "use ./d; var d = $d:name; use ../../../lorem; var lorem = $lorem:name",
- },
- },
- },
- })
- TestWithSetup(t, func(ev *Evaler) { ev.LibDirs = []string{libdir1, libdir2} },
- That(`use lorem; put $lorem:name`).Puts("lorem"),
- // imports are lexically scoped
- // TODO: Support testing for compilation error
- That(`{ use lorem }; put $lorem:name`).DoesNotCompile(),
- // prefers lib dir that appear earlier
- That("use shadow").Puts("lib1"),
- // use of imported variable is captured in upvalue
- That(`use lorem; { put $lorem:name }`).Puts("lorem"),
- That(`{ use lorem; { put $lorem:name } }`).Puts("lorem"),
- That(`({ use lorem; put { { put $lorem:name } } })`).Puts("lorem"),
- // use of imported function is also captured in upvalue
- That(`{ use lorem; { lorem:put-name } }`).Puts("lorem"),
- // use of a nested module
- That(`use a/b/c/d; put $d:name`).Puts("a/b/c/d"),
- // module is cached after first use
- That(`use has-init; use has-init`).Puts("has-init"),
- // repeated uses result in the same namespace being imported
- That("use lorem; use lorem lorem2; put $lorem:name $lorem2:name").
- Puts("lorem", "lorem"),
- // overriding module
- That(`use d; put $d:name; use a/b/c/d; put $d:name`).
- Puts("d", "a/b/c/d"),
- // relative uses
- That(`use a/b/c/x; put $x:d $x:lorem`).Puts("a/b/c/d", "lorem"),
- // relative uses from top-level
- That(`use ./d; put $d:name`).Puts("d"),
- // Renaming module
- That(`use a/b/c/d mod; put $mod:name`).Puts("a/b/c/d"),
- // Variables defined in the default global scope is invisible from
- // modules
- That("var x = foo; use put-x").Throws(ErrorWithType(&diag.Error{})),
- // Using an unknown module spec fails.
- That("use unknown").Throws(ErrorWithType(NoSuchModule{})),
- That("use ./unknown").Throws(ErrorWithType(NoSuchModule{})),
- That("use ../unknown").Throws(ErrorWithType(NoSuchModule{})),
- // Nonexistent module
- That("use non-existent").Throws(ErrorWithMessage("no such module: non-existent")),
- // Wrong uses of "use".
- That("use").DoesNotCompile(),
- That("use a b c").DoesNotCompile(),
- )
- }
- // Regression test for #1072
- func TestUse_WarnsAboutDeprecatedFeatures(t *testing.T) {
- progtest.SetDeprecationLevel(t, 18)
- libdir := InTempDir(t)
- MustWriteFile("dep.elv", "a=b nop $a")
- TestWithSetup(t, func(ev *Evaler) { ev.LibDirs = []string{libdir} },
- // Importing module triggers check for deprecated features
- That("use dep").PrintsStderrWith("is deprecated"),
- )
- }
|