aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/rangefunc/rangefunc_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/compile/internal/rangefunc/rangefunc_test.go')
-rw-r--r--src/cmd/compile/internal/rangefunc/rangefunc_test.go890
1 files changed, 847 insertions, 43 deletions
diff --git a/src/cmd/compile/internal/rangefunc/rangefunc_test.go b/src/cmd/compile/internal/rangefunc/rangefunc_test.go
index 16856c648c..c50059fe18 100644
--- a/src/cmd/compile/internal/rangefunc/rangefunc_test.go
+++ b/src/cmd/compile/internal/rangefunc/rangefunc_test.go
@@ -2,18 +2,19 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build goexperiment.rangefunc
-
package rangefunc_test
import (
+ "fmt"
+ "regexp"
"slices"
"testing"
)
+type Seq[T any] func(yield func(T) bool)
type Seq2[T1, T2 any] func(yield func(T1, T2) bool)
-// OfSliceIndex returns a Seq over the elements of s. It is equivalent
+// OfSliceIndex returns a Seq2 over the elements of s. It is equivalent
// to range s.
func OfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] {
return func(yield func(int, T) bool) {
@@ -54,6 +55,39 @@ func VeryBadOfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] {
}
}
+// SwallowPanicOfSliceIndex hides panics and converts them to normal return
+func SwallowPanicOfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] {
+ return func(yield func(int, T) bool) {
+ for i, v := range s {
+ done := false
+ func() {
+ defer func() {
+ if r := recover(); r != nil {
+ done = true
+ }
+ }()
+ done = !yield(i, v)
+ }()
+ if done {
+ return
+ }
+ }
+ return
+ }
+}
+
+// PanickyOfSliceIndex iterates the slice but panics if it exits the loop early
+func PanickyOfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] {
+ return func(yield func(int, T) bool) {
+ for i, v := range s {
+ if !yield(i, v) {
+ panic(fmt.Errorf("Panicky iterator panicking"))
+ }
+ }
+ return
+ }
+}
+
// CooperativeBadOfSliceIndex calls the loop body from a goroutine after
// a ping on a channel, and returns recover()on that same channel.
func CooperativeBadOfSliceIndex[T any, S ~[]T](s S, proceed chan any) Seq2[int, T] {
@@ -82,6 +116,22 @@ type TrickyIterator struct {
yield func(int, int) bool
}
+func (ti *TrickyIterator) iterEcho(s []int) Seq2[int, int] {
+ return func(yield func(int, int) bool) {
+ for i, v := range s {
+ if !yield(i, v) {
+ ti.yield = yield
+ return
+ }
+ if ti.yield != nil && !ti.yield(i, v) {
+ return
+ }
+ }
+ ti.yield = yield
+ return
+ }
+}
+
func (ti *TrickyIterator) iterAll(s []int) Seq2[int, int] {
return func(yield func(int, int) bool) {
ti.yield = yield // Save yield for future abuse
@@ -118,36 +168,137 @@ func (ti *TrickyIterator) fail() {
}
}
-// Check wraps the function body passed to iterator forall
+const DONE = 0 // body of loop has exited in a non-panic way
+const READY = 1 // body of loop has not exited yet, is not running
+const PANIC = 2 // body of loop is either currently running, or has panicked
+const EXHAUSTED = 3 // iterator function return, i.e., sequence is "exhausted"
+
+const MISSING_PANIC = 4 // overload "READY" for panic call
+
+// Check2 wraps the function body passed to iterator forall
// in code that ensures that it cannot (successfully) be called
// either after body return false (control flow out of loop) or
// forall itself returns (the iteration is now done).
//
// Note that this can catch errors before the inserted checks.
-func Check[U, V any](forall Seq2[U, V]) Seq2[U, V] {
+func Check2[U, V any](forall Seq2[U, V]) Seq2[U, V] {
return func(body func(U, V) bool) {
- ret := true
+ state := READY
forall(func(u U, v V) bool {
- if !ret {
- panic("Checked iterator access after exit")
+ if state != READY {
+ panic(fail[state])
+ }
+ state = PANIC
+ ret := body(u, v)
+ if ret {
+ state = READY
+ } else {
+ state = DONE
}
- ret = body(u, v)
return ret
})
- ret = false
+ if state == PANIC {
+ panic(fail[MISSING_PANIC])
+ }
+ state = EXHAUSTED
+ }
+}
+
+func Check[U any](forall Seq[U]) Seq[U] {
+ return func(body func(U) bool) {
+ state := READY
+ forall(func(u U) bool {
+ if state != READY {
+ panic(fail[state])
+ }
+ state = PANIC
+ ret := body(u)
+ if ret {
+ state = READY
+ } else {
+ state = DONE
+ }
+ return ret
+ })
+ if state == PANIC {
+ panic(fail[MISSING_PANIC])
+ }
+ state = EXHAUSTED
}
}
+func matchError(r any, x string) bool {
+ if r == nil {
+ return false
+ }
+ if x == "" {
+ return true
+ }
+ if p, ok := r.(errorString); ok {
+ return p.Error() == x
+ }
+ if p, ok := r.(error); ok {
+ e, err := regexp.Compile(x)
+ if err != nil {
+ panic(fmt.Errorf("Bad regexp '%s' passed to matchError", x))
+ }
+ return e.MatchString(p.Error())
+ }
+ return false
+}
+
+func matchErrorHelper(t *testing.T, r any, x string) {
+ if matchError(r, x) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v', expected '%s'", r, x)
+ }
+}
+
+// An errorString represents a runtime error described by a single string.
+type errorString string
+
+func (e errorString) Error() string {
+ return string(e)
+}
+
+const (
+ // RERR_ is for runtime error, and may be regexps/substrings, to simplify use of tests with tools
+ RERR_DONE = "runtime error: range function continued iteration after loop body exit"
+ RERR_PANIC = "runtime error: range function continued iteration after loop body panic"
+ RERR_EXHAUSTED = "runtime error: range function continued iteration after whole loop exit"
+ RERR_MISSING = "runtime error: range function recovered a loop body panic and did not resume panicking"
+
+ // CERR_ is for checked errors in the Check combinator defined above, and should be literal strings
+ CERR_PFX = "checked rangefunc error: "
+ CERR_DONE = CERR_PFX + "loop iteration after body done"
+ CERR_PANIC = CERR_PFX + "loop iteration after panic"
+ CERR_EXHAUSTED = CERR_PFX + "loop iteration after iterator exit"
+ CERR_MISSING = CERR_PFX + "loop iterator swallowed panic"
+)
+
+var fail []error = []error{
+ errorString(CERR_DONE),
+ errorString(CERR_PFX + "loop iterator, unexpected error"),
+ errorString(CERR_PANIC),
+ errorString(CERR_EXHAUSTED),
+ errorString(CERR_MISSING),
+}
+
func TestCheck(t *testing.T) {
i := 0
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, CERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
}()
- for _, x := range Check(BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) {
+ for _, x := range Check2(BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) {
i += x
if i > 4*9 {
break
@@ -166,7 +317,11 @@ func TestCooperativeBadOfSliceIndex(t *testing.T) {
}
proceed <- true
if r := <-proceed; r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
@@ -177,10 +332,10 @@ func TestCooperativeBadOfSliceIndex(t *testing.T) {
}
}
-func TestCheckCooperativeBadOfSliceIndex(t *testing.T) {
+func TestCooperativeBadOfSliceIndexCheck(t *testing.T) {
i := 0
proceed := make(chan any)
- for _, x := range Check(CooperativeBadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, proceed)) {
+ for _, x := range Check2(CooperativeBadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, proceed)) {
i += x
if i >= 36 {
break
@@ -188,7 +343,12 @@ func TestCheckCooperativeBadOfSliceIndex(t *testing.T) {
}
proceed <- true
if r := <-proceed; r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, CERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+
} else {
t.Error("Wanted to see a failure")
}
@@ -217,7 +377,11 @@ func TestTrickyIterAll(t *testing.T) {
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
@@ -241,7 +405,11 @@ func TestTrickyIterOne(t *testing.T) {
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
@@ -265,7 +433,11 @@ func TestTrickyIterZero(t *testing.T) {
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
@@ -274,10 +446,10 @@ func TestTrickyIterZero(t *testing.T) {
trickItZero.fail()
}
-func TestCheckTrickyIterZero(t *testing.T) {
+func TestTrickyIterZeroCheck(t *testing.T) {
trickItZero := TrickyIterator{}
i := 0
- for _, x := range Check(trickItZero.iterZero([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) {
+ for _, x := range Check2(trickItZero.iterZero([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) {
i += x
if i >= 36 {
break
@@ -289,7 +461,11 @@ func TestCheckTrickyIterZero(t *testing.T) {
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, CERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
@@ -298,6 +474,78 @@ func TestCheckTrickyIterZero(t *testing.T) {
trickItZero.fail()
}
+func TestTrickyIterEcho(t *testing.T) {
+ trickItAll := TrickyIterator{}
+ i := 0
+ for _, x := range trickItAll.iterAll([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
+ t.Logf("first loop i=%d", i)
+ i += x
+ if i >= 10 {
+ break
+ }
+ }
+
+ if i != 10 {
+ t.Errorf("Expected i == 10, saw %d instead", i)
+ } else {
+ t.Logf("i = %d", i)
+ }
+
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Error("Wanted to see a failure")
+ }
+ }()
+
+ i = 0
+ for _, x := range trickItAll.iterEcho([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
+ t.Logf("second loop i=%d", i)
+ if x >= 5 {
+ break
+ }
+ }
+
+}
+
+func TestTrickyIterEcho2(t *testing.T) {
+ trickItAll := TrickyIterator{}
+ var i int
+
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Error("Wanted to see a failure")
+ }
+ }()
+
+ for k := range 2 {
+ i = 0
+ for _, x := range trickItAll.iterEcho([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
+ t.Logf("k,x,i=%d,%d,%d", k, x, i)
+ i += x
+ if i >= 10 {
+ break
+ }
+ }
+ t.Logf("i = %d", i)
+
+ if i != 10 {
+ t.Errorf("Expected i == 10, saw %d instead", i)
+ }
+ }
+}
+
// TestBreak1 should just work, with well-behaved iterators.
// (The misbehaving iterator detector should not trigger.)
func TestBreak1(t *testing.T) {
@@ -412,7 +660,11 @@ func TestBreak1BadA(t *testing.T) {
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
@@ -443,7 +695,11 @@ func TestBreak1BadB(t *testing.T) {
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
@@ -507,7 +763,11 @@ func TestMultiCont1(t *testing.T) {
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
@@ -551,7 +811,11 @@ func TestMultiCont2(t *testing.T) {
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
@@ -595,7 +859,11 @@ func TestMultiCont3(t *testing.T) {
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
@@ -639,7 +907,11 @@ func TestMultiBreak0(t *testing.T) {
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
@@ -683,7 +955,11 @@ func TestMultiBreak1(t *testing.T) {
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
@@ -727,7 +1003,11 @@ func TestMultiBreak2(t *testing.T) {
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
@@ -771,7 +1051,11 @@ func TestMultiBreak3(t *testing.T) {
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
@@ -808,6 +1092,229 @@ W:
}
}
+func TestPanickyIterator1(t *testing.T) {
+ var result []int
+ var expect = []int{1, 2, 3, 4}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, "Panicky iterator panicking") {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result was %v", result)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, z := range PanickyOfSliceIndex([]int{1, 2, 3, 4}) {
+ result = append(result, z)
+ if z == 4 {
+ break
+ }
+ }
+}
+
+func TestPanickyIterator1Check(t *testing.T) {
+ var result []int
+ var expect = []int{1, 2, 3, 4}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, "Panicky iterator panicking") {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result was %v", result)
+ }
+ }()
+ for _, z := range Check2(PanickyOfSliceIndex([]int{1, 2, 3, 4})) {
+ result = append(result, z)
+ if z == 4 {
+ break
+ }
+ }
+}
+
+func TestPanickyIterator2(t *testing.T) {
+ var result []int
+ var expect = []int{100, 10, 1, 2}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, RERR_MISSING) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result was %v", result)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, x := range OfSliceIndex([]int{100, 200}) {
+ result = append(result, x)
+ Y:
+ // swallows panics and iterates to end BUT `break Y` disables the body, so--> 10, 1, 2
+ for _, y := range VeryBadOfSliceIndex([]int{10, 20}) {
+ result = append(result, y)
+
+ // converts early exit into a panic --> 1, 2
+ for k, z := range PanickyOfSliceIndex([]int{1, 2}) { // iterator panics
+ result = append(result, z)
+ if k == 1 {
+ break Y
+ }
+ }
+ }
+ }
+}
+
+func TestPanickyIterator2Check(t *testing.T) {
+ var result []int
+ var expect = []int{100, 10, 1, 2}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, CERR_MISSING) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result was %v", result)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, x := range Check2(OfSliceIndex([]int{100, 200})) {
+ result = append(result, x)
+ Y:
+ // swallows panics and iterates to end BUT `break Y` disables the body, so--> 10, 1, 2
+ for _, y := range Check2(VeryBadOfSliceIndex([]int{10, 20})) {
+ result = append(result, y)
+
+ // converts early exit into a panic --> 1, 2
+ for k, z := range Check2(PanickyOfSliceIndex([]int{1, 2})) { // iterator panics
+ result = append(result, z)
+ if k == 1 {
+ break Y
+ }
+ }
+ }
+ }
+}
+
+func TestPanickyIterator3(t *testing.T) {
+ var result []int
+ var expect = []int{100, 10, 1, 2, 200, 10, 1, 2}
+ defer func() {
+ if r := recover(); r != nil {
+ t.Errorf("Unexpected panic '%v'", r)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, x := range OfSliceIndex([]int{100, 200}) {
+ result = append(result, x)
+ Y:
+ // swallows panics and iterates to end BUT `break Y` disables the body, so--> 10, 1, 2
+ // This is cross-checked against the checked iterator below; the combinator should behave the same.
+ for _, y := range VeryBadOfSliceIndex([]int{10, 20}) {
+ result = append(result, y)
+
+ for k, z := range OfSliceIndex([]int{1, 2}) { // iterator does not panic
+ result = append(result, z)
+ if k == 1 {
+ break Y
+ }
+ }
+ }
+ }
+}
+func TestPanickyIterator3Check(t *testing.T) {
+ var result []int
+ var expect = []int{100, 10, 1, 2, 200, 10, 1, 2}
+ defer func() {
+ if r := recover(); r != nil {
+ t.Errorf("Unexpected panic '%v'", r)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, x := range Check2(OfSliceIndex([]int{100, 200})) {
+ result = append(result, x)
+ Y:
+ // swallows panics and iterates to end BUT `break Y` disables the body, so--> 10, 1, 2
+ for _, y := range Check2(VeryBadOfSliceIndex([]int{10, 20})) {
+ result = append(result, y)
+
+ for k, z := range Check2(OfSliceIndex([]int{1, 2})) { // iterator does not panic
+ result = append(result, z)
+ if k == 1 {
+ break Y
+ }
+ }
+ }
+ }
+}
+
+func TestPanickyIterator4(t *testing.T) {
+ var result []int
+ var expect = []int{1, 2, 3}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, RERR_MISSING) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, x := range SwallowPanicOfSliceIndex([]int{1, 2, 3, 4}) {
+ result = append(result, x)
+ if x == 3 {
+ panic("x is 3")
+ }
+ }
+
+}
+func TestPanickyIterator4Check(t *testing.T) {
+ var result []int
+ var expect = []int{1, 2, 3}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, CERR_MISSING) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, x := range Check2(SwallowPanicOfSliceIndex([]int{1, 2, 3, 4})) {
+ result = append(result, x)
+ if x == 3 {
+ panic("x is 3")
+ }
+ }
+
+}
+
// veryBad tests that a loop nest behaves sensibly in the face of a
// "very bad" iterator. In this case, "sensibly" means that the
// break out of X still occurs after the very bad iterator finally
@@ -833,17 +1340,17 @@ X:
return result
}
-// checkVeryBad wraps a "very bad" iterator with Check,
+// veryBadCheck wraps a "very bad" iterator with Check,
// demonstrating that the very bad iterator also hides panics
// thrown by Check.
-func checkVeryBad(s []int) []int {
+func veryBadCheck(s []int) []int {
var result []int
X:
for _, x := range OfSliceIndex([]int{1, 2, 3}) {
result = append(result, x)
- for _, y := range Check(VeryBadOfSliceIndex(s)) {
+ for _, y := range Check2(VeryBadOfSliceIndex(s)) {
result = append(result, y)
break X
}
@@ -902,8 +1409,8 @@ func TestVeryBad2(t *testing.T) {
// TestCheckVeryBad checks the behavior of an extremely poorly behaved iterator,
// which also suppresses the exceptions from "Check"
-func TestCheckVeryBad(t *testing.T) {
- result := checkVeryBad([]int{10, 20, 30, 40}) // even length
+func TestVeryBadCheck(t *testing.T) {
+ result := veryBadCheck([]int{10, 20, 30, 40}) // even length
expect := []int{1, 10}
if !slices.Equal(expect, result) {
@@ -929,7 +1436,11 @@ func testBreak1BadDefer(t *testing.T) (result []int) {
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("(Inner) Expected %v, got %v", expect, result)
}
@@ -1036,11 +1547,40 @@ func testReturn3(t *testing.T) (result []int, err any) {
return
}
+// testReturn4 has no bad iterators, but exercises return variable rewriting
+// differs from testReturn1 because deferred append to "result" does not change
+// the return value in this case.
+func testReturn4(t *testing.T) (_ []int, _ []int, err any) {
+ var result []int
+ defer func() {
+ err = recover()
+ }()
+ for _, x := range OfSliceIndex([]int{-1, -2, -3, -4, -5}) {
+ result = append(result, x)
+ if x == -4 {
+ break
+ }
+ defer func() {
+ result = append(result, x*10)
+ }()
+ for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
+ if y == 3 {
+ return result, result, nil
+ }
+ result = append(result, y)
+ }
+ result = append(result, x)
+ }
+ return
+}
+
// TestReturns checks that returns through bad iterators behave properly,
// for inner and outer bad iterators.
func TestReturns(t *testing.T) {
var result []int
+ var result2 []int
var expect = []int{-1, 1, 2, -10}
+ var expect2 = []int{-1, 1, 2}
var err any
result, err = testReturn1(t)
@@ -1058,7 +1598,11 @@ func TestReturns(t *testing.T) {
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ if matchError(err, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", err)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", err)
+ }
}
result, err = testReturn3(t)
@@ -1068,9 +1612,23 @@ func TestReturns(t *testing.T) {
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ if matchError(err, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", err)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", err)
+ }
}
+ result, result2, err = testReturn4(t)
+ if !slices.Equal(expect2, result) {
+ t.Errorf("Expected %v, got %v", expect2, result)
+ }
+ if !slices.Equal(expect2, result2) {
+ t.Errorf("Expected %v, got %v", expect2, result2)
+ }
+ if err != nil {
+ t.Errorf("Unexpected error %v", err)
+ }
}
// testGotoA1 tests loop-nest-internal goto, no bad iterators.
@@ -1169,7 +1727,11 @@ func TestGotoA(t *testing.T) {
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ if matchError(err, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", err)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", err)
+ }
}
result, err = testGotoA3(t)
@@ -1179,7 +1741,11 @@ func TestGotoA(t *testing.T) {
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ if matchError(err, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", err)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", err)
+ }
}
}
@@ -1282,7 +1848,11 @@ func TestGotoB(t *testing.T) {
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ if matchError(err, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", err)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", err)
+ }
}
result, err = testGotoB3(t)
@@ -1292,6 +1862,240 @@ func TestGotoB(t *testing.T) {
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ matchErrorHelper(t, err, RERR_DONE)
+ }
+}
+
+// once returns an iterator that runs its loop body once with the supplied value
+func once[T any](x T) Seq[T] {
+ return func(yield func(T) bool) {
+ yield(x)
+ }
+}
+
+// terrify converts an iterator into one that panics with the supplied string
+// if/when the loop body terminates early (returns false, for break, goto, outer
+// continue, or return).
+func terrify[T any](s string, forall Seq[T]) Seq[T] {
+ return func(yield func(T) bool) {
+ forall(func(v T) bool {
+ if !yield(v) {
+ panic(s)
+ }
+ return true
+ })
+ }
+}
+
+func use[T any](T) {
+}
+
+// f runs a not-rangefunc iterator that recovers from a panic that follows execution of a return.
+// what does f return?
+func f() string {
+ defer func() { recover() }()
+ defer panic("f panic")
+ for _, s := range []string{"f return"} {
+ return s
+ }
+ return "f not reached"
+}
+
+// g runs a rangefunc iterator that recovers from a panic that follows execution of a return.
+// what does g return?
+func g() string {
+ defer func() { recover() }()
+ for s := range terrify("g panic", once("g return")) {
+ return s
+ }
+ return "g not reached"
+}
+
+// h runs a rangefunc iterator that recovers from a panic that follows execution of a return.
+// the panic occurs in the rangefunc iterator itself.
+// what does h return?
+func h() (hashS string) {
+ defer func() { recover() }()
+ for s := range terrify("h panic", once("h return")) {
+ hashS := s
+ use(hashS)
+ return s
+ }
+ return "h not reached"
+}
+
+func j() (hashS string) {
+ defer func() { recover() }()
+ for s := range terrify("j panic", once("j return")) {
+ hashS = s
+ return
+ }
+ return "j not reached"
+}
+
+// k runs a rangefunc iterator that recovers from a panic that follows execution of a return.
+// the panic occurs in the rangefunc iterator itself.
+// k includes an additional mechanism to for making the return happen
+// what does k return?
+func k() (hashS string) {
+ _return := func(s string) { hashS = s }
+
+ defer func() { recover() }()
+ for s := range terrify("k panic", once("k return")) {
+ _return(s)
+ return
+ }
+ return "k not reached"
+}
+
+func m() (hashS string) {
+ _return := func(s string) { hashS = s }
+
+ defer func() { recover() }()
+ for s := range terrify("m panic", once("m return")) {
+ defer _return(s)
+ return s + ", but should be replaced in a defer"
+ }
+ return "m not reached"
+}
+
+func n() string {
+ defer func() { recover() }()
+ for s := range terrify("n panic", once("n return")) {
+ return s + func(s string) string {
+ defer func() { recover() }()
+ for s := range terrify("n closure panic", once(s)) {
+ return s
+ }
+ return "n closure not reached"
+ }(" and n closure return")
+ }
+ return "n not reached"
+}
+
+type terrifyTestCase struct {
+ f func() string
+ e string
+}
+
+func TestPanicReturns(t *testing.T) {
+ tcs := []terrifyTestCase{
+ {f, "f return"},
+ {g, "g return"},
+ {h, "h return"},
+ {k, "k return"},
+ {j, "j return"},
+ {m, "m return"},
+ {n, "n return and n closure return"},
+ }
+
+ for _, tc := range tcs {
+ got := tc.f()
+ if got != tc.e {
+ t.Errorf("Got %s expected %s", got, tc.e)
+ } else {
+ t.Logf("Got expected %s", got)
+ }
+ }
+}
+
+// twice calls yield twice, the first time defer-recover-saving any panic,
+// for re-panicking later if the second call to yield does not also panic.
+// If the first call panicked, the second call ought to also panic because
+// it was called after a panic-termination of the loop body.
+func twice[T any](x, y T) Seq[T] {
+ return func(yield func(T) bool) {
+ var p any
+ done := false
+ func() {
+ defer func() {
+ p = recover()
+ }()
+ done = !yield(x)
+ }()
+ if done {
+ return
+ }
+ yield(y)
+ if p != nil {
+ // do not swallow the panic
+ panic(p)
+ }
+ }
+}
+
+func TestRunBodyAfterPanic(t *testing.T) {
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, RERR_PANIC) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result")
+ }
+ }()
+ for x := range twice(0, 1) {
+ if x == 0 {
+ panic("x is zero")
+ }
+ }
+}
+
+func TestRunBodyAfterPanicCheck(t *testing.T) {
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, CERR_PANIC) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result")
+ }
+ }()
+ for x := range Check(twice(0, 1)) {
+ if x == 0 {
+ panic("x is zero")
+ }
+ }
+}
+
+func TestTwoLevelReturn(t *testing.T) {
+ f := func() int {
+ for a := range twice(0, 1) {
+ for b := range twice(0, 2) {
+ x := a + b
+ t.Logf("x=%d", x)
+ if x == 3 {
+ return x
+ }
+ }
+ }
+ return -1
+ }
+ y := f()
+ if y != 3 {
+ t.Errorf("Expected y=3, got y=%d\n", y)
+ }
+}
+
+func TestTwoLevelReturnCheck(t *testing.T) {
+ f := func() int {
+ for a := range Check(twice(0, 1)) {
+ for b := range Check(twice(0, 2)) {
+ x := a + b
+ t.Logf("a=%d, b=%d, x=%d", a, b, x)
+ if x == 3 {
+ return x
+ }
+ }
+ }
+ return -1
+ }
+ y := f()
+ if y != 3 {
+ t.Errorf("Expected y=3, got y=%d\n", y)
}
}