// SPDX-License-Identifier: Unlicense package cursor_test import ( "bytes" "io" "testing" "git.codemonkeysoftware.net/b/gigaparsec/cursor" ptest "git.codemonkeysoftware.net/b/gigaparsec/test" pgen "git.codemonkeysoftware.net/b/gigaparsec/test/generator" "github.com/shoenig/test" "github.com/shoenig/test/must" "pgregory.net/rapid" ) func Todo(t *testing.T) { t.Errorf("TODO") } 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 := pgen.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 := pgen.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, "skip") 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)) })) 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]) })) 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()) })) } func TestReaderAtCursor(t *testing.T) { testCursor(t, func(b []byte) cursor.ReaderAtCursor[byte] { return cursor.NewReaderAt(bytes.NewReader(b)) }) t.Run("Read returns an error if the ReaderAt fails", rapid.MakeCheck(func(t *rapid.T) { expectedErr := pgen.Error().Draw(t, "expectedErr") startPos := rapid.Uint64().Draw(t, "startPos") dst := pgen.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) })) }