2020-09-03 13:48:39 +08:00
|
|
|
package eval_test
|
2017-12-17 13:20:03 +08:00
|
|
|
|
2020-05-05 07:43:50 +08:00
|
|
|
import (
|
2023-05-07 06:22:20 +08:00
|
|
|
"fmt"
|
2020-05-05 07:43:50 +08:00
|
|
|
"testing"
|
|
|
|
|
2021-01-27 09:28:38 +08:00
|
|
|
. "src.elv.sh/pkg/eval"
|
2023-05-07 06:22:20 +08:00
|
|
|
"src.elv.sh/pkg/testutil"
|
2020-09-03 13:48:39 +08:00
|
|
|
|
2022-01-05 08:12:35 +08:00
|
|
|
"src.elv.sh/pkg/eval/errs"
|
2021-01-27 09:28:38 +08:00
|
|
|
. "src.elv.sh/pkg/eval/evaltest"
|
|
|
|
"src.elv.sh/pkg/eval/vals"
|
2020-05-05 07:43:50 +08:00
|
|
|
)
|
2017-12-22 04:49:14 +08:00
|
|
|
|
2021-01-10 22:48:21 +08:00
|
|
|
func TestRunParallel(t *testing.T) {
|
2018-05-22 08:08:11 +08:00
|
|
|
Test(t,
|
2018-07-06 08:32:42 +08:00
|
|
|
That(`run-parallel { put lorem } { echo ipsum }`).
|
|
|
|
Puts("lorem").Prints("ipsum\n"),
|
2021-10-03 08:09:08 +08:00
|
|
|
That(`run-parallel { } { fail foo }`).Throws(FailError{"foo"}),
|
2021-01-10 22:48:21 +08:00
|
|
|
)
|
|
|
|
}
|
2017-12-17 13:20:03 +08:00
|
|
|
|
2021-01-10 22:48:21 +08:00
|
|
|
func TestEach(t *testing.T) {
|
|
|
|
Test(t,
|
2018-03-03 14:08:09 +08:00
|
|
|
That(`put 1 233 | each $put~`).Puts("1", "233"),
|
|
|
|
That(`echo "1\n233" | each $put~`).Puts("1", "233"),
|
2020-05-04 06:25:25 +08:00
|
|
|
That(`echo "1\r\n233" | each $put~`).Puts("1", "233"),
|
2018-03-03 14:08:09 +08:00
|
|
|
That(`each $put~ [1 233]`).Puts("1", "233"),
|
2021-11-28 11:24:42 +08:00
|
|
|
That(`range 10 | each {|x| if (== $x 4) { break }; put $x }`).
|
2021-04-04 20:37:38 +08:00
|
|
|
Puts(0, 1, 2, 3),
|
2021-11-28 11:24:42 +08:00
|
|
|
That(`range 10 | each {|x| if (== $x 4) { continue }; put $x }`).
|
2021-10-03 08:09:08 +08:00
|
|
|
Puts(0, 1, 2, 3, 5, 6, 7, 8, 9),
|
2021-11-28 11:24:42 +08:00
|
|
|
That(`range 10 | each {|x| if (== $x 4) { fail haha }; put $x }`).
|
2021-07-15 10:31:01 +08:00
|
|
|
Puts(0, 1, 2, 3).Throws(FailError{"haha"}),
|
2019-12-20 17:50:26 +08:00
|
|
|
// TODO(xiaq): Test that "each" does not close the stdin.
|
2021-01-10 22:48:21 +08:00
|
|
|
)
|
|
|
|
}
|
2017-12-17 13:20:03 +08:00
|
|
|
|
2021-05-17 11:12:12 +08:00
|
|
|
func TestPeach(t *testing.T) {
|
2023-05-07 06:22:20 +08:00
|
|
|
testTimeScale := fmt.Sprint(testutil.TestTimeScale())
|
|
|
|
// Testing the `peach` builtin is a challenge since, by definition, the
|
|
|
|
// order of execution is undefined.
|
2021-05-17 11:12:12 +08:00
|
|
|
Test(t,
|
|
|
|
// Verify the output has the expected values when sorted.
|
2021-11-28 11:24:42 +08:00
|
|
|
That(`range 5 | peach {|x| * 2 $x } | order`).Puts(0, 2, 4, 6, 8),
|
2021-10-03 08:09:08 +08:00
|
|
|
|
|
|
|
// Handling of "continue".
|
2021-11-28 11:24:42 +08:00
|
|
|
That(`range 5 | peach {|x| if (== $x 2) { continue }; * 2 $x } | order`).
|
2021-10-03 08:09:08 +08:00
|
|
|
Puts(0, 2, 6, 8),
|
|
|
|
|
2021-06-28 07:38:43 +08:00
|
|
|
// Test that the order of output does not necessarily match the order of
|
|
|
|
// input.
|
|
|
|
//
|
|
|
|
// Most of the time this effect can be observed without the need of any
|
|
|
|
// jitter, but if the system only has one CPU core to execute goroutines
|
|
|
|
// (which can happen even when GOMAXPROCS > 1), the scheduling of
|
|
|
|
// goroutines can become deterministic. The random jitter fixes that by
|
|
|
|
// forcing goroutines to yield the thread and allow other goroutines to
|
|
|
|
// execute.
|
2021-05-17 11:12:12 +08:00
|
|
|
That(`
|
2021-06-28 07:38:43 +08:00
|
|
|
var @in = (range 100)
|
|
|
|
while $true {
|
2021-11-28 11:24:42 +08:00
|
|
|
var @out = (all $in | peach {|x| sleep (* (rand) 0.01); put $x })
|
2021-06-28 07:38:43 +08:00
|
|
|
if (not-eq $in $out) {
|
2021-05-17 11:12:12 +08:00
|
|
|
put $true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`).Puts(true),
|
|
|
|
// Verify that exceptions are propagated.
|
2021-11-28 11:24:42 +08:00
|
|
|
That(`peach {|x| fail $x } [a]`).
|
|
|
|
Throws(FailError{"a"}, "fail $x ", "peach {|x| fail $x } [a]"),
|
2021-05-17 11:12:12 +08:00
|
|
|
// Verify that `break` works by terminating the `peach` before the entire sequence is
|
|
|
|
// consumed.
|
|
|
|
That(`
|
|
|
|
var tot = 0
|
|
|
|
range 1 101 |
|
2021-11-28 11:24:42 +08:00
|
|
|
peach {|x| if (== 50 $x) { break } else { put $x } } |
|
2021-06-01 05:13:24 +08:00
|
|
|
< (+ (all)) (+ (range 1 101))
|
|
|
|
`).Puts(true),
|
2023-04-15 10:57:42 +08:00
|
|
|
|
2023-05-07 06:22:20 +08:00
|
|
|
// Test the parallelism of peach by observing its run time relative to
|
|
|
|
// the run time of the function. Since the exact run time is subject to
|
|
|
|
// scheduling differences, benchmark it multiple times and use the
|
|
|
|
// fastest run time.
|
|
|
|
|
|
|
|
// Unlimited workers: when scheduling allows, no two function runs are
|
|
|
|
// serial. Best run time is between t and 2t, regardless of input size.
|
|
|
|
That(`
|
2023-05-20 18:55:31 +08:00
|
|
|
var t = (* 0.005 `+testTimeScale+`)
|
2023-05-07 06:22:20 +08:00
|
|
|
var best-run = (benchmark &min-runs=5 &min-time=0 {
|
|
|
|
range 6 | peach {|_| sleep $t }
|
|
|
|
} &on-end={|metrics| put $metrics[min] })
|
2023-05-09 06:34:04 +08:00
|
|
|
put $best-run
|
2023-05-07 06:22:20 +08:00
|
|
|
< $t $best-run (* 2 $t)`).
|
2023-05-09 06:34:04 +08:00
|
|
|
Puts(Anything, true),
|
2023-05-07 06:22:20 +08:00
|
|
|
// 2 workers:
|
|
|
|
//
|
|
|
|
// - When scheduling allows, at least two function runs are parallel.
|
|
|
|
//
|
|
|
|
// - No more than two functions are parallel.
|
|
|
|
//
|
|
|
|
// Best run time is between (ceil(n/2) * t) and n*t, where n is the
|
|
|
|
// input size.
|
|
|
|
That(`
|
2023-05-20 18:55:31 +08:00
|
|
|
var t = (* 0.005 `+testTimeScale+`)
|
2023-05-07 06:22:20 +08:00
|
|
|
var best-run = (benchmark &min-runs=5 &min-time=0 {
|
|
|
|
range 6 | peach &num-workers=2 {|_| sleep $t }
|
|
|
|
} &on-end={|metrics| put $metrics[min] })
|
2023-05-09 06:34:04 +08:00
|
|
|
put $best-run
|
2023-05-07 06:22:20 +08:00
|
|
|
< (* 3 $t) $best-run (* 6 $t)`).
|
2023-05-09 06:34:04 +08:00
|
|
|
Puts(Anything, true),
|
2023-05-07 06:22:20 +08:00
|
|
|
|
|
|
|
// Invalid options are handled.
|
|
|
|
That(`peach &num-workers=0 {|x| * 2 $x }`).Throws(errs.BadValue{
|
|
|
|
What: "peach &num-workers",
|
|
|
|
Valid: "exact positive integer or +inf",
|
|
|
|
Actual: "0",
|
|
|
|
}),
|
|
|
|
That(`peach &num-workers=-2 {|x| * 2 $x }`).Throws(errs.BadValue{
|
|
|
|
What: "peach &num-workers",
|
|
|
|
Valid: "exact positive integer or +inf",
|
|
|
|
Actual: "-2",
|
|
|
|
}),
|
|
|
|
)
|
2021-05-17 11:12:12 +08:00
|
|
|
}
|
2021-01-10 22:48:21 +08:00
|
|
|
|
|
|
|
func TestFail(t *testing.T) {
|
|
|
|
Test(t,
|
2020-05-31 20:56:46 +08:00
|
|
|
That("fail haha").Throws(FailError{"haha"}, "fail haha"),
|
2020-05-05 07:43:50 +08:00
|
|
|
That("fn f { fail haha }", "fail ?(f)").Throws(
|
2020-05-31 20:56:46 +08:00
|
|
|
FailError{"haha"}, "fail haha ", "f"),
|
2020-05-05 07:43:50 +08:00
|
|
|
That("fail []").Throws(
|
2020-05-31 20:56:46 +08:00
|
|
|
FailError{vals.EmptyList}, "fail []"),
|
2020-07-01 05:26:01 +08:00
|
|
|
That("put ?(fail 1)[reason][type]").Puts("fail"),
|
|
|
|
That("put ?(fail 1)[reason][content]").Puts("1"),
|
2021-01-10 22:48:21 +08:00
|
|
|
)
|
|
|
|
}
|
2020-05-05 07:43:50 +08:00
|
|
|
|
2021-01-10 22:48:21 +08:00
|
|
|
func TestReturn(t *testing.T) {
|
|
|
|
Test(t,
|
|
|
|
That("return").Throws(Return),
|
|
|
|
// Use of return inside fn is tested in TestFn
|
2018-05-22 08:08:11 +08:00
|
|
|
)
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
2022-01-05 08:12:35 +08:00
|
|
|
|
|
|
|
func TestDefer(t *testing.T) {
|
|
|
|
Test(t,
|
|
|
|
That("{ defer { put a }; put b }").Puts("b", "a"),
|
|
|
|
That("defer { }").
|
|
|
|
Throws(ErrorWithMessage("defer must be called from within a closure")),
|
|
|
|
That("{ defer { fail foo } }").
|
|
|
|
Throws(FailError{"foo"}, "fail foo ", "{ defer { fail foo } }"),
|
|
|
|
That("{ defer {|x| } }").Throws(
|
|
|
|
errs.ArityMismatch{What: "arguments",
|
|
|
|
ValidLow: 1, ValidHigh: 1, Actual: 0},
|
|
|
|
"defer {|x| } ", "{ defer {|x| } }"),
|
|
|
|
)
|
|
|
|
}
|