Skip to content

Commit 1b7a7c8

Browse files
committed
feat: add While.
1 parent 47297b1 commit 1b7a7c8

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

while.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package async
2+
3+
import (
4+
"context"
5+
"reflect"
6+
)
7+
8+
// While repeatedly calls the function while the test function returns true. A valid test function
9+
// must match the following requirements.
10+
//
11+
// - The first return value of the test function must be a boolean value.
12+
// - It should have no parameters or accept a context only.
13+
//
14+
// c := 0
15+
// While(func() bool {
16+
// return c == 5
17+
// }, func() {
18+
// c++
19+
// })
20+
func While(testFn, fn AsyncFn) ([]any, error) {
21+
return while(context.Background(), testFn, fn)
22+
}
23+
24+
// WhileWithContext repeatedly calls the function with the specified context while the test
25+
// function returns true.
26+
func WhileWithContext(ctx context.Context, testFn, fn AsyncFn) ([]any, error) {
27+
return while(ctx, testFn, fn)
28+
}
29+
30+
// while repeatedly calls the function while the test function returns true.
31+
func while(parent context.Context, testFn, fn AsyncFn) ([]any, error) {
32+
validateWhileFuncs(testFn, fn)
33+
34+
ctx := getContext(parent)
35+
var out []any
36+
var err error
37+
38+
for {
39+
testOut, testErr := invokeAsyncFn(testFn, ctx, nil)
40+
if testErr != nil {
41+
return out, testErr
42+
}
43+
44+
isDone := testOut[0].(bool)
45+
if isDone {
46+
return out, nil
47+
}
48+
49+
out, err = invokeAsyncFn(fn, ctx, nil)
50+
if err != nil {
51+
break
52+
}
53+
}
54+
55+
return out, err
56+
}
57+
58+
// validateWhileFuncs validates the test function and the execution function for while.
59+
func validateWhileFuncs(testFn, fn AsyncFn) {
60+
if testFn == nil || fn == nil {
61+
panic(ErrNotFunction)
62+
}
63+
tft := reflect.TypeOf(testFn) // reflect.Type of the test function
64+
if tft.Kind() != reflect.Func || reflect.TypeOf(fn).Kind() != reflect.Func {
65+
panic(ErrNotFunction)
66+
}
67+
68+
if tft.NumOut() <= 0 || tft.Out(0).Kind() != reflect.Bool {
69+
panic(ErrInvalidTestFunc)
70+
}
71+
72+
numIn := tft.NumIn()
73+
isTakeContext := isFuncTakesContext(tft)
74+
if isTakeContext {
75+
numIn--
76+
}
77+
if numIn != 0 {
78+
panic(ErrInvalidTestFunc)
79+
}
80+
}

while_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package async
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
"time"
8+
9+
"github.com/ghosind/go-assert"
10+
)
11+
12+
func TestWhile(t *testing.T) {
13+
a := assert.New(t)
14+
count := 0
15+
16+
out, err := While(func() bool {
17+
return count == 5
18+
}, func() int {
19+
count++
20+
return count
21+
})
22+
a.NilNow(err)
23+
a.EqualNow(out, []any{5})
24+
}
25+
26+
func TestWhileInvalidParameters(t *testing.T) {
27+
a := assert.New(t)
28+
29+
a.PanicOfNow(func() {
30+
While(nil, func() {})
31+
}, ErrNotFunction)
32+
a.PanicOfNow(func() {
33+
While(func() {}, nil)
34+
}, ErrNotFunction)
35+
a.PanicOfNow(func() {
36+
While(1, "hello")
37+
}, ErrNotFunction)
38+
a.PanicOfNow(func() {
39+
While(func() {}, func() {})
40+
}, ErrInvalidTestFunc)
41+
a.NotPanicNow(func() {
42+
While(func() bool { return true }, func() {})
43+
})
44+
a.NotPanicNow(func() {
45+
While(func(ctx context.Context) bool { return true }, func() {})
46+
})
47+
a.PanicOfNow(func() {
48+
While(func(ctx context.Context, i int) bool { return true }, func() {})
49+
}, ErrInvalidTestFunc)
50+
}
51+
52+
func TestWhileWithTestFunctionError(t *testing.T) {
53+
a := assert.New(t)
54+
expectedErr := errors.New("expected error")
55+
56+
out, err := While(func() bool {
57+
panic(expectedErr)
58+
}, func() int {
59+
return 0
60+
})
61+
a.NotNilNow(err)
62+
a.EqualNow(err, expectedErr)
63+
a.EqualNow(out, []any{})
64+
}
65+
66+
func TestWhileWithFunctionError(t *testing.T) {
67+
a := assert.New(t)
68+
expectedErr := errors.New("expected error")
69+
70+
out, err := While(func() bool {
71+
return false
72+
}, func() (int, error) {
73+
return 0, expectedErr
74+
})
75+
a.NotNilNow(err)
76+
a.EqualNow(err, expectedErr)
77+
a.EqualNow(out, []any{0, expectedErr})
78+
}
79+
80+
func TestWhileWithContext(t *testing.T) {
81+
a := assert.New(t)
82+
ctx, canFunc := context.WithTimeout(context.Background(), 100*time.Millisecond)
83+
defer canFunc()
84+
85+
start := time.Now()
86+
out, err := WhileWithContext(ctx, func(ctx context.Context) bool {
87+
select {
88+
case <-ctx.Done():
89+
return true
90+
default:
91+
return false
92+
}
93+
}, func() {
94+
})
95+
a.NilNow(err)
96+
a.EqualNow(out, []any{})
97+
dur := time.Since(start)
98+
a.GteNow(dur, 100*time.Millisecond)
99+
a.LteNow(dur, 150*time.Millisecond)
100+
}

0 commit comments

Comments
 (0)