github.com/anchore/syft@v1.38.2/syft/format/internal/stream/seekable_reader_test.go (about)

     1  package stream
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/require"
    10  )
    11  
    12  func TestSeekableReader(t *testing.T) {
    13  	tests := []struct {
    14  		name    string
    15  		input   io.Reader
    16  		assert  func(io.Reader, io.ReadSeeker)
    17  		wantErr require.ErrorAssertionFunc
    18  	}{
    19  		{
    20  			name:    "nil reader",
    21  			input:   nil,
    22  			wantErr: require.Error,
    23  		},
    24  		{
    25  			name:  "empty reader",
    26  			input: bytes.NewBuffer([]byte{}), // does not implement io.Seeker (but does implement io.Reader)
    27  			assert: func(input io.Reader, got io.ReadSeeker) {
    28  				impl, ok := got.(*bytes.Reader) // contents are copied to a byte slice, accessed via bytes.Reader
    29  				require.True(t, ok)
    30  				_, err := impl.Seek(0, io.SeekStart)
    31  				require.NoError(t, err)
    32  				content, err := io.ReadAll(impl)
    33  				require.NoError(t, err)
    34  				require.Equal(t, []byte{}, content)
    35  			},
    36  		},
    37  		{
    38  			name:  "empty read seeker",
    39  			input: strings.NewReader(""), // implements io.ReadSeeker, not offset
    40  			assert: func(input io.Reader, got io.ReadSeeker) {
    41  				_, ok := got.(*strings.Reader) // same ReadSeeker is returned when not offset
    42  				require.True(t, ok)
    43  				_, err := got.Seek(0, io.SeekStart)
    44  				require.NoError(t, err)
    45  				content, err := io.ReadAll(got)
    46  				require.NoError(t, err)
    47  				require.Equal(t, []byte{}, content)
    48  			},
    49  		},
    50  		{
    51  			name:  "non-empty read seeker",
    52  			input: strings.NewReader("hello world!"), // implements io.ReadSeeker, not offset
    53  			assert: func(input io.Reader, got io.ReadSeeker) {
    54  				_, ok := got.(*strings.Reader) // same ReadSeeker is returned when not offset
    55  				require.True(t, ok)
    56  				_, err := got.Seek(0, io.SeekStart)
    57  				require.NoError(t, err)
    58  				content, err := io.ReadAll(got)
    59  				require.NoError(t, err)
    60  				require.Equal(t, []byte("hello world!"), content)
    61  			},
    62  		},
    63  		{
    64  			name:  "non-empty reader",
    65  			input: bytes.NewBufferString("hello world!"), // does not implement io.Seeker (but does implement io.Reader)
    66  			assert: func(input io.Reader, got io.ReadSeeker) {
    67  				impl, ok := got.(*bytes.Reader)
    68  				require.True(t, ok)
    69  				_, err := impl.Seek(0, io.SeekStart)
    70  				require.NoError(t, err)
    71  				content, err := io.ReadAll(impl)
    72  				require.NoError(t, err)
    73  				require.Equal(t, []byte("hello world!"), content)
    74  			},
    75  		},
    76  		{
    77  			name:  "position zero read seeker",
    78  			input: strings.NewReader("a string reader"), // implements io.ReadSeeker at position 0
    79  			assert: func(input io.Reader, got io.ReadSeeker) {
    80  				_, ok := got.(*strings.Reader) // returns the same ReadSeeker
    81  				require.True(t, ok)
    82  				_, err := got.Seek(0, io.SeekStart)
    83  				require.NoError(t, err)
    84  				content, err := io.ReadAll(got)
    85  				require.NoError(t, err)
    86  				require.Equal(t, []byte("a string reader"), content)
    87  			},
    88  		},
    89  		{
    90  			name:  "offset read seeker",
    91  			input: moveOffset(t, bytes.NewReader([]byte{1, 2, 3, 4, 5}), 3), // implements io.ReadSeeker, with an offset
    92  			assert: func(input io.Reader, got io.ReadSeeker) {
    93  				_, ok := got.(*offsetReadSeeker) // returns an offset-tracking ReadSeeker
    94  				require.True(t, ok)
    95  				_, err := got.Seek(0, io.SeekStart)
    96  				require.NoError(t, err)
    97  				content, err := io.ReadAll(got)
    98  				require.NoError(t, err)
    99  				require.Equal(t, []byte{4, 5}, content)
   100  			},
   101  		},
   102  	}
   103  	for _, tt := range tests {
   104  		t.Run(tt.name, func(t *testing.T) {
   105  			if tt.wantErr == nil {
   106  				tt.wantErr = require.NoError
   107  			}
   108  			got, err := SeekableReader(tt.input)
   109  			tt.wantErr(t, err)
   110  			if err != nil {
   111  				return
   112  			}
   113  			tt.assert(tt.input, got)
   114  		})
   115  	}
   116  }
   117  
   118  func Test_offsetReadSeeker(t *testing.T) {
   119  	abcd1234 := func() io.ReadSeeker { return strings.NewReader("abcd1234") }
   120  	abcd1234offset := func(offset int) func() io.ReadSeeker {
   121  		return func() io.ReadSeeker {
   122  			r := strings.NewReader("abcd1234")
   123  			_, err := r.Seek(int64(offset), io.SeekStart)
   124  			require.NoError(t, err)
   125  			return r
   126  		}
   127  	}
   128  
   129  	tests := []struct {
   130  		name     string
   131  		input    func() io.ReadSeeker
   132  		seek     int64
   133  		seek2    int64
   134  		whence   int
   135  		expected string
   136  		wantErr  require.ErrorAssertionFunc
   137  	}{
   138  		{
   139  			name:     "basic reader",
   140  			input:    abcd1234,
   141  			seek:     0,
   142  			whence:   io.SeekStart,
   143  			expected: "abcd1234",
   144  		},
   145  		{
   146  			name:     "basic reader offset",
   147  			input:    abcd1234offset(1),
   148  			seek:     0,
   149  			whence:   io.SeekStart,
   150  			expected: "bcd1234",
   151  		},
   152  		{
   153  			name:     "basic reader offset both",
   154  			input:    abcd1234offset(2),
   155  			seek:     2,
   156  			whence:   io.SeekStart,
   157  			expected: "1234",
   158  		},
   159  		{
   160  			name:    "basic reader offset seek current",
   161  			input:   abcd1234offset(1),
   162  			seek:    -1,
   163  			whence:  io.SeekCurrent,
   164  			wantErr: require.Error, // would be < current, which is an error
   165  		},
   166  		{
   167  			name:     "valid negative offset from current",
   168  			input:    abcd1234offset(1),
   169  			seek:     2,
   170  			seek2:    -1,
   171  			whence:   io.SeekCurrent,
   172  			expected: "cd1234",
   173  		},
   174  		{
   175  			name:     "basic reader offset multiple",
   176  			input:    abcd1234offset(2),
   177  			seek:     3,
   178  			seek2:    2,
   179  			whence:   io.SeekCurrent,
   180  			expected: "4",
   181  		},
   182  		{
   183  			name:    "bad whence",
   184  			input:   abcd1234,
   185  			seek:    1,
   186  			whence:  io.SeekEnd,
   187  			wantErr: require.Error,
   188  		},
   189  	}
   190  
   191  	for _, tt := range tests {
   192  		t.Run(tt.name, func(t *testing.T) {
   193  			rdr := tt.input()
   194  
   195  			off, err := rdr.Seek(0, io.SeekCurrent)
   196  			require.NoError(t, err)
   197  
   198  			// construct new offsetReadSeeker
   199  			sr := offsetReadSeeker{rdr: rdr, offset: off}
   200  
   201  			_, err = sr.Seek(tt.seek, tt.whence)
   202  			if tt.seek2 != 0 {
   203  				require.NoError(t, err)
   204  				_, err = sr.Seek(tt.seek2, tt.whence)
   205  			}
   206  			if tt.wantErr != nil {
   207  				tt.wantErr(t, err)
   208  				return
   209  			} else {
   210  				require.NoError(t, err)
   211  			}
   212  
   213  			buf := make([]byte, 1024)
   214  			n, err := sr.Read(buf)
   215  			require.NoError(t, err)
   216  			require.Equal(t, tt.expected, string(buf[:n]))
   217  		})
   218  	}
   219  }
   220  
   221  func moveOffset(t *testing.T, reader io.ReadSeeker, offset int64) io.Reader {
   222  	pos, err := reader.Seek(offset, io.SeekStart)
   223  	require.NoError(t, err)
   224  	require.Equal(t, offset, pos)
   225  	return reader
   226  }