| // Copyright 2023 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| 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 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) { | |
| for i, v := range s { | |
| if !yield(i, v) { | |
| return | |
| } | |
| } | |
| return | |
| } | |
| } | |
| // BadOfSliceIndex is "bad" because it ignores the return value from yield | |
| // and just keeps on iterating. | |
| func BadOfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] { | |
| return func(yield func(int, T) bool) { | |
| for i, v := range s { | |
| yield(i, v) | |
| } | |
| return | |
| } | |
| } | |
| // VeryBadOfSliceIndex is "very bad" because it ignores the return value from yield | |
| // and just keeps on iterating, and also wraps that call in a defer-recover so it can | |
| // keep on trying after the first panic. | |
| func VeryBadOfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] { | |
| return func(yield func(int, T) bool) { | |
| for i, v := range s { | |
| func() { | |
| defer func() { | |
| recover() | |
| }() | |
| yield(i, v) | |
| }() | |
| } | |
| return | |
| } | |
| } | |
| // 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] { | |
| return func(yield func(int, T) bool) { | |
| for i, v := range s { | |
| if !yield(i, v) { | |
| // if the body breaks, call yield just once in a goroutine | |
| go func() { | |
| <-proceed | |
| defer func() { | |
| proceed <- recover() | |
| }() | |
| yield(0, s[0]) | |
| }() | |
| return | |
| } | |
| } | |
| return | |
| } | |
| } | |
| // TrickyIterator is a type intended to test whether an iterator that | |
| // calls a yield function after loop exit must inevitably escape the | |
| // closure; this might be relevant to future checking/optimization. | |
| 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 | |
| for i, v := range s { | |
| if !yield(i, v) { | |
| return | |
| } | |
| } | |
| return | |
| } | |
| } | |
| func (ti *TrickyIterator) iterOne(s []int) Seq2[int, int] { | |
| return func(yield func(int, int) bool) { | |
| ti.yield = yield // Save yield for future abuse | |
| if len(s) > 0 { // Not in a loop might escape differently | |
| yield(0, s[0]) | |
| } | |
| return | |
| } | |
| } | |
| func (ti *TrickyIterator) iterZero(s []int) Seq2[int, int] { | |
| return func(yield func(int, int) bool) { | |
| ti.yield = yield // Save yield for future abuse | |
| // Don't call it at all, maybe it won't escape | |
| return | |
| } | |
| } | |
| func (ti *TrickyIterator) fail() { | |
| if ti.yield != nil { | |
| ti.yield(1, 1) | |
| } | |
| } | |
| 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 Check2[U, V any](forall Seq2[U, V]) Seq2[U, V] { | |
| return func(body func(U, V) bool) { | |
| state := READY | |
| forall(func(u U, v V) bool { | |
| tmp := state | |
| state = PANIC | |
| if tmp != READY { | |
| panic(fail[tmp]) | |
| } | |
| ret := body(u, v) | |
| if ret { | |
| state = READY | |
| } else { | |
| state = DONE | |
| } | |
| return ret | |
| }) | |
| 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 { | |
| tmp := state | |
| state = PANIC | |
| if tmp != READY { | |
| panic(fail[tmp]) | |
| } | |
| 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 function for loop body returned false" | |
| 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), | |
| } | |
| // TestNoVars ensures that versions of rangefunc that use zero or one | |
| // iteration variable (instead of two) run the proper number of times | |
| // and in the one variable case supply the proper values. | |
| // For #65236. | |
| func TestNoVars(t *testing.T) { | |
| i, k := 0, 0 | |
| for range Check2(OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) { | |
| i++ | |
| } | |
| for j := range Check2(OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) { | |
| k += j | |
| } | |
| if i != 10 { | |
| t.Errorf("Expected 10, got %d", i) | |
| } | |
| if k != 45 { | |
| t.Errorf("Expected 45, got %d", k) | |
| } | |
| } | |
| func TestCheck(t *testing.T) { | |
| i := 0 | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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 Check2(BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) { | |
| i += x | |
| if i > 4*9 { | |
| break | |
| } | |
| } | |
| } | |
| func TestCooperativeBadOfSliceIndex(t *testing.T) { | |
| i := 0 | |
| proceed := make(chan any) | |
| for _, x := range CooperativeBadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, proceed) { | |
| i += x | |
| if i >= 36 { | |
| break | |
| } | |
| } | |
| proceed <- true | |
| if r := <-proceed; 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") | |
| } | |
| if i != 36 { | |
| t.Errorf("Expected i == 36, saw %d instead", i) | |
| } else { | |
| t.Logf("i = %d", i) | |
| } | |
| } | |
| func TestCooperativeBadOfSliceIndexCheck(t *testing.T) { | |
| i := 0 | |
| proceed := make(chan any) | |
| for _, x := range Check2(CooperativeBadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, proceed)) { | |
| i += x | |
| if i >= 36 { | |
| break | |
| } | |
| } | |
| proceed <- true | |
| if r := <-proceed; r != nil { | |
| 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") | |
| } | |
| if i != 36 { | |
| t.Errorf("Expected i == 36, saw %d instead", i) | |
| } else { | |
| t.Logf("i = %d", i) | |
| } | |
| } | |
| func TestTrickyIterAll(t *testing.T) { | |
| trickItAll := TrickyIterator{} | |
| i := 0 | |
| for _, x := range trickItAll.iterAll([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| i += x | |
| if i >= 36 { | |
| break | |
| } | |
| } | |
| if i != 36 { | |
| t.Errorf("Expected i == 36, 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") | |
| } | |
| }() | |
| trickItAll.fail() | |
| } | |
| func TestTrickyIterOne(t *testing.T) { | |
| trickItOne := TrickyIterator{} | |
| i := 0 | |
| for _, x := range trickItOne.iterOne([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| i += x | |
| if i >= 36 { | |
| break | |
| } | |
| } | |
| // Don't care about value, ought to be 36 anyhow. | |
| 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") | |
| } | |
| }() | |
| trickItOne.fail() | |
| } | |
| func TestTrickyIterZero(t *testing.T) { | |
| trickItZero := TrickyIterator{} | |
| i := 0 | |
| for _, x := range trickItZero.iterZero([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| i += x | |
| if i >= 36 { | |
| break | |
| } | |
| } | |
| // Don't care about value, ought to be 0 anyhow. | |
| 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") | |
| } | |
| }() | |
| trickItZero.fail() | |
| } | |
| func TestTrickyIterZeroCheck(t *testing.T) { | |
| trickItZero := TrickyIterator{} | |
| i := 0 | |
| for _, x := range Check2(trickItZero.iterZero([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) { | |
| i += x | |
| if i >= 36 { | |
| break | |
| } | |
| } | |
| // Don't care about value, ought to be 0 anyhow. | |
| t.Logf("i = %d", i) | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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") | |
| } | |
| }() | |
| 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) { | |
| var result []int | |
| var expect = []int{1, 2, -1, 1, 2, -2, 1, 2, -3} | |
| for _, x := range OfSliceIndex([]int{-1, -2, -3, -4}) { | |
| if x == -4 { | |
| break | |
| } | |
| for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if y == 3 { | |
| break | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestBreak2 should just work, with well-behaved iterators. | |
| // (The misbehaving iterator detector should not trigger.) | |
| func TestBreak2(t *testing.T) { | |
| var result []int | |
| var expect = []int{1, 2, -1, 1, 2, -2, 1, 2, -3} | |
| outer: | |
| for _, x := range OfSliceIndex([]int{-1, -2, -3, -4}) { | |
| for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if y == 3 { | |
| break | |
| } | |
| if x == -4 { | |
| break outer | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestContinue should just work, with well-behaved iterators. | |
| // (The misbehaving iterator detector should not trigger.) | |
| func TestContinue(t *testing.T) { | |
| var result []int | |
| var expect = []int{-1, 1, 2, -2, 1, 2, -3, 1, 2, -4} | |
| outer: | |
| for _, x := range OfSliceIndex([]int{-1, -2, -3, -4}) { | |
| result = append(result, x) | |
| for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if y == 3 { | |
| continue outer | |
| } | |
| if x == -4 { | |
| break outer | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x-10) | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestBreak3 should just work, with well-behaved iterators. | |
| // (The misbehaving iterator detector should not trigger.) | |
| func TestBreak3(t *testing.T) { | |
| var result []int | |
| var expect = []int{100, 10, 2, 4, 200, 10, 2, 4, 20, 2, 4, 300, 10, 2, 4, 20, 2, 4, 30} | |
| X: | |
| for _, x := range OfSliceIndex([]int{100, 200, 300, 400}) { | |
| Y: | |
| for _, y := range OfSliceIndex([]int{10, 20, 30, 40}) { | |
| if 10*y >= x { | |
| break | |
| } | |
| result = append(result, y) | |
| if y == 30 { | |
| continue X | |
| } | |
| Z: | |
| for _, z := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if z&1 == 1 { | |
| continue Z | |
| } | |
| result = append(result, z) | |
| if z >= 4 { | |
| continue Y | |
| } | |
| } | |
| result = append(result, -y) // should never be executed | |
| } | |
| result = append(result, x) | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestBreak1BadA should end in a panic when the outer-loop's | |
| // single-level break is ignore by BadOfSliceIndex | |
| func TestBreak1BadA(t *testing.T) { | |
| var result []int | |
| var expect = []int{1, 2, -1, 1, 2, -2, 1, 2, -3} | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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) | |
| } | |
| } else { | |
| t.Error("Wanted to see a failure") | |
| } | |
| }() | |
| for _, x := range BadOfSliceIndex([]int{-1, -2, -3, -4, -5}) { | |
| if x == -4 { | |
| break | |
| } | |
| for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if y == 3 { | |
| break | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| } | |
| } | |
| // TestBreak1BadB should end in a panic, sooner, when the inner-loop's | |
| // (nested) single-level break is ignored by BadOfSliceIndex | |
| func TestBreak1BadB(t *testing.T) { | |
| var result []int | |
| var expect = []int{1, 2} // inner breaks, panics, after before outer appends | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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) | |
| } | |
| } else { | |
| t.Error("Wanted to see a failure") | |
| } | |
| }() | |
| for _, x := range OfSliceIndex([]int{-1, -2, -3, -4, -5}) { | |
| if x == -4 { | |
| break | |
| } | |
| for _, y := range BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if y == 3 { | |
| break | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| } | |
| } | |
| // TestMultiCont0 tests multilevel continue with no bad iterators | |
| // (it should just work) | |
| func TestMultiCont0(t *testing.T) { | |
| var result []int | |
| var expect = []int{1000, 10, 2, 4, 2000} | |
| W: | |
| for _, w := range OfSliceIndex([]int{1000, 2000}) { | |
| result = append(result, w) | |
| if w == 2000 { | |
| break | |
| } | |
| for _, x := range OfSliceIndex([]int{100, 200, 300, 400}) { | |
| for _, y := range OfSliceIndex([]int{10, 20, 30, 40}) { | |
| result = append(result, y) | |
| for _, z := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if z&1 == 1 { | |
| continue | |
| } | |
| result = append(result, z) | |
| if z >= 4 { | |
| continue W // modified to be multilevel | |
| } | |
| } | |
| result = append(result, -y) // should never be executed | |
| } | |
| result = append(result, x) | |
| } | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestMultiCont1 tests multilevel continue with a bad iterator | |
| // in the outermost loop exited by the continue. | |
| func TestMultiCont1(t *testing.T) { | |
| var result []int | |
| var expect = []int{1000, 10, 2, 4} | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a failure, result was %v", result) | |
| } | |
| }() | |
| W: | |
| for _, w := range OfSliceIndex([]int{1000, 2000}) { | |
| result = append(result, w) | |
| if w == 2000 { | |
| break | |
| } | |
| for _, x := range BadOfSliceIndex([]int{100, 200, 300, 400}) { | |
| for _, y := range OfSliceIndex([]int{10, 20, 30, 40}) { | |
| result = append(result, y) | |
| for _, z := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if z&1 == 1 { | |
| continue | |
| } | |
| result = append(result, z) | |
| if z >= 4 { | |
| continue W | |
| } | |
| } | |
| result = append(result, -y) // should never be executed | |
| } | |
| result = append(result, x) | |
| } | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestMultiCont2 tests multilevel continue with a bad iterator | |
| // in a middle loop exited by the continue. | |
| func TestMultiCont2(t *testing.T) { | |
| var result []int | |
| var expect = []int{1000, 10, 2, 4} | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a failure, result was %v", result) | |
| } | |
| }() | |
| W: | |
| for _, w := range OfSliceIndex([]int{1000, 2000}) { | |
| result = append(result, w) | |
| if w == 2000 { | |
| break | |
| } | |
| for _, x := range OfSliceIndex([]int{100, 200, 300, 400}) { | |
| for _, y := range BadOfSliceIndex([]int{10, 20, 30, 40}) { | |
| result = append(result, y) | |
| for _, z := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if z&1 == 1 { | |
| continue | |
| } | |
| result = append(result, z) | |
| if z >= 4 { | |
| continue W | |
| } | |
| } | |
| result = append(result, -y) // should never be executed | |
| } | |
| result = append(result, x) | |
| } | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestMultiCont3 tests multilevel continue with a bad iterator | |
| // in the innermost loop exited by the continue. | |
| func TestMultiCont3(t *testing.T) { | |
| var result []int | |
| var expect = []int{1000, 10, 2, 4} | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a failure, result was %v", result) | |
| } | |
| }() | |
| W: | |
| for _, w := range OfSliceIndex([]int{1000, 2000}) { | |
| result = append(result, w) | |
| if w == 2000 { | |
| break | |
| } | |
| for _, x := range OfSliceIndex([]int{100, 200, 300, 400}) { | |
| for _, y := range OfSliceIndex([]int{10, 20, 30, 40}) { | |
| result = append(result, y) | |
| for _, z := range BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if z&1 == 1 { | |
| continue | |
| } | |
| result = append(result, z) | |
| if z >= 4 { | |
| continue W | |
| } | |
| } | |
| result = append(result, -y) // should never be executed | |
| } | |
| result = append(result, x) | |
| } | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestMultiBreak0 tests multilevel break with a bad iterator | |
| // in the outermost loop exited by the break (the outermost loop). | |
| func TestMultiBreak0(t *testing.T) { | |
| var result []int | |
| var expect = []int{1000, 10, 2, 4} | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a failure, result was %v", result) | |
| } | |
| }() | |
| W: | |
| for _, w := range BadOfSliceIndex([]int{1000, 2000}) { | |
| result = append(result, w) | |
| if w == 2000 { | |
| break | |
| } | |
| for _, x := range OfSliceIndex([]int{100, 200, 300, 400}) { | |
| for _, y := range OfSliceIndex([]int{10, 20, 30, 40}) { | |
| result = append(result, y) | |
| for _, z := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if z&1 == 1 { | |
| continue | |
| } | |
| result = append(result, z) | |
| if z >= 4 { | |
| break W | |
| } | |
| } | |
| result = append(result, -y) // should never be executed | |
| } | |
| result = append(result, x) | |
| } | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestMultiBreak1 tests multilevel break with a bad iterator | |
| // in an intermediate loop exited by the break. | |
| func TestMultiBreak1(t *testing.T) { | |
| var result []int | |
| var expect = []int{1000, 10, 2, 4} | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a failure, result was %v", result) | |
| } | |
| }() | |
| W: | |
| for _, w := range OfSliceIndex([]int{1000, 2000}) { | |
| result = append(result, w) | |
| if w == 2000 { | |
| break | |
| } | |
| for _, x := range BadOfSliceIndex([]int{100, 200, 300, 400}) { | |
| for _, y := range OfSliceIndex([]int{10, 20, 30, 40}) { | |
| result = append(result, y) | |
| for _, z := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if z&1 == 1 { | |
| continue | |
| } | |
| result = append(result, z) | |
| if z >= 4 { | |
| break W | |
| } | |
| } | |
| result = append(result, -y) // should never be executed | |
| } | |
| result = append(result, x) | |
| } | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestMultiBreak2 tests multilevel break with two bad iterators | |
| // in intermediate loops exited by the break. | |
| func TestMultiBreak2(t *testing.T) { | |
| var result []int | |
| var expect = []int{1000, 10, 2, 4} | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a failure, result was %v", result) | |
| } | |
| }() | |
| W: | |
| for _, w := range OfSliceIndex([]int{1000, 2000}) { | |
| result = append(result, w) | |
| if w == 2000 { | |
| break | |
| } | |
| for _, x := range BadOfSliceIndex([]int{100, 200, 300, 400}) { | |
| for _, y := range BadOfSliceIndex([]int{10, 20, 30, 40}) { | |
| result = append(result, y) | |
| for _, z := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if z&1 == 1 { | |
| continue | |
| } | |
| result = append(result, z) | |
| if z >= 4 { | |
| break W | |
| } | |
| } | |
| result = append(result, -y) // should never be executed | |
| } | |
| result = append(result, x) | |
| } | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // TestMultiBreak3 tests multilevel break with the bad iterator | |
| // in the innermost loop exited by the break. | |
| func TestMultiBreak3(t *testing.T) { | |
| var result []int | |
| var expect = []int{1000, 10, 2, 4} | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a failure, result was %v", result) | |
| } | |
| }() | |
| W: | |
| for _, w := range OfSliceIndex([]int{1000, 2000}) { | |
| result = append(result, w) | |
| if w == 2000 { | |
| break | |
| } | |
| for _, x := range OfSliceIndex([]int{100, 200, 300, 400}) { | |
| for _, y := range OfSliceIndex([]int{10, 20, 30, 40}) { | |
| result = append(result, y) | |
| for _, z := range BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if z&1 == 1 { | |
| continue | |
| } | |
| result = append(result, z) | |
| if z >= 4 { | |
| break W | |
| } | |
| } | |
| result = append(result, -y) // should never be executed | |
| } | |
| result = append(result, x) | |
| } | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| 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) | |
| } | |
| 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 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) | |
| } | |
| 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 _, 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) | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a panic, result was %v", 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} | |
| 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a panic, result was %v", 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} | |
| 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a panic, result was %v", 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a panic, result was %v", 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) | |
| } | |
| } else { | |
| t.Errorf("Wanted to see a panic, result was %v", 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 | |
| // quits running (the control flow bread crumbs remain.) | |
| func veryBad(s []int) []int { | |
| var result []int | |
| X: | |
| for _, x := range OfSliceIndex([]int{1, 2, 3}) { | |
| result = append(result, x) | |
| for _, y := range VeryBadOfSliceIndex(s) { | |
| result = append(result, y) | |
| break X | |
| } | |
| for _, z := range OfSliceIndex([]int{100, 200, 300}) { | |
| result = append(result, z) | |
| if z == 100 { | |
| break | |
| } | |
| } | |
| } | |
| return result | |
| } | |
| // veryBadCheck wraps a "very bad" iterator with Check, | |
| // demonstrating that the very bad iterator also hides panics | |
| // thrown by Check. | |
| func veryBadCheck(s []int) []int { | |
| var result []int | |
| X: | |
| for _, x := range OfSliceIndex([]int{1, 2, 3}) { | |
| result = append(result, x) | |
| for _, y := range Check2(VeryBadOfSliceIndex(s)) { | |
| result = append(result, y) | |
| break X | |
| } | |
| for _, z := range OfSliceIndex([]int{100, 200, 300}) { | |
| result = append(result, z) | |
| if z == 100 { | |
| break | |
| } | |
| } | |
| } | |
| return result | |
| } | |
| // okay is the not-bad version of veryBad. | |
| // They should behave the same. | |
| func okay(s []int) []int { | |
| var result []int | |
| X: | |
| for _, x := range OfSliceIndex([]int{1, 2, 3}) { | |
| result = append(result, x) | |
| for _, y := range OfSliceIndex(s) { | |
| result = append(result, y) | |
| break X | |
| } | |
| for _, z := range OfSliceIndex([]int{100, 200, 300}) { | |
| result = append(result, z) | |
| if z == 100 { | |
| break | |
| } | |
| } | |
| } | |
| return result | |
| } | |
| // TestVeryBad1 checks the behavior of an extremely poorly behaved iterator. | |
| func TestVeryBad1(t *testing.T) { | |
| expect := []int{} // assignment does not happen | |
| var result []int | |
| defer func() { | |
| if r := recover(); r != nil { | |
| expectPanic(t, r, RERR_MISSING) | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("(Inner) Expected %v, got %v", expect, result) | |
| } | |
| } else { | |
| t.Error("Wanted to see a failure") | |
| } | |
| }() | |
| result = veryBad([]int{10, 20, 30, 40, 50}) // odd length | |
| } | |
| func expectPanic(t *testing.T, r any, s string) { | |
| if matchError(r, s) { | |
| t.Logf("Saw expected panic '%v'", r) | |
| } else { | |
| t.Errorf("Saw wrong panic '%v'", r) | |
| } | |
| } | |
| func expectError(t *testing.T, err any, s string) { | |
| if matchError(err, s) { | |
| t.Logf("Saw expected error '%v'", err) | |
| } else { | |
| t.Errorf("Saw wrong error '%v'", err) | |
| } | |
| } | |
| // TestVeryBad2 checks the behavior of an extremely poorly behaved iterator. | |
| func TestVeryBad2(t *testing.T) { | |
| result := []int{} | |
| expect := []int{} | |
| defer func() { | |
| if r := recover(); r != nil { | |
| expectPanic(t, r, RERR_MISSING) | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("(Inner) Expected %v, got %v", expect, result) | |
| } | |
| } else { | |
| t.Error("Wanted to see a failure") | |
| } | |
| }() | |
| result = veryBad([]int{10, 20, 30, 40}) // even length | |
| } | |
| // TestVeryBadCheck checks the behavior of an extremely poorly behaved iterator, | |
| // which also suppresses the exceptions from "Check" | |
| func TestVeryBadCheck(t *testing.T) { | |
| expect := []int{} | |
| var result []int | |
| defer func() { | |
| if r := recover(); r != nil { | |
| expectPanic(t, r, CERR_MISSING) | |
| } | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| }() | |
| result = veryBadCheck([]int{10, 20, 30, 40}) // even length | |
| } | |
| // TestOk is the nice version of the very bad iterator. | |
| func TestOk(t *testing.T) { | |
| result := okay([]int{10, 20, 30, 40, 50}) // odd length | |
| expect := []int{1, 10} | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // testBreak1BadDefer checks that defer behaves properly even in | |
| // the presence of loop bodies panicking out of bad iterators. | |
| // (i.e., the instrumentation did not break defer in these loops) | |
| func testBreak1BadDefer(t *testing.T) (result []int) { | |
| var expect = []int{1, 2, -1, 1, 2, -2, 1, 2, -3, -30, -20, -10} | |
| defer func() { | |
| if r := recover(); r != nil { | |
| 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) | |
| } | |
| } else { | |
| t.Error("Wanted to see a failure") | |
| } | |
| }() | |
| for _, x := range BadOfSliceIndex([]int{-1, -2, -3, -4, -5}) { | |
| 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 { | |
| break | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| } | |
| return | |
| } | |
| func TestBreak1BadDefer(t *testing.T) { | |
| var result []int | |
| var expect = []int{1, 2, -1, 1, 2, -2, 1, 2, -3, -30, -20, -10} | |
| result = testBreak1BadDefer(t) | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("(Outer) Expected %v, got %v", expect, result) | |
| } | |
| } | |
| // testReturn1 has no bad iterators. | |
| func testReturn1(t *testing.T) (result []int, err any) { | |
| 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 = append(result, y) | |
| } | |
| result = append(result, x) | |
| } | |
| return | |
| } | |
| // testReturn2 has an outermost bad iterator | |
| func testReturn2(t *testing.T) (result []int, err any) { | |
| defer func() { | |
| err = recover() | |
| }() | |
| for _, x := range BadOfSliceIndex([]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 = append(result, y) | |
| } | |
| result = append(result, x) | |
| } | |
| return | |
| } | |
| // testReturn3 has an innermost bad iterator | |
| func testReturn3(t *testing.T) (result []int, err any) { | |
| 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 BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if y == 3 { | |
| return | |
| } | |
| result = append(result, y) | |
| } | |
| } | |
| 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) | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| if err != nil { | |
| t.Errorf("Unexpected error %v", err) | |
| } | |
| result, err = testReturn2(t) | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| if err == nil { | |
| t.Errorf("Missing expected error") | |
| } else { | |
| if matchError(err, RERR_DONE) { | |
| t.Logf("Saw expected panic '%v'", err) | |
| } else { | |
| t.Errorf("Saw wrong panic '%v'", err) | |
| } | |
| } | |
| result, err = testReturn3(t) | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| if err == nil { | |
| t.Errorf("Missing expected error") | |
| } else { | |
| 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. | |
| func testGotoA1(t *testing.T) (result []int, err any) { | |
| 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 { | |
| goto A | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| A: | |
| } | |
| return | |
| } | |
| // testGotoA2 tests loop-nest-internal goto, outer bad iterator. | |
| func testGotoA2(t *testing.T) (result []int, err any) { | |
| defer func() { | |
| err = recover() | |
| }() | |
| for _, x := range BadOfSliceIndex([]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 { | |
| goto A | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| A: | |
| } | |
| return | |
| } | |
| // testGotoA3 tests loop-nest-internal goto, inner bad iterator. | |
| func testGotoA3(t *testing.T) (result []int, err any) { | |
| 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 BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if y == 3 { | |
| goto A | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| A: | |
| } | |
| return | |
| } | |
| func TestGotoA(t *testing.T) { | |
| var result []int | |
| var expect = []int{-1, 1, 2, -2, 1, 2, -3, 1, 2, -4, -30, -20, -10} | |
| var expect3 = []int{-1, 1, 2, -10} // first goto becomes a panic | |
| var err any | |
| result, err = testGotoA1(t) | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| if err != nil { | |
| t.Errorf("Unexpected error %v", err) | |
| } | |
| result, err = testGotoA2(t) | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| if err == nil { | |
| t.Errorf("Missing expected error") | |
| } else { | |
| if matchError(err, RERR_DONE) { | |
| t.Logf("Saw expected panic '%v'", err) | |
| } else { | |
| t.Errorf("Saw wrong panic '%v'", err) | |
| } | |
| } | |
| result, err = testGotoA3(t) | |
| if !slices.Equal(expect3, result) { | |
| t.Errorf("Expected %v, got %v", expect3, result) | |
| } | |
| if err == nil { | |
| t.Errorf("Missing expected error") | |
| } else { | |
| if matchError(err, RERR_DONE) { | |
| t.Logf("Saw expected panic '%v'", err) | |
| } else { | |
| t.Errorf("Saw wrong panic '%v'", err) | |
| } | |
| } | |
| } | |
| // testGotoB1 tests loop-nest-exiting goto, no bad iterators. | |
| func testGotoB1(t *testing.T) (result []int, err any) { | |
| 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 { | |
| goto B | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| } | |
| B: | |
| result = append(result, 999) | |
| return | |
| } | |
| // testGotoB2 tests loop-nest-exiting goto, outer bad iterator. | |
| func testGotoB2(t *testing.T) (result []int, err any) { | |
| defer func() { | |
| err = recover() | |
| }() | |
| for _, x := range BadOfSliceIndex([]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 { | |
| goto B | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| } | |
| B: | |
| result = append(result, 999) | |
| return | |
| } | |
| // testGotoB3 tests loop-nest-exiting goto, inner bad iterator. | |
| func testGotoB3(t *testing.T) (result []int, err any) { | |
| 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 BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { | |
| if y == 3 { | |
| goto B | |
| } | |
| result = append(result, y) | |
| } | |
| result = append(result, x) | |
| } | |
| B: | |
| result = append(result, 999) | |
| return | |
| } | |
| func TestGotoB(t *testing.T) { | |
| var result []int | |
| var expect = []int{-1, 1, 2, 999, -10} | |
| var expectX = []int{-1, 1, 2, -10} | |
| var err any | |
| result, err = testGotoB1(t) | |
| if !slices.Equal(expect, result) { | |
| t.Errorf("Expected %v, got %v", expect, result) | |
| } | |
| if err != nil { | |
| t.Errorf("Unexpected error %v", err) | |
| } | |
| result, err = testGotoB2(t) | |
| if !slices.Equal(expectX, result) { | |
| t.Errorf("Expected %v, got %v", expectX, result) | |
| } | |
| if err == nil { | |
| t.Errorf("Missing expected error") | |
| } else { | |
| if matchError(err, RERR_DONE) { | |
| t.Logf("Saw expected panic '%v'", err) | |
| } else { | |
| t.Errorf("Saw wrong panic '%v'", err) | |
| } | |
| } | |
| result, err = testGotoB3(t) | |
| if !slices.Equal(expectX, result) { | |
| t.Errorf("Expected %v, got %v", expectX, result) | |
| } | |
| if err == nil { | |
| t.Errorf("Missing expected error") | |
| } else { | |
| 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) | |
| } | |
| } | |
| func Bug70035(s1, s2, s3 []string) string { | |
| var c1 string | |
| for v1 := range slices.Values(s1) { | |
| var c2 string | |
| for v2 := range slices.Values(s2) { | |
| var c3 string | |
| for v3 := range slices.Values(s3) { | |
| c3 = c3 + v3 | |
| } | |
| c2 = c2 + v2 + c3 | |
| } | |
| c1 = c1 + v1 + c2 | |
| } | |
| return c1 | |
| } | |
| func Test70035(t *testing.T) { | |
| got := Bug70035([]string{"1", "2", "3"}, []string{"a", "b", "c"}, []string{"A", "B", "C"}) | |
| want := "1aABCbABCcABC2aABCbABCcABC3aABCbABCcABC" | |
| if got != want { | |
| t.Errorf("got %v, want %v", got, want) | |
| } | |
| } |