Added cursor tests
This commit is contained in:
parent
b06454a4bc
commit
49bfcb7462
@ -17,8 +17,9 @@ type Cursor[Datum any] interface {
|
|||||||
// source. It returns the number of data it read and a new Cursor for
|
// source. It returns the number of data it read and a new Cursor for
|
||||||
// the position at which the read ended, or an error if the read failed.
|
// the position at which the read ended, or an error if the read failed.
|
||||||
// All calls to a given Cursor will return data from the same position.
|
// All calls to a given Cursor will return data from the same position.
|
||||||
// If n < len(dst), Read will return an error explaining why it read fewer
|
// If n < len(dst) or if the cursor's position is at the end of the data source,
|
||||||
// bytes than requested. If Read tried to read past the end of the source,
|
// Read will return an error explaining why it read fewer bytes than requested.
|
||||||
|
// If the error was due to the cursor reaching the end of the data source,
|
||||||
// err will be io.EOF.
|
// err will be io.EOF.
|
||||||
Read(dst []Datum) (n uint64, next Cursor[Datum], err error)
|
Read(dst []Datum) (n uint64, next Cursor[Datum], err error)
|
||||||
|
|
||||||
@ -31,9 +32,17 @@ type SliceCursor[Datum any] struct {
|
|||||||
offset uint64
|
offset uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSlice[Datum any]([]Datum) SliceCursor[Datum] { panic("not implemented") }
|
func NewSlice[Datum any](data []Datum) SliceCursor[Datum] {
|
||||||
|
return SliceCursor[Datum]{
|
||||||
|
data: data,
|
||||||
|
offset: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (sc SliceCursor[Datum]) Read(dst []Datum) (n uint64, next Cursor[Datum], err error) {
|
func (sc SliceCursor[Datum]) Read(dst []Datum) (n uint64, next Cursor[Datum], err error) {
|
||||||
|
if sc.offset == uint64(len(sc.data)) {
|
||||||
|
return 0, sc, io.EOF
|
||||||
|
}
|
||||||
copied := copy(dst, sc.data[sc.offset:])
|
copied := copy(dst, sc.data[sc.offset:])
|
||||||
if copied < len(dst) {
|
if copied < len(dst) {
|
||||||
err = io.EOF
|
err = io.EOF
|
||||||
@ -81,6 +90,9 @@ func NewString(s string) StringCursor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sc StringCursor) Read(dst []byte) (n uint64, next Cursor[byte], err error) {
|
func (sc StringCursor) Read(dst []byte) (n uint64, next Cursor[byte], err error) {
|
||||||
|
if sc.offset == uint64(len(sc.source)) {
|
||||||
|
return 0, sc, io.EOF
|
||||||
|
}
|
||||||
copied := copy(dst, sc.source[sc.offset:])
|
copied := copy(dst, sc.source[sc.offset:])
|
||||||
if copied < len(dst) {
|
if copied < len(dst) {
|
||||||
err = io.EOF
|
err = io.EOF
|
||||||
|
92
cursor/cursor_test.go
Normal file
92
cursor/cursor_test.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package cursor_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.codemonkeysoftware.net/b/gigaparsec/cursor"
|
||||||
|
"github.com/shoenig/test"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
|
"pgregory.net/rapid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Todo(t *testing.T) {
|
||||||
|
t.Errorf("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
skip := rapid.IntRange(0, len(data)-1).Draw(t, "data")
|
||||||
|
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))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSliceCursor(t *testing.T) {
|
||||||
|
testCursor(t, cursor.NewSlice[byte])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringCursor(t *testing.T) {
|
||||||
|
testCursor(t, func(b []byte) cursor.StringCursor {
|
||||||
|
return cursor.NewString(string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaderAtCursor(t *testing.T) {
|
||||||
|
testCursor(t, func(b []byte) cursor.ReaderAtCursor {
|
||||||
|
return cursor.NewReaderAt(bytes.NewReader(b))
|
||||||
|
})
|
||||||
|
}
|
@ -6,7 +6,9 @@ import "io"
|
|||||||
// with limited backward seeking.
|
// with limited backward seeking.
|
||||||
type BufferedReaderAt struct{}
|
type BufferedReaderAt struct{}
|
||||||
|
|
||||||
func NewBufferedReaderAt(r io.Reader, minBuffer uint64) *BufferedReaderAt
|
func NewBufferedReaderAt(r io.Reader, minBuffer uint64) *BufferedReaderAt {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ReadAt reads bytes from the underlying reader. If the offset is after
|
// ReadAt reads bytes from the underlying reader. If the offset is after
|
||||||
// the end of the buffer, ReadAt will first read and ignore bytes from the
|
// the end of the buffer, ReadAt will first read and ignore bytes from the
|
||||||
@ -15,7 +17,9 @@ func NewBufferedReaderAt(r io.Reader, minBuffer uint64) *BufferedReaderAt
|
|||||||
//
|
//
|
||||||
// If your parser needs unlimited lookahead, you should probably
|
// If your parser needs unlimited lookahead, you should probably
|
||||||
// just read the whole input into a slice and use BytesCursor.
|
// just read the whole input into a slice and use BytesCursor.
|
||||||
func (b *BufferedReaderAt) ReadAt(dst []byte, offset int64) (int, error)
|
func (b *BufferedReaderAt) ReadAt(dst []byte, offset int64) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RuneReader is an io.RuneReader backed by a Cursor, for compatibility
|
// RuneReader is an io.RuneReader backed by a Cursor, for compatibility
|
||||||
// with the regexp package.
|
// with the regexp package.
|
||||||
|
9
go.mod
9
go.mod
@ -1,3 +1,12 @@
|
|||||||
module git.codemonkeysoftware.net/b/gigaparsec
|
module git.codemonkeysoftware.net/b/gigaparsec
|
||||||
|
|
||||||
go 1.23
|
go 1.23
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/shoenig/test v1.9.1
|
||||||
|
pgregory.net/rapid v1.1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
|
||||||
|
replace github.com/shoenig/test v1.9.1 => ../shoenig-test
|
||||||
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
|
||||||
|
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
Loading…
Reference in New Issue
Block a user