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 }