Files
trufflehog/pkg/iobuf/bufferedreaderseeker_test.go

481 lines
12 KiB
Go

package iobuf
import (
"bytes"
"errors"
"io"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBufferedReaderSeekerRead(t *testing.T) {
tests := []struct {
name string
reader io.Reader
reads []int
expectedReads []int
expectedBytes [][]byte
expectedBytesRead int64
expectedIndex int64
expectedBuffer []byte
expectedError error
}{
{
name: "read from seekable reader",
reader: strings.NewReader("test data"),
reads: []int{4},
expectedReads: []int{4},
expectedBytes: [][]byte{[]byte("test")},
expectedBytesRead: 4,
expectedIndex: 4,
},
{
name: "read from non-seekable reader with buffering",
reader: bytes.NewBufferString("test data"),
reads: []int{4},
expectedReads: []int{4},
expectedBytes: [][]byte{[]byte("test")},
expectedBytesRead: 4,
expectedIndex: 4,
expectedBuffer: []byte("test"),
},
{
name: "read from non-seekable reader without buffering",
reader: bytes.NewBufferString("test data"),
reads: []int{4},
expectedReads: []int{4},
expectedBytes: [][]byte{[]byte("test")},
expectedBytesRead: 4,
expectedIndex: 4,
},
{
name: "read beyond buffer",
reader: strings.NewReader("test data"),
reads: []int{10},
expectedReads: []int{9},
expectedBytes: [][]byte{[]byte("test data")},
expectedBytesRead: 9,
expectedIndex: 9,
},
{
name: "read with empty reader",
reader: strings.NewReader(""),
reads: []int{4},
expectedReads: []int{0},
expectedBytes: [][]byte{[]byte("")},
expectedBytesRead: 0,
expectedIndex: 0,
expectedError: io.EOF,
},
{
name: "read exact buffer size",
reader: strings.NewReader("test"),
reads: []int{4},
expectedReads: []int{4},
expectedBytes: [][]byte{[]byte("test")},
expectedBytesRead: 4,
expectedIndex: 4,
},
{
name: "read less than buffer size",
reader: strings.NewReader("te"),
reads: []int{4},
expectedReads: []int{2},
expectedBytes: [][]byte{[]byte("te")},
expectedBytesRead: 2,
expectedIndex: 2,
},
{
name: "read more than buffer size without buffering",
reader: bytes.NewBufferString("test data"),
reads: []int{4},
expectedReads: []int{4},
expectedBytes: [][]byte{[]byte("test")},
expectedBytesRead: 4,
expectedIndex: 4,
},
{
name: "multiple reads with buffering",
reader: bytes.NewBufferString("test data"),
reads: []int{4, 5},
expectedReads: []int{4, 5},
expectedBytes: [][]byte{[]byte("test"), []byte(" data")},
expectedBytesRead: 9,
expectedIndex: 9,
expectedBuffer: []byte("test data"),
},
{
name: "multiple reads without buffering",
reader: bytes.NewBufferString("test data"),
reads: []int{4, 5},
expectedReads: []int{4, 5},
expectedBytes: [][]byte{[]byte("test"), []byte(" data")},
expectedBytesRead: 9,
expectedIndex: 9,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
brs := NewBufferedReaderSeeker(tt.reader)
for i, readSize := range tt.reads {
buf := make([]byte, readSize)
n, err := brs.Read(buf)
assert.Equal(t, tt.expectedReads[i], n, "read %d: unexpected number of bytes read", i+1)
assert.Equal(t, tt.expectedBytes[i], buf[:n], "read %d: unexpected bytes", i+1)
if i == len(tt.reads)-1 {
if tt.expectedError != nil {
assert.ErrorIs(t, err, tt.expectedError)
} else {
assert.NoError(t, err)
}
}
}
assert.Equal(t, tt.expectedBytesRead, brs.bytesRead)
if brs.seeker == nil {
assert.Equal(t, tt.expectedIndex, brs.index)
}
if brs.buf != nil && len(tt.expectedBuffer) > 0 {
assert.Equal(t, tt.expectedBuffer, brs.buf.Bytes())
} else {
assert.Nil(t, tt.expectedBuffer)
}
})
}
}
func TestBufferedReaderSeekerSeek(t *testing.T) {
tests := []struct {
name string
reader io.Reader
offset int64
whence int
expectedPos int64
expectedErr bool
expectedRead []byte
}{
{
name: "seek on seekable reader with SeekStart",
reader: strings.NewReader("test data"),
offset: 4,
whence: io.SeekStart,
expectedPos: 4,
expectedErr: false,
expectedRead: []byte(" dat"),
},
{
name: "seek on seekable reader with SeekCurrent",
reader: strings.NewReader("test data"),
offset: 4,
whence: io.SeekCurrent,
expectedPos: 4,
expectedErr: false,
expectedRead: []byte(" dat"),
},
{
name: "seek on seekable reader with SeekEnd",
reader: strings.NewReader("test data"),
offset: -4,
whence: io.SeekEnd,
expectedPos: 5,
expectedErr: false,
expectedRead: []byte("data"),
},
{
name: "seek on non-seekable reader with SeekStart",
reader: bytes.NewBufferString("test data"),
offset: 4,
whence: io.SeekStart,
expectedPos: 4,
expectedErr: false,
expectedRead: []byte{},
},
{
name: "seek on non-seekable reader with SeekCurrent",
reader: bytes.NewBufferString("test data"),
offset: 4,
whence: io.SeekCurrent,
expectedPos: 4,
expectedErr: false,
expectedRead: []byte{},
},
{
name: "seek on non-seekable reader with SeekEnd",
reader: bytes.NewBufferString("test data"),
offset: -4,
whence: io.SeekEnd,
expectedPos: 5,
expectedErr: false,
expectedRead: []byte{},
},
{
name: "seek to negative position",
reader: strings.NewReader("test data"),
offset: -1,
whence: io.SeekStart,
expectedPos: 0,
expectedErr: true,
expectedRead: nil,
},
{
name: "seek beyond EOF on non-seekable reader",
reader: bytes.NewBufferString("test data"),
offset: 20,
whence: io.SeekEnd,
expectedPos: 29,
expectedErr: false,
expectedRead: []byte{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
brs := NewBufferedReaderSeeker(tt.reader)
pos, err := brs.Seek(tt.offset, tt.whence)
if tt.expectedErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.expectedPos, pos)
if len(tt.expectedRead) > 0 {
buf := make([]byte, len(tt.expectedRead))
nn, err := brs.Read(buf)
assert.NoError(t, err)
assert.Equal(t, len(tt.expectedRead), nn)
assert.Equal(t, tt.expectedRead, buf[:nn])
}
})
}
}
func TestBufferedReaderSeekerReadAt(t *testing.T) {
tests := []struct {
name string
reader io.Reader
offset int64
length int
expectedN int
expectErr bool
expectedOut []byte
}{
{
name: "read within buffer on seekable reader",
reader: strings.NewReader("test data"),
offset: 5,
length: 4,
expectedN: 4,
expectedOut: []byte("data"),
},
{
name: "read within buffer on non-seekable reader",
reader: bytes.NewBufferString("test data"),
offset: 5,
length: 4,
expectedN: 4,
expectedOut: []byte("data"),
},
{
name: "read beyond buffer",
reader: strings.NewReader("test data"),
offset: 9,
length: 1,
expectedN: 0,
expectErr: true,
expectedOut: []byte{},
},
{
name: "read at start",
reader: strings.NewReader("test data"),
offset: 0,
length: 4,
expectedN: 4,
expectedOut: []byte("test"),
},
{
name: "read with zero length",
reader: strings.NewReader("test data"),
offset: 0,
length: 0,
expectedN: 0,
expectedOut: []byte{},
},
{
name: "read negative offset",
reader: strings.NewReader("test data"),
offset: -1,
length: 4,
expectedN: 0,
expectErr: true,
expectedOut: []byte{},
},
{
name: "read beyond end on non-seekable reader",
reader: bytes.NewBufferString("test data"),
offset: 20,
length: 4,
expectedN: 0,
expectErr: true,
expectedOut: []byte{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
brs := NewBufferedReaderSeeker(tt.reader)
out := make([]byte, tt.length)
n, err := brs.ReadAt(out, tt.offset)
if tt.expectErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.expectedN, n)
assert.Equal(t, tt.expectedOut, out[:n])
})
}
}
// TestBufferedReadSeekerSize tests the Size method of BufferedReadSeeker.
func TestBufferedReadSeekerSize(t *testing.T) {
tests := []struct {
name string
reader io.Reader
setup func(*BufferedReadSeeker)
expectedSize int64
expectError bool
verifyPosition func(*BufferedReadSeeker, int64)
}{
{
name: "size of seekable reader",
reader: strings.NewReader("Hello, World!"),
expectedSize: 13,
},
{
name: "size of non-seekable reader",
reader: bytes.NewBufferString("Hello, World!"),
expectedSize: 13,
},
{
name: "size of empty seekable reader",
reader: strings.NewReader(""),
expectedSize: 0,
},
{
name: "size of empty non-seekable reader",
reader: bytes.NewBufferString(""),
expectedSize: 0,
},
{
name: "size of non-seekable reader after partial read",
reader: bytes.NewBufferString("Partial read data"),
setup: func(brs *BufferedReadSeeker) {
// Read first 7 bytes ("Partial").
buf := make([]byte, 7)
_, _ = brs.Read(buf)
},
expectedSize: 17, // "Partial read data" is 16 bytes
expectError: false,
verifyPosition: func(brs *BufferedReadSeeker, expectedSize int64) {
// After Size is called, the read position should remain at 7
currentPos, err := brs.Seek(0, io.SeekCurrent)
assert.NoError(t, err)
assert.Equal(t, int64(7), currentPos)
},
},
{
name: "repeated Size calls",
reader: strings.NewReader("Repeated Size Calls Test"),
expectedSize: 24,
expectError: false,
setup: func(brs *BufferedReadSeeker) {
// Call Size multiple times.
size1, err1 := brs.Size()
assert.NoError(t, err1)
assert.Equal(t, int64(24), size1)
size2, err2 := brs.Size()
assert.NoError(t, err2)
assert.Equal(t, int64(24), size2)
},
},
{
name: "size with error during reading",
reader: &errorReader{
data: "Data before error",
errorAfter: 5, // Return error after reading 5 bytes
},
expectedSize: 0,
expectError: true,
},
{
name: "size with limited reader simulating EOF",
reader: io.LimitReader(strings.NewReader("Limited data"), 7),
expectedSize: 7,
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
brs := NewBufferedReaderSeeker(tt.reader)
if tt.setup != nil {
tt.setup(brs)
}
size, err := brs.Size()
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedSize, size)
}
if tt.verifyPosition != nil {
tt.verifyPosition(brs, tt.expectedSize)
}
})
}
}
// errorReader is an io.Reader that returns an error after reading a specified number of bytes.
// It's used to simulate non-EOF errors during read operations.
type errorReader struct {
data string
errorAfter int // Number of bytes to read before returning an error
readBytes int
}
func (er *errorReader) Read(p []byte) (int, error) {
if er.readBytes >= er.errorAfter {
return 0, errors.New("simulated read error")
}
remaining := er.errorAfter - er.readBytes
toRead := len(p)
if toRead > remaining {
toRead = remaining
}
copy(p, er.data[er.readBytes:er.readBytes+toRead])
er.readBytes += toRead
return toRead, nil
}