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
|
||||
// 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.
|
||||
// If n < len(dst), Read will return an error explaining why it read fewer
|
||||
// bytes than requested. If Read tried to read past the end of the source,
|
||||
// If n < len(dst) or if the cursor's position is at the end of the data 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.
|
||||
Read(dst []Datum) (n uint64, next Cursor[Datum], err error)
|
||||
|
||||
@ -31,9 +32,17 @@ type SliceCursor[Datum any] struct {
|
||||
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) {
|
||||
if sc.offset == uint64(len(sc.data)) {
|
||||
return 0, sc, io.EOF
|
||||
}
|
||||
copied := copy(dst, sc.data[sc.offset:])
|
||||
if copied < len(dst) {
|
||||
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) {
|
||||
if sc.offset == uint64(len(sc.source)) {
|
||||
return 0, sc, io.EOF
|
||||
}
|
||||
copied := copy(dst, sc.source[sc.offset:])
|
||||
if copied < len(dst) {
|
||||
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.
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// with the regexp package.
|
||||
|
9
go.mod
9
go.mod
@ -1,3 +1,12 @@
|
||||
module git.codemonkeysoftware.net/b/gigaparsec
|
||||
|
||||
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