diff --git a/pkg/eval/builtin_fn_container.go b/pkg/eval/builtin_fn_container.go index 237f40ca..4da1360e 100644 --- a/pkg/eval/builtin_fn_container.go +++ b/pkg/eval/builtin_fn_container.go @@ -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 diff --git a/pkg/eval/builtin_fn_container_test.go b/pkg/eval/builtin_fn_container_test.go index 2dcc37e9..08f0b111 100644 --- a/pkg/eval/builtin_fn_container_test.go +++ b/pkg/eval/builtin_fn_container_test.go @@ -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]"), ) } diff --git a/pkg/eval/builtin_fn_io_test.go b/pkg/eval/builtin_fn_io_test.go index f0ee0387..aafb1010 100644 --- a/pkg/eval/builtin_fn_io_test.go +++ b/pkg/eval/builtin_fn_io_test.go @@ -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)) } diff --git a/pkg/eval/builtin_fn_misc_test.go b/pkg/eval/builtin_fn_misc_test.go index 00c5431c..377efdff 100644 --- a/pkg/eval/builtin_fn_misc_test.go +++ b/pkg/eval/builtin_fn_misc_test.go @@ -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 { }"), ) } diff --git a/pkg/eval/builtin_fn_str_test.go b/pkg/eval/builtin_fn_str_test.go index b19778f5..53e6ee3d 100644 --- a/pkg/eval/builtin_fn_str_test.go +++ b/pkg/eval/builtin_fn_str_test.go @@ -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"), ) } diff --git a/pkg/eval/builtin_special_test.go b/pkg/eval/builtin_special_test.go index 8f998e26..a99ebbbf 100644 --- a/pkg/eval/builtin_special_test.go +++ b/pkg/eval/builtin_special_test.go @@ -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"), ) } diff --git a/pkg/eval/evaltest/matchers.go b/pkg/eval/evaltest/matchers.go index 0d1f94f9..5365e9f5 100644 --- a/pkg/eval/evaltest/matchers.go +++ b/pkg/eval/evaltest/matchers.go @@ -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 +} diff --git a/pkg/eval/mods/re/re_test.go b/pkg/eval/mods/re/re_test.go index 03fa59af..cad12136 100644 --- a/pkg/eval/mods/re/re_test.go +++ b/pkg/eval/mods/re/re_test.go @@ -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(`\(\*\)`), ) diff --git a/pkg/eval/mods/str/str_test.go b/pkg/eval/mods/str/str_test.go index 62db1446..9dc85d86 100644 --- a/pkg/eval/mods/str/str_test.go +++ b/pkg/eval/mods/str/str_test.go @@ -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"),