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  }