2024-09-03 19:55:32 +00:00
|
|
|
package cursor_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"io"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"git.codemonkeysoftware.net/b/gigaparsec/cursor"
|
2024-09-13 17:17:32 +00:00
|
|
|
ptest "git.codemonkeysoftware.net/b/gigaparsec/test"
|
|
|
|
"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"
|
|
|
|
)
|
|
|
|
|
|
|
|
func Todo(t *testing.T) {
|
|
|
|
t.Errorf("TODO")
|
|
|
|
}
|
|
|
|
|
2024-09-13 17:17:32 +00:00
|
|
|
// TODO move this to generator
|
2024-09-03 19:55:32 +00:00
|
|
|
func SliceOfNZero[T any](minLen, maxLen int) *rapid.Generator[[]T] {
|
|
|
|
return rapid.Map(rapid.IntRange(minLen, maxLen), func(n int) []T {
|
|
|
|
return make([]T, n)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func testCursor[C cursor.Cursor[byte]](t *testing.T, makeCursor func([]byte) C) {
|
|
|
|
t.Helper()
|
|
|
|
t.Run("cursor reads the same position every time", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
data := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "data")
|
|
|
|
dst := SliceOfNZero[byte](0, len(data)-1).Draw(t, "dst")
|
|
|
|
expected := data[:len(dst)]
|
|
|
|
c := makeCursor(data)
|
|
|
|
|
|
|
|
_, next, err := c.Read(dst)
|
|
|
|
must.NoError(t, err)
|
|
|
|
must.SliceEqOp(t, expected, dst)
|
|
|
|
|
|
|
|
next.Read(dst)
|
|
|
|
_, _, err = c.Read(dst)
|
|
|
|
must.NoError(t, err)
|
|
|
|
must.SliceEqOp(t, expected, dst)
|
|
|
|
}))
|
|
|
|
t.Run("Read returns io.EOF iff it overruns source", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
data := rapid.SliceOfN(rapid.Byte(), 0, 100).Draw(t, "data")
|
|
|
|
dst := SliceOfNZero[byte](0, 200).Draw(t, "dst")
|
|
|
|
c := makeCursor(data)
|
|
|
|
|
|
|
|
n, _, err := c.Read(dst)
|
|
|
|
t.Logf("n=%d", n)
|
|
|
|
must.EqOp(t, min(len(data), len(dst)), int(n))
|
|
|
|
if len(dst) > len(data) || c.Pos() == uint64(len(data)) {
|
|
|
|
must.ErrorIs(t, err, io.EOF)
|
|
|
|
} else {
|
|
|
|
must.NoError(t, err)
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
t.Run("next cursor reads next input", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
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-03 19:55:32 +00:00
|
|
|
c := makeCursor(data)
|
|
|
|
|
|
|
|
_, next, err := c.Read(make([]byte, skip))
|
|
|
|
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])
|
|
|
|
}))
|
|
|
|
t.Run("Read returns an error if n is less than requested", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
data := rapid.SliceOfN(rapid.Byte(), 0, 100).Draw(t, "data")
|
|
|
|
c := makeCursor(data)
|
|
|
|
|
|
|
|
n, _, err := c.Read(make([]byte, len(data)+1))
|
|
|
|
test.ErrorIs(t, err, io.EOF)
|
|
|
|
test.EqOp(t, len(data), int(n))
|
|
|
|
}))
|
2024-09-08 15:17:29 +00:00
|
|
|
t.Run("At sets cursor position", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
data := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "data")
|
|
|
|
pos := rapid.Uint64Range(0, uint64(len(data)-1)).Draw(t, "pos")
|
|
|
|
c := makeCursor(data).At(pos)
|
|
|
|
|
|
|
|
dst := make([]byte, 1)
|
|
|
|
n, _, err := c.Read(dst)
|
|
|
|
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")
|
|
|
|
c := makeCursor(data).At(pos)
|
|
|
|
test.EqOp(t, pos, c.Pos())
|
|
|
|
}))
|
|
|
|
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")
|
|
|
|
c := makeCursor(data)
|
|
|
|
|
|
|
|
_, next, err := c.Read(make([]byte, skip))
|
|
|
|
must.NoError(t, err)
|
|
|
|
test.EqOp(t, skip, next.Pos())
|
|
|
|
}))
|
2024-09-03 19:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestSliceCursor(t *testing.T) {
|
|
|
|
testCursor(t, cursor.NewSlice[byte])
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestReaderAtCursor(t *testing.T) {
|
|
|
|
testCursor(t, func(b []byte) cursor.ReaderAtCursor {
|
|
|
|
return cursor.NewReaderAt(bytes.NewReader(b))
|
|
|
|
})
|
2024-09-13 17:17:32 +00:00
|
|
|
t.Run("Read returns an error if the ReaderAt fails", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
expectedErr := generator.Error().Draw(t, "expectedErr")
|
|
|
|
startPos := rapid.Uint64().Draw(t, "startPos")
|
|
|
|
dst := SliceOfNZero[byte](0, 100).Draw(t, "dst")
|
|
|
|
c := cursor.NewReaderAt(ptest.ErrReaderAt(expectedErr)).At(startPos)
|
|
|
|
n, next, err := c.Read(dst)
|
|
|
|
test.ErrorIs(t, err, expectedErr)
|
|
|
|
test.EqOp(t, startPos, next.Pos())
|
|
|
|
test.Zero(t, n)
|
|
|
|
}))
|
2024-09-03 19:55:32 +00:00
|
|
|
}
|