Merge pull request #1310 from krader1961/peach-unit-test

Add test coverage of `peach`
This commit is contained in:
Qi Xiao 2021-05-31 21:49:24 +01:00 committed by GitHub
commit 45fe3ac2c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 10 deletions

View File

@ -2,6 +2,7 @@ package eval
import (
"sync"
"sync/atomic"
"src.elv.sh/pkg/diag"
"src.elv.sh/pkg/eval/vals"
@ -136,10 +137,15 @@ func each(fm *Frame, f Callable, inputs Inputs) error {
//elvdoc:fn peach
//
// ```elvish
// peach $f $input-list?
// peach $f~ $input-list?
// ```
//
// Call `$f` on all inputs, possibly in parallel.
// Call `$f~` on all inputs, possibly in parallel. The exception from a
// [`break`](./builtin.html#break) command it will cause `peach` to stop starting new instances of
// `$f` with any remaining inputs. Because each instance of `$f~` is being run in parallel it is not
// predictable when the early termination will occur or even that it will happen before all the
// input has been consumed. The exception from a [`continue`](./builtin.html#continue) command is
// ignored.
//
// Example (your output will differ):
//
@ -151,6 +157,12 @@ func each(fm *Frame, f Callable, inputs Inputs) error {
// ▶ 16
// ▶ 15
// ▶ 14
// ~> var tot = 0
// ~> range 1 101 |
// peach [x]{ if (== 50 $x) { break } else { put $x } } |
// each [x]{ set tot = (+ $tot $x) }
// ~> put $tot # without the break the total should be (num 5050)
// ▶ (num 1603)
// ```
//
// This command is intended for homogeneous processing of possibly unbound data. If
@ -161,10 +173,11 @@ func each(fm *Frame, f Callable, inputs Inputs) error {
func peach(fm *Frame, f Callable, inputs Inputs) error {
var wg sync.WaitGroup
broken := false
var broken atomic.Value
broken.Store(false)
var err error
inputs(func(v interface{}) {
if broken || err != nil {
if broken.Load().(bool) || err != nil {
return
}
wg.Add(1)
@ -179,9 +192,9 @@ func peach(fm *Frame, f Callable, inputs Inputs) error {
case nil, Continue:
// nop
case Break:
broken = true
broken.Store(true)
default:
broken = true
broken.Store(true)
err = diag.Errors(err, ex)
}
}

View File

@ -30,7 +30,42 @@ func TestEach(t *testing.T) {
)
}
// TODO: test peach
func TestPeach(t *testing.T) {
// Testing the `peach` builtin is a challenge since, by definition, the order of execution is
// undefined.
Test(t,
// Verify the output has the expected values when sorted.
That(`range 5 | peach [x]{ + 1 $x } | to-lines | order`).
Puts("1", "2", "3", "4", "5"),
// Verify that successive runs produce the output in different order. This test can
// theoretically suffer false positives but the vast majority of the time this will produce
// the expected output in the first iteration. The probability it will produce the same
// order of output in 100 iterations is effectively zero.
That(`
var cpu-count = 99
var x = [(range $cpu-count | peach [x]{ + 1 $x })]
for r [(range 100)] {
var y = [(range $cpu-count | peach [x]{ sleep 1us; + 1 $x })]
if (not-eq $x $y) {
put $true
break
}
}
`).Puts(true),
// Verify that exceptions are propagated.
That(`peach [x]{ fail $x } [a]`).
Throws(FailError{"a"}, "fail $x ", "peach [x]{ fail $x } [a]"),
// Verify that `break` works by terminating the `peach` before the entire sequence is
// consumed.
That(`
var tot = 0
range 1 101 |
peach [x]{ if (== 50 $x) { break } else { put $x } } |
each [x]{ set tot = (+ $tot $x) }
if (< $tot (/ (* 100 101) 2)) { put okay }
`).Puts("okay"),
)
}
func TestFail(t *testing.T) {
Test(t,

View File

@ -2090,9 +2090,9 @@ If an external command exits with a non-zero status, Elvish treats that as an
exception.
Flow commands -- `break`, `continue` and `return` -- are ordinary builtin
commands that raise special "flow control" exceptions. The `for` and `while`
commands capture `break` and `continue`, while `fn` modifies its closure to
capture `return`.
commands that raise special "flow control" exceptions. The `for`, `while`, and
`peach` commands capture `break` and `continue`, while `fn` modifies its closure
to capture `return`.
One interesting implication is that since flow commands are just ordinary
commands you can build functions on top of them. For instance, this function