aboutsummaryrefslogtreecommitdiff
path: root/internal/format
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-06-11 14:23:49 +0000
committerGravatar Runxi Yu2026-06-11 14:23:49 +0000
commit454a3f8e491f84ce19aab0d3873062f5ccc2330e (patch)
tree45b9efb74544e69a7a30d833ed6930cce345b516 /internal/format
parentinternal/format/packidx: Add test helpers (diff)
internal/format/packidx: Add pack index parse tests
Diffstat (limited to 'internal/format')
-rw-r--r--internal/format/packidx/packidx_test.go124
1 files changed, 124 insertions, 0 deletions
diff --git a/internal/format/packidx/packidx_test.go b/internal/format/packidx/packidx_test.go
new file mode 100644
index 00000000..d225a993
--- /dev/null
+++ b/internal/format/packidx/packidx_test.go
@@ -0,0 +1,124 @@
+package packidx_test
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "os"
+ "testing"
+
+ "lindenii.org/go/furgit/internal/format/packidx"
+ "lindenii.org/go/furgit/object/id"
+)
+
+func TestParseGitIndex(t *testing.T) {
+ t.Parallel()
+
+ for _, objectFormat := range id.SupportedObjectFormats() {
+ t.Run(objectFormat.String(), func(t *testing.T) {
+ t.Parallel()
+
+ _, prefix, oids := makeGitPack(t, objectFormat)
+ _, idx := parseGitIdxFile(t, prefix, objectFormat)
+
+ if idx.NumObjects() != len(oids) {
+ t.Fatalf("NumObjects = %d, want %d", idx.NumObjects(), len(oids))
+ }
+
+ for pos := 1; pos < idx.NumObjects(); pos++ {
+ if bytes.Compare(idx.OIDAt(pos-1), idx.OIDAt(pos)) >= 0 {
+ t.Fatalf("OIDAt(%d) not sorted after predecessor", pos)
+ }
+ }
+
+ 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(idx.PackHash(), packTrailer) {
+ t.Fatalf("PackHash does not match pack trailer")
+ }
+ })
+ }
+}
+
+func TestParseMalformed(t *testing.T) {
+ t.Parallel()
+
+ for _, objectFormat := range id.SupportedObjectFormats() {
+ t.Run(objectFormat.String(), func(t *testing.T) {
+ t.Parallel()
+
+ hashSize := objectFormat.Size()
+
+ valid := writeSyntheticIndex(t, objectFormat, syntheticEntries(8))
+
+ 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[:20] })},
+ {
+ 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] = 3
+
+ return d
+ }),
+ },
+ {
+ name: "non-monotonic fanout",
+ data: corrupt(func(d []byte) []byte {
+ binary.BigEndian.PutUint32(d[8:], 0xffffffff)
+
+ return d
+ }),
+ },
+ {
+ name: "tables exceed index size",
+ data: corrupt(func(d []byte) []byte {
+ binary.BigEndian.PutUint32(d[8+255*4:], 0x00ffffff)
+
+ return d
+ }),
+ },
+ {
+ name: "trailing size not 64-bit multiple",
+ data: corrupt(func(d []byte) []byte { return append(d, 0xde, 0xad, 0xbe, 0xef) }),
+ },
+ {
+ name: "more 64-bit offsets than objects",
+ data: corrupt(func(d []byte) []byte {
+ return append(d, bytes.Repeat([]byte{0x00}, 9*8)...)
+ }),
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ _, err := packidx.Parse(tc.data, hashSize)
+ if !errors.Is(err, packidx.ErrMalformedPackIndex) {
+ t.Fatalf("Parse error = %v, want ErrMalformedPackIndex", err)
+ }
+ })
+ }
+ })
+ }
+}