Skip to content

Commit 172577c

Browse files
authored
Merge pull request #840 from devlights/add-iotest-examples
2 parents bd2b135 + ef4917b commit 172577c

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# https://taskfile.dev
2+
3+
version: '3'
4+
5+
tasks:
6+
default:
7+
cmds:
8+
- go test -v .
9+
ignore_error: true
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package timeoutreader
2+
3+
import (
4+
"math/rand"
5+
"time"
6+
)
7+
8+
const (
9+
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
10+
)
11+
12+
func randomString(buf []byte) {
13+
var (
14+
unixNano = time.Now().UnixNano()
15+
rndSource = rand.NewSource(unixNano)
16+
rnd = rand.New(rndSource)
17+
)
18+
19+
for i := range buf {
20+
buf[i] = charset[rnd.Intn(len(charset))]
21+
}
22+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package timeoutreader
2+
3+
import (
4+
"errors"
5+
"io"
6+
"time"
7+
)
8+
9+
var (
10+
interval = 10 * time.Millisecond
11+
ErrRetryOver = errors.New("retry over")
12+
)
13+
14+
func readAllAtOnce(r io.Reader, p []byte) error {
15+
var (
16+
b []byte
17+
err error
18+
)
19+
20+
b, err = io.ReadAll(r)
21+
if err != nil {
22+
return err
23+
}
24+
25+
copy(p, b)
26+
27+
return nil
28+
}
29+
30+
func readWithRetry(r io.Reader, p []byte, retries int) error {
31+
var (
32+
buf = make([]byte, 1<<9)
33+
numRead int
34+
offset int
35+
count int
36+
maxCount = retries + 1
37+
err error
38+
)
39+
40+
for count = 0; count < maxCount; {
41+
if len(p) <= offset {
42+
break
43+
}
44+
45+
clear(buf)
46+
47+
numRead, err = r.Read(buf)
48+
if err != nil {
49+
if errors.Is(err, io.EOF) {
50+
break
51+
}
52+
53+
time.Sleep(interval)
54+
count++
55+
continue
56+
}
57+
58+
copy(p[offset:offset+numRead], buf[:numRead])
59+
offset += numRead
60+
}
61+
62+
if maxCount <= count {
63+
return ErrRetryOver
64+
}
65+
66+
return nil
67+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package timeoutreader
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"testing"
7+
"testing/iotest"
8+
)
9+
10+
// TestReadAllAtOnce は、io.ReadAll()を利用してデータを読み取るテストケースです。
11+
// テストで利用している io.Reader は、iotest.TimeoutReader()から生成しています。
12+
//
13+
// iotest.TimeoutReader() は、**2回目のみ**タイムアウトで失敗する
14+
// io.Readerを作成してくれます。なので、通信処理などをテストする際にとても便利です。
15+
// 2回目以降は普通にデータが読み取れるようになっています。
16+
//
17+
// このテストケースでは、読み取りに io.ReadAll() を利用しているため、2回目のエラーで
18+
// 処理が返ってしまい、テストケースがFailとなります。io.ReadAll()には、リトライ処理などは
19+
// 実装されていないため、タイムアウトが発生する可能性がある場面では利用しない方が良いということになります。
20+
//
21+
// # REFERENCES
22+
// - https://pkg.go.dev/testing/iotest@go1.23.0#TimeoutReader
23+
func TestReadAllAtOnce(t *testing.T) {
24+
var (
25+
data = make([]byte, 1<<10)
26+
buf = make([]byte, len(data))
27+
reader io.Reader
28+
err error
29+
)
30+
31+
randomString(data)
32+
reader = iotest.TimeoutReader(bytes.NewReader(data))
33+
34+
err = readAllAtOnce(reader, buf)
35+
if err != nil {
36+
t.Fatal(err)
37+
}
38+
39+
if !bytes.Equal(data, buf) {
40+
t.Fatalf("[want] equal\t[got] not equal")
41+
}
42+
}
43+
44+
// TestReadWithRetry は、リトライ処理を考慮した読み取り処理を利用してデータを読み取るテストケースです。
45+
// テストで利用している io.Reader は、iotest.TimeoutReader()から生成しています。
46+
//
47+
// iotest.TimeoutReader() は、**2回目のみ**タイムアウトで失敗する
48+
// io.Readerを作成してくれます。なので、通信処理などをテストする際にとても便利です。
49+
// 2回目以降は普通にデータが読み取れるようになっています。
50+
//
51+
// このテストケースでは、読み取り中にタイムアウトを含むエラーが発生しても、指定回数リトライして
52+
// 再試行するようになっているため、テストケースが通ります。
53+
//
54+
// # REFERENCES
55+
// - https://pkg.go.dev/testing/iotest@go1.23.0#TimeoutReader
56+
func TestReadWithRetry(t *testing.T) {
57+
var (
58+
data = make([]byte, 1<<10)
59+
buf = make([]byte, len(data))
60+
reader io.Reader
61+
err error
62+
)
63+
64+
randomString(data)
65+
reader = iotest.TimeoutReader(bytes.NewReader(data))
66+
67+
err = readWithRetry(reader, buf, 3)
68+
if err != nil {
69+
t.Fatal(err)
70+
}
71+
72+
if !bytes.Equal(data, buf) {
73+
t.Fatalf("[want] equal\t[got] not equal")
74+
}
75+
}

0 commit comments

Comments
 (0)