2024-09-27 15:29:27 +00:00
|
|
|
package gigaparsec_test
|
2024-09-03 19:55:32 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2024-09-30 19:02:57 +00:00
|
|
|
"cmp"
|
2024-09-03 19:55:32 +00:00
|
|
|
"io"
|
2024-09-30 21:33:24 +00:00
|
|
|
"math"
|
2024-09-03 19:55:32 +00:00
|
|
|
"testing"
|
|
|
|
|
2024-09-27 15:29:27 +00:00
|
|
|
"git.codemonkeysoftware.net/b/gigaparsec"
|
2024-09-13 17:17:32 +00:00
|
|
|
ptest "git.codemonkeysoftware.net/b/gigaparsec/test"
|
2024-09-30 19:02:57 +00:00
|
|
|
"git.codemonkeysoftware.net/b/gigaparsec/test/generator"
|
2024-09-13 17:24:43 +00:00
|
|
|
pgen "git.codemonkeysoftware.net/b/gigaparsec/test/generator"
|
2024-09-03 19:55:32 +00:00
|
|
|
"github.com/shoenig/test"
|
|
|
|
"github.com/shoenig/test/must"
|
|
|
|
"pgregory.net/rapid"
|
|
|
|
)
|
|
|
|
|
2024-09-30 21:33:24 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-09-27 15:29:27 +00:00
|
|
|
func TestState(t *testing.T) {
|
|
|
|
t.Run("state reads the same position every time", rapid.MakeCheck(func(t *rapid.T) {
|
2024-09-03 19:55:32 +00:00
|
|
|
data := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "data")
|
2024-09-13 17:24:43 +00:00
|
|
|
dst := pgen.SliceOfNZero[byte](0, len(data)-1).Draw(t, "dst")
|
2024-09-03 19:55:32 +00:00
|
|
|
expected := data[:len(dst)]
|
2024-09-27 15:29:27 +00:00
|
|
|
st := gigaparsec.MakeState(bytes.NewReader(data))
|
2024-09-03 19:55:32 +00:00
|
|
|
|
2024-09-27 15:29:27 +00:00
|
|
|
_, next, err := st.Read(dst)
|
2024-09-03 19:55:32 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.SliceEqOp(t, expected, dst)
|
|
|
|
|
|
|
|
next.Read(dst)
|
2024-09-27 15:29:27 +00:00
|
|
|
_, _, err = st.Read(dst)
|
2024-09-03 19:55:32 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.SliceEqOp(t, expected, dst)
|
|
|
|
}))
|
2024-09-30 21:33:24 +00:00
|
|
|
t.Run("Read ends before end of source", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
src := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "src")
|
|
|
|
endReadAt := rapid.IntRange(0, len(src)-1).Draw(t, "endReadAt")
|
|
|
|
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)
|
2024-09-03 19:55:32 +00:00
|
|
|
|
2024-09-30 21:33:24 +00:00
|
|
|
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("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)
|
2024-09-03 19:55:32 +00:00
|
|
|
}))
|
2024-09-27 15:29:27 +00:00
|
|
|
t.Run("next state reads next input", rapid.MakeCheck(func(t *rapid.T) {
|
2024-09-03 19:55:32 +00:00
|
|
|
const maxLen = 100
|
|
|
|
data := rapid.SliceOfN(rapid.Byte(), 1, maxLen).Draw(t, "data")
|
2024-09-11 20:52:27 +00:00
|
|
|
skip := rapid.IntRange(0, len(data)-1).Draw(t, "skip")
|
2024-09-27 15:29:27 +00:00
|
|
|
st := gigaparsec.MakeState(bytes.NewReader(data))
|
2024-09-03 19:55:32 +00:00
|
|
|
|
2024-09-27 15:29:27 +00:00
|
|
|
_, next, err := st.Read(make([]byte, skip))
|
2024-09-03 19:55:32 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.EqOp(t, skip, int(next.Pos()))
|
|
|
|
dst := make([]byte, maxLen)
|
|
|
|
n, _, _ := next.Read(dst)
|
|
|
|
must.SliceEqOp(t, data[skip:skip+int(n)], dst[:n])
|
|
|
|
}))
|
2024-09-27 15:29:27 +00:00
|
|
|
t.Run("At sets state position", rapid.MakeCheck(func(t *rapid.T) {
|
2024-09-08 15:17:29 +00:00
|
|
|
data := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "data")
|
|
|
|
pos := rapid.Uint64Range(0, uint64(len(data)-1)).Draw(t, "pos")
|
2024-09-27 15:29:27 +00:00
|
|
|
st := gigaparsec.MakeState(bytes.NewReader(data)).At(pos)
|
2024-09-08 15:17:29 +00:00
|
|
|
|
|
|
|
dst := make([]byte, 1)
|
2024-09-27 15:29:27 +00:00
|
|
|
n, _, err := st.Read(dst)
|
2024-09-08 15:17:29 +00:00
|
|
|
test.EqOp(t, 1, n)
|
|
|
|
test.NoError(t, err)
|
|
|
|
test.EqOp(t, data[pos], dst[0])
|
|
|
|
}))
|
2024-09-11 20:52:27 +00:00
|
|
|
t.Run("Pos returns correct position after At", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
var data []byte
|
|
|
|
pos := rapid.Uint64().Draw(t, "pos")
|
2024-09-27 15:29:27 +00:00
|
|
|
st := gigaparsec.MakeState(bytes.NewReader(data)).At(pos)
|
|
|
|
test.EqOp(t, pos, st.Pos())
|
2024-09-11 20:52:27 +00:00
|
|
|
}))
|
|
|
|
t.Run("Pos returns correct position after Read", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
const maxLen = 100
|
|
|
|
data := rapid.SliceOfN(rapid.Byte(), 1, maxLen).Draw(t, "data")
|
|
|
|
skip := rapid.Uint64Range(0, uint64(len(data)-1)).Draw(t, "skip")
|
2024-09-27 15:29:27 +00:00
|
|
|
st := gigaparsec.MakeState(bytes.NewReader(data))
|
2024-09-11 20:52:27 +00:00
|
|
|
|
2024-09-27 15:29:27 +00:00
|
|
|
_, next, err := st.Read(make([]byte, skip))
|
2024-09-11 20:52:27 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
test.EqOp(t, skip, next.Pos())
|
|
|
|
}))
|
2024-09-13 17:17:32 +00:00
|
|
|
t.Run("Read returns an error if the ReaderAt fails", rapid.MakeCheck(func(t *rapid.T) {
|
2024-09-13 17:24:43 +00:00
|
|
|
expectedErr := pgen.Error().Draw(t, "expectedErr")
|
2024-09-30 21:33:24 +00:00
|
|
|
startPos := rapid.Uint64Max(math.MaxInt64).Draw(t, "startPos")
|
2024-09-13 17:24:43 +00:00
|
|
|
dst := pgen.SliceOfNZero[byte](0, 100).Draw(t, "dst")
|
2024-09-27 15:29:27 +00:00
|
|
|
st := gigaparsec.MakeState(ptest.ErrReaderAt(expectedErr)).At(startPos)
|
|
|
|
n, next, err := st.Read(dst)
|
2024-09-13 17:17:32 +00:00
|
|
|
test.ErrorIs(t, err, expectedErr)
|
|
|
|
test.EqOp(t, startPos, next.Pos())
|
|
|
|
test.Zero(t, n)
|
|
|
|
}))
|
2024-09-03 19:55:32 +00:00
|
|
|
}
|
2024-09-30 19:02:57 +00:00
|
|
|
|
|
|
|
func TestSliceReaderAt(t *testing.T) {
|
|
|
|
const maxLen = 100
|
|
|
|
t.Run("offset ≥ 0", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
src := rapid.SliceOfN(rapid.Byte(), 0, maxLen).Draw(t, "src")
|
|
|
|
dst := generator.SliceOfNZero[byte](0, maxLen).Draw(t, "dst")
|
|
|
|
offset := rapid.Int64Range(0, int64(len(src))+10).Draw(t, "offset")
|
|
|
|
|
|
|
|
n, err := gigaparsec.SliceReaderAt[byte](src).ReadAt(dst, offset)
|
|
|
|
|
|
|
|
switch cmp.Compare(len(src), int(offset)+len(dst)) {
|
|
|
|
case -1:
|
|
|
|
// Read overruns src.
|
|
|
|
test.ErrorIs(t, err, io.EOF)
|
|
|
|
test.EqOp(t, max(0, len(src)-int(offset)), n)
|
|
|
|
case 0:
|
|
|
|
// Read exactly reaches end of source.
|
|
|
|
// io.ReaderAt spec allows error to be either io.EOF or nil.
|
|
|
|
test.EqOp(t, len(dst), n)
|
|
|
|
case 1:
|
|
|
|
// Read ends before end of source.
|
|
|
|
test.NoError(t, err)
|
|
|
|
test.EqOp(t, len(dst), n)
|
|
|
|
}
|
|
|
|
|
|
|
|
if offset < int64(len(src)) {
|
|
|
|
test.SliceEqOp(t, src[offset:offset+int64(n)], dst[:n])
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
t.Run("offset < 0", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
src := rapid.SliceOfN(rapid.Byte(), 0, maxLen).Draw(t, "src")
|
|
|
|
dst := generator.SliceOfNZero[byte](0, maxLen).Draw(t, "dst")
|
|
|
|
offset := rapid.Int64Max(-1).Draw(t, "offset")
|
|
|
|
|
|
|
|
n, err := gigaparsec.SliceReaderAt[byte](src).ReadAt(dst, offset)
|
|
|
|
test.Error(t, err)
|
|
|
|
test.EqOp(t, 0, n)
|
|
|
|
}))
|
|
|
|
}
|