@@ -3,9 +3,9 @@ package idxfile_test
33import (
44 "bytes"
55 "encoding/base64"
6+ "encoding/binary"
67 "fmt"
78 "io"
8- "os"
99 "testing"
1010
1111 "github.com/go-git/go-git/v5/plumbing"
@@ -137,28 +137,155 @@ func BenchmarkDecode(b *testing.B) {
137137 }
138138}
139139
140- func TestChecksumMismatch (t * testing.T ) {
140+ func TestDecodeErrors (t * testing.T ) {
141141 t .Parallel ()
142142
143- f , err := os .CreateTemp (t .TempDir (), "temp.idx" )
143+ idx := fixtures .Basic ().One ().Idx ()
144+ t .Cleanup (func () { idx .Close () })
145+ validIdx , err := io .ReadAll (idx )
144146 require .NoError (t , err )
145- defer f .Close ()
146147
147- _ , err = io .Copy (f , fixtures .Basic ().One ().Idx ())
148- require .NoError (t , err )
149-
150- _ , err = f .Seek (- 1 , io .SeekEnd )
151- require .NoError (t , err )
152-
153- _ , err = f .Write ([]byte {0 })
154- require .NoError (t , err )
148+ tests := []struct {
149+ name string
150+ input func () []byte
151+ wantErr error
152+ wantErrContains string
153+ }{
154+ {
155+ name : "empty input" ,
156+ input : func () []byte { return nil },
157+ wantErr : io .EOF ,
158+ },
159+ {
160+ name : "wrong magic" ,
161+ input : func () []byte { return []byte {0 , 0 , 0 , 0 , 0 , 0 , 0 , 2 } },
162+ wantErr : ErrMalformedIdxFile ,
163+ },
164+ {
165+ name : "truncated header" ,
166+ input : func () []byte { return []byte {255 , 't' } },
167+ wantErr : io .ErrUnexpectedEOF ,
168+ },
169+ {
170+ name : "unsupported version 1" ,
171+ input : func () []byte {
172+ var buf bytes.Buffer
173+ buf .Write ([]byte {255 , 't' , 'O' , 'c' })
174+ binary .Write (& buf , binary .BigEndian , uint32 (1 ))
175+ return buf .Bytes ()
176+ },
177+ wantErr : ErrUnsupportedVersion ,
178+ wantErrContains : "v1" ,
179+ },
180+ {
181+ name : "unsupported version 3" ,
182+ input : func () []byte {
183+ var buf bytes.Buffer
184+ buf .Write ([]byte {255 , 't' , 'O' , 'c' })
185+ binary .Write (& buf , binary .BigEndian , uint32 (3 ))
186+ return buf .Bytes ()
187+ },
188+ wantErr : ErrUnsupportedVersion ,
189+ wantErrContains : "v3" ,
190+ },
191+ {
192+ name : "truncated fanout table" ,
193+ input : func () []byte {
194+ buf := idxV2Header ()
195+ // Only 10 fanout entries instead of 256.
196+ for range 10 {
197+ buf = binary .BigEndian .AppendUint32 (buf , 0 )
198+ }
199+ return buf
200+ },
201+ wantErr : io .EOF ,
202+ },
203+ {
204+ name : "non-monotonic fanout at entry 1" ,
205+ input : func () []byte {
206+ buf := idxV2Header ()
207+ // entry[0]=5, entry[1]=3 (decrease), rest=5
208+ buf = append (buf , writeFanout (5 , map [int ]uint32 {0 : 5 , 1 : 3 })... )
209+ return buf
210+ },
211+ wantErr : ErrMalformedIdxFile ,
212+ wantErrContains : "not monotonically non-decreasing" ,
213+ },
214+ {
215+ name : "non-monotonic fanout at last entry" ,
216+ input : func () []byte {
217+ buf := idxV2Header ()
218+ // all entries = 10, except entry[255] = 5
219+ buf = append (buf , writeFanout (10 , map [int ]uint32 {255 : 5 })... )
220+ return buf
221+ },
222+ wantErr : ErrMalformedIdxFile ,
223+ wantErrContains : "not monotonically non-decreasing" ,
224+ },
225+ {
226+ name : "truncated object names" ,
227+ input : func () []byte {
228+ buf := idxV2Header ()
229+ // Fanout claims 1 object, but no name data follows.
230+ buf = append (buf , writeFanout (1 , nil )... )
231+ return buf
232+ },
233+ wantErr : io .EOF ,
234+ },
235+ {
236+ name : "checksum mismatch" ,
237+ input : func () []byte {
238+ corrupted := make ([]byte , len (validIdx ))
239+ copy (corrupted , validIdx )
240+ // Flip the last byte of the idx checksum.
241+ corrupted [len (corrupted )- 1 ] ^= 0xff
242+ return corrupted
243+ },
244+ wantErr : ErrMalformedIdxFile ,
245+ wantErrContains : "checksum mismatch" ,
246+ },
247+ }
155248
156- _ , err = f .Seek (0 , io .SeekStart )
157- require .NoError (t , err )
249+ for _ , tt := range tests {
250+ t .Run (tt .name , func (t * testing.T ) {
251+ t .Parallel ()
252+
253+ idx := new (MemoryIndex )
254+ d := NewDecoder (bytes .NewReader (tt .input ()))
255+
256+ err := d .Decode (idx )
257+ require .Error (t , err )
258+ if tt .wantErr != nil {
259+ require .ErrorIs (t , err , tt .wantErr )
260+ }
261+ if tt .wantErrContains != "" {
262+ require .ErrorContains (t , err , tt .wantErrContains )
263+ }
264+ })
265+ }
266+ }
158267
159- idx := new (MemoryIndex )
160- d := NewDecoder (f )
268+ // writeFanout writes a 256-entry fanout table where every entry is set to total,
269+ // except for overrides specified as index→value pairs applied afterwards.
270+ func writeFanout (total uint32 , overrides map [int ]uint32 ) []byte {
271+ var buf bytes.Buffer
272+ entries := [256 ]uint32 {}
273+ for i := range entries {
274+ entries [i ] = total
275+ }
276+ for k , v := range overrides {
277+ entries [k ] = v
278+ }
279+ for _ , v := range entries {
280+ binary .Write (& buf , binary .BigEndian , v )
281+ }
282+ return buf .Bytes ()
283+ }
161284
162- err = d .Decode (idx )
163- require .ErrorContains (t , err , "checksum mismatch" )
285+ // idxV2Header returns the 8-byte idx v2 header (magic + version).
286+ func idxV2Header () []byte {
287+ var buf bytes.Buffer
288+ buf .Write ([]byte {255 , 't' , 'O' , 'c' })
289+ binary .Write (& buf , binary .BigEndian , uint32 (2 ))
290+ return buf .Bytes ()
164291}
0 commit comments