Files
syft/syft/format/internal/stream/seekable_reader_test.go
T
2025-05-12 13:33:24 -04:00

227 lines
5.8 KiB
Go

package stream
import (
"bytes"
"io"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestSeekableReader(t *testing.T) {
tests := []struct {
name string
input io.Reader
assert func(io.Reader, io.ReadSeeker)
wantErr require.ErrorAssertionFunc
}{
{
name: "nil reader",
input: nil,
wantErr: require.Error,
},
{
name: "empty reader",
input: bytes.NewBuffer([]byte{}), // does not implement io.Seeker (but does implement io.Reader)
assert: func(input io.Reader, got io.ReadSeeker) {
impl, ok := got.(*bytes.Reader) // contents are copied to a byte slice, accessed via bytes.Reader
require.True(t, ok)
_, err := impl.Seek(0, io.SeekStart)
require.NoError(t, err)
content, err := io.ReadAll(impl)
require.NoError(t, err)
require.Equal(t, []byte{}, content)
},
},
{
name: "empty read seeker",
input: strings.NewReader(""), // implements io.ReadSeeker, not offset
assert: func(input io.Reader, got io.ReadSeeker) {
_, ok := got.(*strings.Reader) // same ReadSeeker is returned when not offset
require.True(t, ok)
_, err := got.Seek(0, io.SeekStart)
require.NoError(t, err)
content, err := io.ReadAll(got)
require.NoError(t, err)
require.Equal(t, []byte{}, content)
},
},
{
name: "non-empty read seeker",
input: strings.NewReader("hello world!"), // implements io.ReadSeeker, not offset
assert: func(input io.Reader, got io.ReadSeeker) {
_, ok := got.(*strings.Reader) // same ReadSeeker is returned when not offset
require.True(t, ok)
_, err := got.Seek(0, io.SeekStart)
require.NoError(t, err)
content, err := io.ReadAll(got)
require.NoError(t, err)
require.Equal(t, []byte("hello world!"), content)
},
},
{
name: "non-empty reader",
input: bytes.NewBufferString("hello world!"), // does not implement io.Seeker (but does implement io.Reader)
assert: func(input io.Reader, got io.ReadSeeker) {
impl, ok := got.(*bytes.Reader)
require.True(t, ok)
_, err := impl.Seek(0, io.SeekStart)
require.NoError(t, err)
content, err := io.ReadAll(impl)
require.NoError(t, err)
require.Equal(t, []byte("hello world!"), content)
},
},
{
name: "position zero read seeker",
input: strings.NewReader("a string reader"), // implements io.ReadSeeker at position 0
assert: func(input io.Reader, got io.ReadSeeker) {
_, ok := got.(*strings.Reader) // returns the same ReadSeeker
require.True(t, ok)
_, err := got.Seek(0, io.SeekStart)
require.NoError(t, err)
content, err := io.ReadAll(got)
require.NoError(t, err)
require.Equal(t, []byte("a string reader"), content)
},
},
{
name: "offset read seeker",
input: moveOffset(t, bytes.NewReader([]byte{1, 2, 3, 4, 5}), 3), // implements io.ReadSeeker, with an offset
assert: func(input io.Reader, got io.ReadSeeker) {
_, ok := got.(*offsetReadSeeker) // returns an offset-tracking ReadSeeker
require.True(t, ok)
_, err := got.Seek(0, io.SeekStart)
require.NoError(t, err)
content, err := io.ReadAll(got)
require.NoError(t, err)
require.Equal(t, []byte{4, 5}, content)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = require.NoError
}
got, err := SeekableReader(tt.input)
tt.wantErr(t, err)
if err != nil {
return
}
tt.assert(tt.input, got)
})
}
}
func Test_offsetReadSeeker(t *testing.T) {
abcd1234 := func() io.ReadSeeker { return strings.NewReader("abcd1234") }
abcd1234offset := func(offset int) func() io.ReadSeeker {
return func() io.ReadSeeker {
r := strings.NewReader("abcd1234")
_, err := r.Seek(int64(offset), io.SeekStart)
require.NoError(t, err)
return r
}
}
tests := []struct {
name string
input func() io.ReadSeeker
seek int64
seek2 int64
whence int
expected string
wantErr require.ErrorAssertionFunc
}{
{
name: "basic reader",
input: abcd1234,
seek: 0,
whence: io.SeekStart,
expected: "abcd1234",
},
{
name: "basic reader offset",
input: abcd1234offset(1),
seek: 0,
whence: io.SeekStart,
expected: "bcd1234",
},
{
name: "basic reader offset both",
input: abcd1234offset(2),
seek: 2,
whence: io.SeekStart,
expected: "1234",
},
{
name: "basic reader offset seek current",
input: abcd1234offset(1),
seek: -1,
whence: io.SeekCurrent,
wantErr: require.Error, // would be < current, which is an error
},
{
name: "valid negative offset from current",
input: abcd1234offset(1),
seek: 2,
seek2: -1,
whence: io.SeekCurrent,
expected: "cd1234",
},
{
name: "basic reader offset multiple",
input: abcd1234offset(2),
seek: 3,
seek2: 2,
whence: io.SeekCurrent,
expected: "4",
},
{
name: "bad whence",
input: abcd1234,
seek: 1,
whence: io.SeekEnd,
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rdr := tt.input()
off, err := rdr.Seek(0, io.SeekCurrent)
require.NoError(t, err)
// construct new offsetReadSeeker
sr := offsetReadSeeker{rdr: rdr, offset: off}
_, err = sr.Seek(tt.seek, tt.whence)
if tt.seek2 != 0 {
require.NoError(t, err)
_, err = sr.Seek(tt.seek2, tt.whence)
}
if tt.wantErr != nil {
tt.wantErr(t, err)
return
} else {
require.NoError(t, err)
}
buf := make([]byte, 1024)
n, err := sr.Read(buf)
require.NoError(t, err)
require.Equal(t, tt.expected, string(buf[:n]))
})
}
}
func moveOffset(t *testing.T, reader io.ReadSeeker, offset int64) io.Reader {
pos, err := reader.Seek(offset, io.SeekStart)
require.NoError(t, err)
require.Equal(t, offset, pos)
return reader
}