package packrev_test
import (
"bytes"
"encoding/binary"
"errors"
"os"
"testing"
"lindenii.org/go/furgit/internal/format/packidx"
"lindenii.org/go/furgit/internal/format/packrev"
"lindenii.org/go/furgit/object/id"
)
func TestParseGitReverseIndex(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
t.Run(objectFormat.String(), func(t *testing.T) {
t.Parallel()
prefix := makeGitPack(t, objectFormat)
revData, err := os.ReadFile(prefix + ".rev") //nolint:gosec
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
rev, err := packrev.Parse(revData, objectFormat)
if err != nil {
t.Fatalf("Parse: %v", err)
}
idxData, err := os.ReadFile(prefix + ".idx") //nolint:gosec
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
idx, err := packidx.Parse(idxData, objectFormat.Size())
if err != nil {
t.Fatalf("packidx.Parse: %v", err)
}
if rev.NumObjects() != idx.NumObjects() {
t.Fatalf("NumObjects = %d, want %d", rev.NumObjects(), idx.NumObjects())
}
packData, err := os.ReadFile(prefix + ".pack") //nolint:gosec
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
packTrailer := packData[len(packData)-objectFormat.Size():]
if !bytes.Equal(rev.PackHash(), packTrailer) {
t.Fatalf("PackHash does not match pack trailer")
}
want := packOrderPositions(t, &idx)
for packOrder, wantPosition := range want {
position, err := rev.PositionAt(packOrder)
if err != nil {
t.Fatalf("PositionAt(%d): %v", packOrder, err)
}
if position != int(wantPosition) {
t.Fatalf("PositionAt(%d) = %d, want %d", packOrder, position, wantPosition)
}
}
})
}
}
func TestParseMalformed(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
t.Run(objectFormat.String(), func(t *testing.T) {
t.Parallel()
valid := writeSyntheticRev(t, objectFormat, []uint32{2, 0, 1, 3})
corrupt := func(mutate func(data []byte) []byte) []byte {
return mutate(bytes.Clone(valid))
}
cases := []struct {
name string
data []byte
}{
{name: "empty", data: []byte{}},
{name: "truncated", data: corrupt(func(d []byte) []byte { return d[:10] })},
{
name: "bad signature",
data: corrupt(func(d []byte) []byte {
d[0] ^= 0xff
return d
}),
},
{
name: "bad version",
data: corrupt(func(d []byte) []byte {
d[7] = 2
return d
}),
},
{
name: "hash function mismatch",
data: corrupt(func(d []byte) []byte {
d[11] ^= 0xff
return d
}),
},
{
name: "position table size not 32-bit multiple",
data: corrupt(func(d []byte) []byte { return append(d, 0xde, 0xad) }),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
_, err := packrev.Parse(tc.data, objectFormat)
if !errors.Is(err, packrev.ErrMalformedReverseIndex) {
t.Fatalf("Parse error = %v, want ErrMalformedReverseIndex", err)
}
})
}
})
}
}
func TestPositionAtMalformed(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
t.Run(objectFormat.String(), func(t *testing.T) {
t.Parallel()
data := writeSyntheticRev(t, objectFormat, []uint32{2, 0, 1, 3})
// Corrupt the first stored position to one past the object count.
binary.BigEndian.PutUint32(data[12:], 4)
rev, err := packrev.Parse(data, objectFormat)
if err != nil {
t.Fatalf("Parse: %v", err)
}
_, err = rev.PositionAt(0)
if !errors.Is(err, packrev.ErrMalformedReverseIndex) {
t.Fatalf("PositionAt error = %v, want ErrMalformedReverseIndex", err)
}
})
}
}