Test almost all commands correctly bubble output errors.

Also make the helper thatOutputErrorIsBubbled more precise by matching for the
exact error that is thrown.
This commit is contained in:
Qi Xiao 2021-06-24 23:24:43 +01:00
parent 20274d3245
commit 82b9bddb15
9 changed files with 78 additions and 16 deletions

View File

@ -408,9 +408,16 @@ func dissoc(a, k interface{}) (interface{}, error) {
//
// @cf one
func all(fm *Frame, inputs Inputs) {
func all(fm *Frame, inputs Inputs) error {
out := fm.ValueOutput()
inputs(func(v interface{}) { out.Put(v) })
var errOut error
inputs(func(v interface{}) {
if errOut != nil {
return
}
errOut = out.Put(v)
})
return errOut
}
//elvdoc:fn one
@ -466,15 +473,20 @@ func one(fm *Frame, inputs Inputs) error {
//
// Etymology: Haskell.
func take(fm *Frame, n int, inputs Inputs) {
func take(fm *Frame, n int, inputs Inputs) error {
out := fm.ValueOutput()
var errOut error
i := 0
inputs(func(v interface{}) {
if errOut != nil {
return
}
if i < n {
out.Put(v)
errOut = out.Put(v)
}
i++
})
return errOut
}
//elvdoc:fn drop
@ -504,15 +516,20 @@ func take(fm *Frame, n int, inputs Inputs) {
//
// @cf take
func drop(fm *Frame, n int, inputs Inputs) {
func drop(fm *Frame, n int, inputs Inputs) error {
out := fm.ValueOutput()
var errOut error
i := 0
inputs(func(v interface{}) {
if errOut != nil {
return
}
if i >= n {
out.Put(v)
errOut = out.Put(v)
}
i++
})
return errOut
}
//elvdoc:fn has-value

View File

@ -59,19 +59,14 @@ func TestRange(t *testing.T) {
// non-positive int step
That("range &step=0 10").
Throws(errs.BadValue{What: "step", Valid: "positive", Actual: "0"}),
thatOutputErrorIsBubbled("range 1"),
That("range 10_000_000_000_000_000_000 10_000_000_000_000_000_003").
Puts(
vals.ParseNum("10_000_000_000_000_000_000"),
vals.ParseNum("10_000_000_000_000_000_001"),
vals.ParseNum("10_000_000_000_000_000_002")),
That("range 10_000_000_000_000_000_000 10_000_000_000_000_000_003 &step=2").
Puts(
vals.ParseNum("10_000_000_000_000_000_000"),
vals.ParseNum("10_000_000_000_000_000_002")),
That("range "+z+" "+z3).Puts(bigInt(z), bigInt(z1), bigInt(z2)),
That("range "+z+" "+z3+" &step=2").Puts(bigInt(z), bigInt(z2)),
// non-positive bigint step
That("range &step=-"+z+" 10").
Throws(errs.BadValue{What: "step", Valid: "positive", Actual: "-" + z}),
thatOutputErrorIsBubbled("range "+z+" "+z1),
That("range 23/10").Puts(0, 1, 2),
That("range 1/10 23/10").Puts(
@ -81,6 +76,7 @@ func TestRange(t *testing.T) {
// non-positive bigrat step
That("range &step=-1/2 10").
Throws(errs.BadValue{What: "step", Valid: "positive", Actual: "-1/2"}),
thatOutputErrorIsBubbled("range 1/2 3/2"),
That("range 1.2").Puts(0.0, 1.0),
That("range &step=0.5 1 3").Puts(1.0, 1.5, 2.0, 2.5),
@ -90,12 +86,14 @@ func TestRange(t *testing.T) {
// non-positive float64 step
That("range &step=-0.5 10").
Throws(errs.BadValue{What: "step", Valid: "positive", Actual: "-0.5"}),
thatOutputErrorIsBubbled("range 1.2"),
)
}
func TestRepeat(t *testing.T) {
Test(t,
That(`repeat 4 foo`).Puts("foo", "foo", "foo", "foo"),
thatOutputErrorIsBubbled("repeat 1 foo"),
)
}
@ -118,6 +116,7 @@ func TestAll(t *testing.T) {
That(`put foo bar | all`).Puts("foo", "bar"),
That(`echo foobar | all`).Puts("foobar"),
That(`all [foo bar]`).Puts("foo", "bar"),
thatOutputErrorIsBubbled("all [foo]"),
)
}
@ -129,18 +128,21 @@ func TestOne(t *testing.T) {
That(`one [foo]`).Puts("foo"),
That(`one []`).Throws(AnyError),
That(`one [foo bar]`).Throws(AnyError),
thatOutputErrorIsBubbled("one [foo]"),
)
}
func TestTake(t *testing.T) {
Test(t,
That(`range 100 | take 2`).Puts(0, 1),
thatOutputErrorIsBubbled("take 1 [foo]"),
)
}
func TestDrop(t *testing.T) {
Test(t,
That(`range 100 | drop 98`).Puts(98, 99),
thatOutputErrorIsBubbled("drop 1 [foo bar]"),
)
}
@ -184,6 +186,7 @@ func TestKeys(t *testing.T) {
// Windows does not have an external sort command. Disabled until we have a
// builtin sort command.
That(`keys [&a=foo &b=bar] | order`).Puts("a", "b"),
thatOutputErrorIsBubbled("keys [&a=foo]"),
)
}
@ -266,5 +269,7 @@ func TestOrder(t *testing.T) {
// are equal, an check that the order among them has not changed.
That("put l x o x r x e x m | order &less-than=[a b]{ eq $a x }").
Puts("x", "x", "x", "x", "l", "o", "r", "e", "m"),
thatOutputErrorIsBubbled("order [foo]"),
)
}

View File

@ -1,8 +1,10 @@
package eval_test
import (
"os"
"testing"
"src.elv.sh/pkg/eval"
"src.elv.sh/pkg/eval/errs"
. "src.elv.sh/pkg/eval/evaltest"
"src.elv.sh/pkg/eval/vals"
@ -204,5 +206,5 @@ func TestPrintf(t *testing.T) {
}
func thatOutputErrorIsBubbled(code string) TestCase {
return That(code + " >&-").Throws(AnyError)
return That(code + " >&-").Throws(OneOfErrors(os.ErrInvalid, eval.ErrNoValueOutput))
}

View File

@ -12,9 +12,17 @@ import (
"src.elv.sh/pkg/testutil"
)
func TestKindOf(t *testing.T) {
Test(t,
That("kind-of a []").Puts("string", "list"),
thatOutputErrorIsBubbled("kind-of a"),
)
}
func TestConstantly(t *testing.T) {
Test(t,
That(`f = (constantly foo); $f; $f`).Puts("foo", "foo"),
thatOutputErrorIsBubbled("(constantly foo)"),
)
}
@ -74,6 +82,8 @@ func TestTime(t *testing.T) {
That("time &on-end=[_]{ fail on-end } { fail body }").Throws(
FailError{"body"}),
thatOutputErrorIsBubbled("time { }"),
)
}

View File

@ -29,6 +29,7 @@ func TestStringComparisonCommands(t *testing.T) {
func TestToString(t *testing.T) {
Test(t,
That(`to-string str (num 1) $true`).Puts("str", "1", "$true"),
thatOutputErrorIsBubbled("to-string str"),
)
}
@ -38,6 +39,7 @@ func TestBase(t *testing.T) {
That(`base 16 42 233`).Puts("2a", "e9"),
That(`base 1 1`).Throws(AnyError), // no base-1
That(`base 37 10`).Throws(AnyError), // no letter for base-37
thatOutputErrorIsBubbled("base 2 1"),
)
}

View File

@ -138,6 +138,7 @@ func TestAnd(t *testing.T) {
// Exception
That("and a (fail x)").Throws(FailError{"x"}, "fail x"),
thatOutputErrorIsBubbled("and a"),
)
}
@ -152,6 +153,7 @@ func TestOr(t *testing.T) {
// Exception
That("or $false (fail x)").Throws(FailError{"x"}, "fail x"),
thatOutputErrorIsBubbled("or a"),
)
}

View File

@ -132,3 +132,18 @@ func (e errCmdExit) matchError(gotErr error) bool {
ge := gotErr.(eval.ExternalCmdExit)
return e.v.CmdName == ge.CmdName && e.v.WaitStatus == ge.WaitStatus
}
type errOneOf struct{ errs []error }
func OneOfErrors(errs ...error) error { return errOneOf{errs} }
func (e errOneOf) Error() string { return fmt.Sprint("one of", e.errs) }
func (e errOneOf) matchError(gotError error) bool {
for _, want := range e.errs {
if matchErr(want, gotError) {
return true
}
}
return false
}

View File

@ -45,6 +45,9 @@ func TestRe(t *testing.T) {
// Basic verification of &posix behavior.
That("put (re:find &posix 'a(x|xy)+' AaxyxxxyZ)[text]").Puts("axyxxxy"),
// re:find bubbles output error
That("re:find . ab >&-").Throws(eval.ErrNoValueOutput),
That("re:replace '(ba|z)sh' '${1}SH' 'bash and zsh'").Puts("baSH and zSH"),
That("re:replace &literal '(ba|z)sh' '$sh' 'bash and zsh'").Puts("$sh and $sh"),
That("re:replace '(ba|z)sh' [x]{ put [&bash=BaSh &zsh=ZsH][$x] } 'bash and zsh'").Puts("BaSh and ZsH"),
@ -66,6 +69,9 @@ func TestRe(t *testing.T) {
// Invalid pattern in re:split
That("re:split '(' x").Throws(AnyError),
// re:split bubbles output error
That("re:split . ab >&-").Throws(eval.ErrNoValueOutput),
That("re:quote a.txt").Puts(`a\.txt`),
That("re:quote '(*)'").Puts(`\(\*\)`),
)

View File

@ -97,14 +97,17 @@ func TestStr(t *testing.T) {
That(`str:split : /usr:/bin:/tmp`).Puts("/usr", "/bin", "/tmp"),
That(`str:split : /usr:/bin:/tmp &max=2`).Puts("/usr", "/bin:/tmp"),
That("str:split : a:b >&-").Throws(eval.ErrNoValueOutput),
That(`str:to-codepoints a`).Puts("0x61"),
That(`str:to-codepoints 你好`).Puts("0x4f60", "0x597d"),
That(`str:to-codepoints 你好 | str:from-codepoints (all)`).Puts("你好"),
That("str:to-codepoints a >&-").Throws(eval.ErrNoValueOutput),
That(`str:to-utf8-bytes a`).Puts("0x61"),
That(`str:to-utf8-bytes 你好`).Puts("0xe4", "0xbd", "0xa0", "0xe5", "0xa5", "0xbd"),
That(`str:to-utf8-bytes 你好 | str:from-utf8-bytes (all)`).Puts("你好"),
That("str:to-utf8-bytes a >&-").Throws(eval.ErrNoValueOutput),
That(`str:title abc`).Puts("Abc"),
That(`str:title "abc def"`).Puts("Abc Def"),