2014-01-28 15:02:48 +08:00
|
|
|
package eval
|
|
|
|
|
|
|
|
import (
|
2016-01-31 08:40:49 +08:00
|
|
|
"errors"
|
2016-01-23 05:34:14 +08:00
|
|
|
"os"
|
2014-09-21 05:39:16 +08:00
|
|
|
"reflect"
|
2014-01-28 15:02:48 +08:00
|
|
|
"strconv"
|
2014-01-29 21:17:04 +08:00
|
|
|
"syscall"
|
|
|
|
"testing"
|
2014-09-21 05:39:16 +08:00
|
|
|
|
2016-01-25 01:10:54 +08:00
|
|
|
"github.com/elves/elvish/parse"
|
2016-02-17 02:14:05 +08:00
|
|
|
"github.com/elves/elvish/util"
|
2014-01-28 15:02:48 +08:00
|
|
|
)
|
|
|
|
|
2015-02-25 07:37:18 +08:00
|
|
|
func TestNewEvaler(t *testing.T) {
|
2016-01-29 21:01:51 +08:00
|
|
|
ev := NewEvaler(nil)
|
2014-01-28 15:02:48 +08:00
|
|
|
pid := strconv.Itoa(syscall.Getpid())
|
2016-01-29 20:55:14 +08:00
|
|
|
if ToString(ev.global["pid"].Get()) != pid {
|
2016-01-26 06:53:40 +08:00
|
|
|
t.Errorf(`ev.global["pid"] = %v, want %v`, ev.global["pid"], pid)
|
2014-01-28 15:02:48 +08:00
|
|
|
}
|
|
|
|
}
|
2014-09-21 05:39:16 +08:00
|
|
|
|
2016-02-08 06:23:16 +08:00
|
|
|
var errAny = errors.New("")
|
2016-01-31 08:40:49 +08:00
|
|
|
|
2016-01-23 05:34:14 +08:00
|
|
|
type more struct {
|
|
|
|
wantBytesOut []byte
|
2016-01-31 08:40:49 +08:00
|
|
|
wantError error
|
2016-01-23 05:34:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
var nomore more
|
|
|
|
|
2014-09-21 05:39:16 +08:00
|
|
|
var evalTests = []struct {
|
2016-01-23 05:34:14 +08:00
|
|
|
text string
|
|
|
|
wantOut []Value
|
|
|
|
more
|
2014-09-21 05:39:16 +08:00
|
|
|
}{
|
2016-02-21 04:10:56 +08:00
|
|
|
// Chunks.
|
2014-09-25 06:08:37 +08:00
|
|
|
// Empty chunk
|
2016-01-23 05:34:14 +08:00
|
|
|
{"", []Value{}, nomore},
|
|
|
|
// Outputs of pipelines in a chunk are concatenated
|
|
|
|
{"put x; put y; put z", strs("x", "y", "z"), nomore},
|
|
|
|
// A failed pipeline cause the whole chunk to fail
|
2016-01-31 08:40:49 +08:00
|
|
|
{"put a; false; put b", strs("a"), more{wantError: errors.New("1")}},
|
2014-09-25 06:08:37 +08:00
|
|
|
|
2016-02-21 04:10:56 +08:00
|
|
|
// Pipelines.
|
2016-01-23 05:34:14 +08:00
|
|
|
// Pure byte pipeline
|
|
|
|
{`echo "Albert\nAllan\nAlbraham\nBerlin" | sed s/l/1/g | grep e`,
|
|
|
|
[]Value{}, more{wantBytesOut: []byte("A1bert\nBer1in\n")}},
|
2016-01-29 04:30:32 +08:00
|
|
|
// Pure channel pipeline
|
|
|
|
{`put 233 42 19 | each [x]{+ $x 10}`, strs("243", "52", "29"), nomore},
|
2016-01-23 05:49:32 +08:00
|
|
|
// TODO: Add a useful hybrid pipeline sample
|
2014-09-25 06:08:37 +08:00
|
|
|
|
2016-02-21 04:10:56 +08:00
|
|
|
// Forms.
|
|
|
|
// List element assignment
|
|
|
|
{"li=[foo bar]; li[0]=233; put-all $li", strs("233", "bar"), nomore},
|
|
|
|
// Map element assignment
|
|
|
|
{"di=[&k=v]; di[k]=lorem; di[k2]=ipsum; put $di[k] $di[k2]",
|
|
|
|
strs("lorem", "ipsum"), nomore},
|
2016-02-21 04:43:25 +08:00
|
|
|
{"d=[&a=[&b=v]]; put $d[a][b]; d[a][b]=u; put $d[a][b]",
|
|
|
|
strs("v", "u"), nomore},
|
|
|
|
// Control structures.
|
|
|
|
{"if true; then put then; fi", strs("then"), nomore},
|
|
|
|
{"if false; then put then; else put else; fi", strs("else"), nomore},
|
|
|
|
{"if false; then put 1; elif false; then put 2; else put 3; fi",
|
|
|
|
strs("3"), nomore},
|
|
|
|
{"if false; then put 2; elif true; then put 2; else put 3; fi",
|
|
|
|
strs("2"), nomore},
|
|
|
|
{"x=0; while lt $x 4; do put $x; x=(+ $x 1); done",
|
|
|
|
strs("0", "1", "2", "3"), nomore},
|
|
|
|
{"for x in tempora mores; do put 'O '$x; done",
|
|
|
|
strs("O tempora", "O mores"), nomore},
|
|
|
|
{"for x in a; do break; else put $x; done", strs(), nomore},
|
|
|
|
{"for x in a; do put $x; else put $x; done", strs("a"), nomore},
|
|
|
|
{"begin; put lorem; put ipsum; end", strs("lorem", "ipsum"), nomore},
|
|
|
|
// Redirections.
|
|
|
|
{"f=`mktemp`; echo 233 > $f; cat < $f", strs(),
|
|
|
|
more{wantBytesOut: []byte("233\n")}},
|
2014-09-25 06:08:37 +08:00
|
|
|
|
2016-02-21 04:10:56 +08:00
|
|
|
// Compounding.
|
2016-01-23 01:05:15 +08:00
|
|
|
{"put {fi,elvi}sh{1.0,1.1}",
|
2016-01-23 05:34:14 +08:00
|
|
|
strs("fish1.0", "fish1.1", "elvish1.0", "elvish1.1"), nomore},
|
2014-09-25 06:08:37 +08:00
|
|
|
|
2016-01-23 01:05:15 +08:00
|
|
|
// List, map and indexing
|
2016-02-19 20:55:35 +08:00
|
|
|
{"println [a b c] [&key=value] | from-lines",
|
|
|
|
strs("[a b c] [&key=value]"), nomore},
|
2016-01-23 05:34:14 +08:00
|
|
|
{"put [a b c][2]", strs("c"), nomore},
|
2016-02-19 20:55:35 +08:00
|
|
|
{"put [&key=value][key]", strs("value"), nomore},
|
2016-01-23 05:34:14 +08:00
|
|
|
|
2016-02-21 04:10:56 +08:00
|
|
|
// String literal
|
|
|
|
{`put 'such \"''literal'`, strs(`such \"'literal`), nomore},
|
|
|
|
{`put "much \n\033[31;1m$cool\033[m"`,
|
|
|
|
strs("much \n\033[31;1m$cool\033[m"), nomore},
|
|
|
|
|
2016-01-23 05:49:32 +08:00
|
|
|
// Output capture
|
|
|
|
{"put (put lorem ipsum)", strs("lorem", "ipsum"), nomore},
|
|
|
|
|
|
|
|
// Status capture
|
|
|
|
{"put ?(true|false|false)",
|
2016-02-08 06:23:16 +08:00
|
|
|
[]Value{newMultiError(OK, Error{errors.New("1")},
|
|
|
|
Error{errors.New("1")})}, nomore},
|
2016-01-23 05:49:32 +08:00
|
|
|
|
2016-01-23 07:56:09 +08:00
|
|
|
// Variable and compounding
|
2016-02-01 03:36:14 +08:00
|
|
|
{"x='SHELL'\nput 'WOW, SUCH '$x', MUCH COOL'\n",
|
2016-01-23 07:56:09 +08:00
|
|
|
strs("WOW, SUCH SHELL, MUCH COOL"), nomore},
|
2016-02-07 10:12:54 +08:00
|
|
|
// Splicing
|
|
|
|
{"x=[elvish rules]; put $@x", strs("elvish", "rules"), nomore},
|
2016-01-23 07:56:09 +08:00
|
|
|
|
2016-02-09 07:05:16 +08:00
|
|
|
// Wildcard.
|
2016-02-17 02:14:05 +08:00
|
|
|
{"put /*", strs(util.RootNames()...), nomore},
|
2016-02-09 07:05:16 +08:00
|
|
|
|
2016-02-21 04:43:25 +08:00
|
|
|
// Tilde.
|
|
|
|
{"h=$env:HOME; env:HOME=/foo; put ~ ~/src; env:HOME=$h",
|
|
|
|
strs("/foo", "/foo/src"), nomore},
|
|
|
|
|
2016-01-23 07:56:09 +08:00
|
|
|
// Closure
|
|
|
|
// Basics
|
|
|
|
{"[]{ }", strs(), nomore},
|
|
|
|
{"[x]{put $x} foo", strs("foo"), nomore},
|
2016-01-26 06:28:24 +08:00
|
|
|
// Variable capture
|
2016-02-01 03:36:14 +08:00
|
|
|
{"x=lorem; []{x=ipsum}; put $x", strs("ipsum"), nomore},
|
|
|
|
{"x=lorem; []{ put $x; x=ipsum }; put $x",
|
2016-01-23 07:56:09 +08:00
|
|
|
strs("lorem", "ipsum"), nomore},
|
|
|
|
// Shadowing
|
2016-02-01 03:36:14 +08:00
|
|
|
{"x=ipsum; []{ local:x=lorem; put $x }; put $x",
|
2016-01-23 07:56:09 +08:00
|
|
|
strs("lorem", "ipsum"), nomore},
|
|
|
|
// Shadowing by argument
|
2016-02-01 03:36:14 +08:00
|
|
|
{"x=ipsum; [x]{ put $x; x=BAD } lorem; put $x",
|
2016-01-23 07:56:09 +08:00
|
|
|
strs("lorem", "ipsum"), nomore},
|
2016-01-24 23:06:14 +08:00
|
|
|
// Closure captures new local variables every time
|
2016-02-01 03:36:14 +08:00
|
|
|
{`fn f []{ x=0; put []{x=(+ $x 1)} []{put $x} }
|
|
|
|
{inc1,put1}=(f); $put1; $inc1; $put1
|
|
|
|
{inc2,put2}=(f); $put2; $inc2; $put2`,
|
2016-01-24 23:06:14 +08:00
|
|
|
strs("0", "1", "0", "1"), nomore},
|
2016-01-23 05:34:14 +08:00
|
|
|
|
2016-01-23 08:24:47 +08:00
|
|
|
// fn
|
2016-02-10 00:57:29 +08:00
|
|
|
{"fn f [x]{ put x=$x'.' }; f lorem; f ipsum",
|
|
|
|
strs("x=lorem.", "x=ipsum."), nomore},
|
2016-01-23 08:02:26 +08:00
|
|
|
|
|
|
|
// Namespaces
|
|
|
|
// Pseudo-namespaces local: and up:
|
2016-02-01 03:36:14 +08:00
|
|
|
{"x=lorem; []{local:x=ipsum; put $up:x $local:x}",
|
2016-01-23 08:02:26 +08:00
|
|
|
strs("lorem", "ipsum"), nomore},
|
2016-02-01 03:36:14 +08:00
|
|
|
{"x=lorem; []{up:x=ipsum; put $x}; put $x",
|
2016-01-23 08:02:26 +08:00
|
|
|
strs("ipsum", "ipsum"), nomore},
|
|
|
|
// Pseudo-namespace env:
|
2016-02-01 03:36:14 +08:00
|
|
|
{"env:foo=lorem; put $env:foo", strs("lorem"), nomore},
|
2016-01-23 08:02:26 +08:00
|
|
|
{"del env:foo; put $env:foo", strs(""), nomore},
|
|
|
|
// TODO: Test module namespace
|
2016-02-01 16:20:01 +08:00
|
|
|
|
2016-02-21 04:10:56 +08:00
|
|
|
// Builtin functions
|
|
|
|
// Arithmetics
|
|
|
|
// TODO test more edge cases
|
|
|
|
{"+ 233100 233", strs("233333"), nomore},
|
|
|
|
{"- 233333 233100", strs("233"), nomore},
|
|
|
|
{"mul 353 661", strs("233333"), nomore},
|
|
|
|
{"div 233333 353", strs("661"), nomore},
|
|
|
|
{"div 1 0", strs("+Inf"), nomore},
|
2016-02-01 16:20:01 +08:00
|
|
|
// Equality
|
2016-02-16 08:54:19 +08:00
|
|
|
{"put ?(= a a) ?(= [] []) ?(= [&] [&])",
|
|
|
|
[]Value{Error{nil}, Error{ErrNotEqual}, Error{ErrNotEqual}}, nomore},
|
2016-02-01 16:20:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func strs(ss ...string) []Value {
|
|
|
|
vs := make([]Value, len(ss))
|
|
|
|
for i, s := range ss {
|
|
|
|
vs[i] = String(s)
|
|
|
|
}
|
|
|
|
return vs
|
|
|
|
}
|
|
|
|
|
|
|
|
func bools(bs ...bool) []Value {
|
|
|
|
vs := make([]Value, len(bs))
|
|
|
|
for i, b := range bs {
|
|
|
|
vs[i] = Bool(b)
|
|
|
|
}
|
|
|
|
return vs
|
2016-01-23 01:05:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func mustParse(t *testing.T, name, text string) *parse.Chunk {
|
2016-02-06 07:43:51 +08:00
|
|
|
n, err := parse.Parse(text)
|
2016-01-23 01:05:15 +08:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Parser(%q) error: %s", text, err)
|
|
|
|
}
|
|
|
|
return n
|
2014-09-21 05:39:16 +08:00
|
|
|
}
|
|
|
|
|
2016-01-31 08:40:49 +08:00
|
|
|
func evalAndCollect(t *testing.T, texts []string, chsize int) ([]Value, []byte, error) {
|
2014-09-21 05:39:16 +08:00
|
|
|
name := "<eval test>"
|
2016-01-29 21:01:51 +08:00
|
|
|
ev := NewEvaler(nil)
|
2016-01-23 05:34:14 +08:00
|
|
|
|
|
|
|
// Collect byte output
|
|
|
|
outBytes := []byte{}
|
|
|
|
pr, pw, _ := os.Pipe()
|
2016-02-09 01:51:06 +08:00
|
|
|
bytesDone := make(chan struct{})
|
2016-01-23 05:34:14 +08:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
var buf [64]byte
|
|
|
|
nr, err := pr.Read(buf[:])
|
|
|
|
outBytes = append(outBytes, buf[:nr]...)
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2016-02-09 01:51:06 +08:00
|
|
|
close(bytesDone)
|
2016-01-23 05:34:14 +08:00
|
|
|
}()
|
|
|
|
|
|
|
|
// Channel output
|
2015-02-25 21:47:50 +08:00
|
|
|
outs := []Value{}
|
2014-09-21 05:39:16 +08:00
|
|
|
|
2016-01-31 00:11:43 +08:00
|
|
|
// Exit. Only the exit of the last text is saved.
|
2016-01-31 08:40:49 +08:00
|
|
|
var ex error
|
2016-01-23 05:34:14 +08:00
|
|
|
|
2015-02-25 21:47:50 +08:00
|
|
|
for _, text := range texts {
|
2016-01-23 01:05:15 +08:00
|
|
|
n := mustParse(t, name, text)
|
2015-02-25 21:47:50 +08:00
|
|
|
|
2016-02-09 01:51:06 +08:00
|
|
|
outCh := make(chan Value, chsize)
|
|
|
|
outDone := make(chan struct{})
|
2014-09-21 05:39:16 +08:00
|
|
|
go func() {
|
2016-02-09 01:51:06 +08:00
|
|
|
for v := range outCh {
|
2014-09-21 05:39:16 +08:00
|
|
|
outs = append(outs, v)
|
|
|
|
}
|
2016-02-09 01:51:06 +08:00
|
|
|
close(outDone)
|
2014-09-21 05:39:16 +08:00
|
|
|
}()
|
|
|
|
|
2016-02-09 03:11:20 +08:00
|
|
|
ports := []*Port{
|
|
|
|
{File: os.Stdin},
|
2016-02-09 08:34:19 +08:00
|
|
|
{File: pw, Chan: outCh},
|
2016-02-09 03:11:20 +08:00
|
|
|
{File: os.Stderr},
|
2016-02-09 01:51:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
ex = ev.Eval(name, text, n, ports)
|
2016-02-09 08:34:19 +08:00
|
|
|
close(outCh)
|
2016-02-09 01:51:06 +08:00
|
|
|
<-outDone
|
2015-02-25 21:47:50 +08:00
|
|
|
}
|
2016-02-09 01:51:06 +08:00
|
|
|
|
2016-01-23 05:34:14 +08:00
|
|
|
pw.Close()
|
2016-02-09 01:51:06 +08:00
|
|
|
<-bytesDone
|
2016-01-31 08:40:49 +08:00
|
|
|
return outs, outBytes, ex
|
2015-02-25 21:47:50 +08:00
|
|
|
}
|
|
|
|
|
2016-02-12 02:11:38 +08:00
|
|
|
func init() {
|
|
|
|
PutInForeground = false
|
|
|
|
}
|
|
|
|
|
2015-02-25 21:47:50 +08:00
|
|
|
func TestEval(t *testing.T) {
|
|
|
|
for _, tt := range evalTests {
|
2016-01-31 08:40:49 +08:00
|
|
|
// fmt.Printf("eval %q\n", tt.text)
|
|
|
|
|
|
|
|
out, bytesOut, err := evalAndCollect(t, []string{tt.text}, len(tt.wantOut))
|
2015-02-25 19:21:06 +08:00
|
|
|
|
2016-01-23 08:24:47 +08:00
|
|
|
good := true
|
|
|
|
errorf := func(format string, args ...interface{}) {
|
|
|
|
if good {
|
|
|
|
good = false
|
|
|
|
t.Errorf("eval(%q) fails:", tt.text)
|
|
|
|
}
|
|
|
|
t.Errorf(format, args...)
|
|
|
|
}
|
2016-01-23 05:34:14 +08:00
|
|
|
|
2016-01-23 08:24:47 +08:00
|
|
|
if tt.wantBytesOut != nil && !reflect.DeepEqual(tt.wantBytesOut, bytesOut) {
|
|
|
|
errorf("got bytesOut=%q, want %q", bytesOut, tt.wantBytesOut)
|
|
|
|
}
|
2016-02-08 06:23:16 +08:00
|
|
|
if !(tt.wantError == errAny && err != nil) && !reflect.DeepEqual(tt.wantError, err) {
|
2016-02-21 04:43:25 +08:00
|
|
|
errorf("got err=%v, want %v", err, tt.wantError)
|
2016-01-24 07:01:50 +08:00
|
|
|
}
|
2016-01-23 08:24:47 +08:00
|
|
|
if !reflect.DeepEqual(tt.wantOut, out) {
|
|
|
|
errorf("got out=%v, want %v", out, tt.wantOut)
|
|
|
|
}
|
|
|
|
if !good {
|
|
|
|
t.Errorf("--------------")
|
2014-09-21 05:39:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-25 21:47:50 +08:00
|
|
|
|
|
|
|
func TestMultipleEval(t *testing.T) {
|
2016-02-01 18:42:28 +08:00
|
|
|
texts := []string{"x=hello", "put $x"}
|
|
|
|
outs, _, err := evalAndCollect(t, texts, 1)
|
2016-01-23 05:34:14 +08:00
|
|
|
wanted := strs("hello")
|
2015-02-25 21:47:50 +08:00
|
|
|
if err != nil {
|
2016-02-01 18:42:28 +08:00
|
|
|
t.Errorf("eval %s => %v, want nil", texts, err)
|
2015-02-25 21:47:50 +08:00
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(outs, wanted) {
|
2016-02-01 18:42:28 +08:00
|
|
|
t.Errorf("eval %s outputs %v, want %v", texts, outs, wanted)
|
2015-02-25 21:47:50 +08:00
|
|
|
}
|
|
|
|
}
|