いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-465 (iotestパッケージのメモ)(02-ErrReader)

関連記事

Goメモ-464 (iotestパッケージのメモ)(01-TestReader) - いろいろ備忘録日記

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。忘れないうちにメモメモ。。。

以下の書籍で知ったのですが、testing/iotest というパッケージの存在を知りました。こんなのあったんですね。

このパッケージの中には、io.Readerやio.Writerを受け取り処理する関数をテストする際に便利な関数なども用意されています。

今回は、iotest.ErrReader 関数について。

iotest.ErrReader 関数は、常にエラーを返す io.Reader です。io.Readerを受け取って処理する関数のエラー処理のテストなどに使えます。

サンプル

read.go
package errreader

import (
    "errors"
    "io"
    "syscall"
    "time"
)

var (
    Interval         = 10 * time.Millisecond
    ErrTooManyEAGAIN = errors.New("retry over (EAGAIN)")
    MaxRetryCount    = 5
)

func read(r io.Reader, p []byte) error {
    var (
        buf     = make([]byte, 2)
        numRead int
        offset  int
        count   int
        err     error
    )

    for count = 0; count < MaxRetryCount; {
        if len(p) <= offset {
            break
        }

        clear(buf)

        numRead, err = r.Read(buf)
        if err != nil {
            switch {
            case errors.Is(err, io.EOF):
                break
            case errors.Is(err, syscall.EAGAIN):
                time.Sleep(Interval)
                count++
                continue
            default:
                return err
            }
        }

        copy(p[offset:offset+numRead], buf[:numRead])
        offset += numRead
    }

    if MaxRetryCount <= count {
        return ErrTooManyEAGAIN
    }

    return nil
}
errreader_test.go
package errreader

import (
    "errors"
    "strings"
    "syscall"
    "testing"
    "testing/iotest"
)

func TestReadOk(t *testing.T) {
    var (
        s   = "hello world"
        r   = strings.NewReader(s)
        p   = make([]byte, len(s))
        err error
    )

    err = read(r, p)
    if err != nil {
        t.Fatal(err)
    }

    if string(p) != s {
        t.Fatalf("[want] equal\t[got] not equal (%s, %s)", string(p), s)
    }
}

func TestReadTooManyEAGAIN(t *testing.T) {
    //
    // iotest.ErrReader() は、指定したエラーを返す io.Reader を返してくれる。
    // io.Readerを使って何らかの処理を行う関数などを実装している場合のエラーテストに便利。
    // (例えば、ノンブロッキング処理をしている実装で、ずっとEAGAINが返ってくる場合のテストなど)
    //
    // - https://pkg.go.dev/testing/iotest@go1.23.0#ErrReader
    //
    var (
        s = "hello world"
        r = iotest.ErrReader(syscall.EAGAIN)
        p = make([]byte, len(s))
    )

    err := read(r, p)
    if err == nil {
        t.Fatal("[want] err \t[got] nil")
    }

    if !errors.Is(err, ErrTooManyEAGAIN) {
        t.Fatalf("[want] ErrRetryOver\t[got] %v", err)
    }
}

実行すると以下のようになります。

$ task
task: [default] go test -v .
=== RUN   TestReadOk
--- PASS: TestReadOk (0.00s)
=== RUN   TestReadTooManyEAGAIN
--- PASS: TestReadTooManyEAGAIN (0.05s)
PASS
ok      github.com/devlights/try-golang/examples/singleapp/iotest/errreader     0.054s

参考情報

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。