Clarify and fix State's EOF behavior
This commit is contained in:
parent
c29be1a7b6
commit
526e40323d
1
TODO.txt
1
TODO.txt
@ -1,4 +1,3 @@
|
|||||||
Test State against both possible ReaderAt EOF behaviors
|
|
||||||
Write Repeat tests
|
Write Repeat tests
|
||||||
Think about not requiring so much Pos() when making messages
|
Think about not requiring so much Pos() when making messages
|
||||||
Rename Seq2 to Seq
|
Rename Seq2 to Seq
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -142,10 +143,19 @@ type State[In any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s State[In]) Read(dst []In) (n uint64, next State[In], err error) {
|
func (s State[In]) Read(dst []In) (n uint64, next State[In], err error) {
|
||||||
|
if s.pos > math.MaxInt64 {
|
||||||
|
return 0, s, io.EOF
|
||||||
|
}
|
||||||
nread, err := s.r.ReadAt(dst, int64(s.pos))
|
nread, err := s.r.ReadAt(dst, int64(s.pos))
|
||||||
if nread > 0 {
|
if nread > 0 {
|
||||||
s.pos += uint64(nread)
|
s.pos += uint64(nread)
|
||||||
}
|
}
|
||||||
|
if nread == len(dst) && err == io.EOF {
|
||||||
|
if nread == 0 {
|
||||||
|
return 0, s, io.EOF
|
||||||
|
}
|
||||||
|
return uint64(nread), s, nil
|
||||||
|
}
|
||||||
return uint64(nread), s, err
|
return uint64(nread), s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.codemonkeysoftware.net/b/gigaparsec"
|
"git.codemonkeysoftware.net/b/gigaparsec"
|
||||||
@ -15,6 +16,30 @@ import (
|
|||||||
"pgregory.net/rapid"
|
"pgregory.net/rapid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type customEOFReaderAt struct {
|
||||||
|
r *bytes.Reader
|
||||||
|
eofAtExactFit bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCustomEOFReaderAt(b []byte, eofAtExactFit bool) customEOFReaderAt {
|
||||||
|
return customEOFReaderAt{
|
||||||
|
r: bytes.NewReader(b),
|
||||||
|
eofAtExactFit: eofAtExactFit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r customEOFReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
|
||||||
|
n, err = r.r.ReadAt(p, off)
|
||||||
|
if int64(len(p))+off >= r.r.Size() {
|
||||||
|
if r.eofAtExactFit {
|
||||||
|
err = io.EOF
|
||||||
|
} else {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
func TestState(t *testing.T) {
|
func TestState(t *testing.T) {
|
||||||
t.Run("state reads the same position every time", rapid.MakeCheck(func(t *rapid.T) {
|
t.Run("state reads the same position every time", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
data := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "data")
|
data := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "data")
|
||||||
@ -31,19 +56,59 @@ func TestState(t *testing.T) {
|
|||||||
must.NoError(t, err)
|
must.NoError(t, err)
|
||||||
must.SliceEqOp(t, expected, dst)
|
must.SliceEqOp(t, expected, dst)
|
||||||
}))
|
}))
|
||||||
t.Run("Read returns io.EOF iff it overruns source", rapid.MakeCheck(func(t *rapid.T) {
|
t.Run("Read ends before end of source", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
data := rapid.SliceOfN(rapid.Byte(), 0, 100).Draw(t, "data")
|
src := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "src")
|
||||||
dst := pgen.SliceOfNZero[byte](0, 200).Draw(t, "dst")
|
endReadAt := rapid.IntRange(0, len(src)-1).Draw(t, "endReadAt")
|
||||||
st := gigaparsec.MakeState(bytes.NewReader(data))
|
pos := rapid.Uint64Range(0, uint64(endReadAt)).Draw(t, "pos")
|
||||||
|
dst := pgen.SliceOfNZero[byte](0, endReadAt-int(pos)).Draw(t, "dst")
|
||||||
|
st := gigaparsec.MakeState(bytes.NewReader(src)).At(pos)
|
||||||
|
|
||||||
n, _, err := st.Read(dst)
|
n, next, err := st.At(pos).Read(dst)
|
||||||
t.Logf("n=%d", n)
|
|
||||||
must.EqOp(t, min(len(data), len(dst)), int(n))
|
test.EqOp(t, uint64(len(dst)), n)
|
||||||
if len(dst) > len(data) || st.Pos() == uint64(len(data)) {
|
ptest.StateIsAt(t, next, pos+n)
|
||||||
must.ErrorIs(t, err, io.EOF)
|
test.NoError(t, err)
|
||||||
} else {
|
test.SliceEqOp(t, src[pos:pos+n], dst)
|
||||||
must.NoError(t, err)
|
}))
|
||||||
}
|
t.Run("Non-empty Read ends at end of source", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
|
readerReturnsEOF := rapid.Bool().Draw(t, "readerReturnsEOF")
|
||||||
|
src := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "src")
|
||||||
|
dst := pgen.SliceOfNZero[byte](1, len(src)).Draw(t, "dst")
|
||||||
|
pos := uint64(len(src) - len(dst))
|
||||||
|
st := gigaparsec.MakeState(newCustomEOFReaderAt(src, readerReturnsEOF))
|
||||||
|
|
||||||
|
n, next, err := st.At(pos).Read(dst)
|
||||||
|
|
||||||
|
test.EqOp(t, uint64(len(dst)), n)
|
||||||
|
ptest.StateIsAt(t, next, pos+n)
|
||||||
|
test.NoError(t, err)
|
||||||
|
test.SliceEqOp(t, src[pos:pos+n], dst)
|
||||||
|
}))
|
||||||
|
t.Run("Read overruns source", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
|
src := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "src")
|
||||||
|
pos := rapid.Uint64Range(0, uint64(len(src))-1).Draw(t, "pos")
|
||||||
|
minDstLen := len(src) - int(pos) + 1
|
||||||
|
dst := pgen.SliceOfNZero[byte](minDstLen, minDstLen+10).Draw(t, "dst")
|
||||||
|
st := gigaparsec.MakeState(bytes.NewReader(src)).At(pos)
|
||||||
|
|
||||||
|
n, next, err := st.Read(dst)
|
||||||
|
|
||||||
|
test.EqOp(t, uint64(len(src)), n+pos)
|
||||||
|
ptest.StateIsAt(t, next, pos+n)
|
||||||
|
test.ErrorIs(t, err, io.EOF)
|
||||||
|
test.SliceEqOp(t, src[pos:pos+n], dst[:n])
|
||||||
|
}))
|
||||||
|
t.Run("Read starts after end of source", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
|
src := rapid.SliceOfN(rapid.Byte(), 0, 100).Draw(t, "src")
|
||||||
|
dst := pgen.SliceOfNZero[byte](0, 100).Draw(t, "dst")
|
||||||
|
pos := rapid.Uint64Min(uint64(len(src))).Draw(t, "pos")
|
||||||
|
st := gigaparsec.MakeState(bytes.NewReader(src)).At(pos)
|
||||||
|
|
||||||
|
n, next, err := st.Read(dst)
|
||||||
|
|
||||||
|
test.EqOp(t, 0, n)
|
||||||
|
ptest.StateIsAt(t, next, pos)
|
||||||
|
test.ErrorIs(t, err, io.EOF)
|
||||||
}))
|
}))
|
||||||
t.Run("next state reads next input", rapid.MakeCheck(func(t *rapid.T) {
|
t.Run("next state reads next input", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
const maxLen = 100
|
const maxLen = 100
|
||||||
@ -58,14 +123,6 @@ func TestState(t *testing.T) {
|
|||||||
n, _, _ := next.Read(dst)
|
n, _, _ := next.Read(dst)
|
||||||
must.SliceEqOp(t, data[skip:skip+int(n)], dst[:n])
|
must.SliceEqOp(t, data[skip:skip+int(n)], dst[:n])
|
||||||
}))
|
}))
|
||||||
t.Run("Read returns io.EOF if n is less than requested", rapid.MakeCheck(func(t *rapid.T) {
|
|
||||||
data := rapid.SliceOfN(rapid.Byte(), 0, 100).Draw(t, "data")
|
|
||||||
st := gigaparsec.MakeState(bytes.NewReader(data))
|
|
||||||
|
|
||||||
n, _, err := st.Read(make([]byte, len(data)+1))
|
|
||||||
test.ErrorIs(t, err, io.EOF)
|
|
||||||
test.EqOp(t, len(data), int(n))
|
|
||||||
}))
|
|
||||||
t.Run("At sets state position", rapid.MakeCheck(func(t *rapid.T) {
|
t.Run("At sets state position", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
data := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "data")
|
data := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "data")
|
||||||
pos := rapid.Uint64Range(0, uint64(len(data)-1)).Draw(t, "pos")
|
pos := rapid.Uint64Range(0, uint64(len(data)-1)).Draw(t, "pos")
|
||||||
@ -95,7 +152,7 @@ func TestState(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
t.Run("Read returns an error if the ReaderAt fails", rapid.MakeCheck(func(t *rapid.T) {
|
t.Run("Read returns an error if the ReaderAt fails", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
expectedErr := pgen.Error().Draw(t, "expectedErr")
|
expectedErr := pgen.Error().Draw(t, "expectedErr")
|
||||||
startPos := rapid.Uint64().Draw(t, "startPos")
|
startPos := rapid.Uint64Max(math.MaxInt64).Draw(t, "startPos")
|
||||||
dst := pgen.SliceOfNZero[byte](0, 100).Draw(t, "dst")
|
dst := pgen.SliceOfNZero[byte](0, 100).Draw(t, "dst")
|
||||||
st := gigaparsec.MakeState(ptest.ErrReaderAt(expectedErr)).At(startPos)
|
st := gigaparsec.MakeState(ptest.ErrReaderAt(expectedErr)).At(startPos)
|
||||||
n, next, err := st.Read(dst)
|
n, next, err := st.Read(dst)
|
||||||
|
Loading…
Reference in New Issue
Block a user