aboutsummaryrefslogtreecommitdiff
path: root/pack_test.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2025-11-16 00:00:00 +0000
committerGravatar Runxi Yu2025-11-16 00:00:00 +0000
commitbad0f9715556a470d0de2a22c7040181e3a033ba (patch)
tree21463072ce5bc85682a887ce0cae26d833941af3 /pack_test.go
parentEntryRecursive should return ErrNotFound instead of nil, nil (diff)
signature
Use actual git for tests and enhance Head
Diffstat (limited to 'pack_test.go')
-rw-r--r--pack_test.go330
1 files changed, 168 insertions, 162 deletions
diff --git a/pack_test.go b/pack_test.go
index 4d6f651f..c23a9d9d 100644
--- a/pack_test.go
+++ b/pack_test.go
@@ -2,215 +2,221 @@ package furgit
import (
"bytes"
- "compress/zlib"
- "encoding/binary"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
"testing"
-
- "git.sr.ht/~runxiyu/furgit/internal/bufpool"
)
-func compressBytes(t *testing.T, payload []byte) []byte {
- var buf bytes.Buffer
- zw := zlib.NewWriter(&buf)
- if _, err := zw.Write(payload); err != nil {
- t.Fatalf("compress write: %v", err)
+func TestPackfileRead(t *testing.T) {
+ repoPath, cleanup := setupTestRepo(t)
+ defer cleanup()
+
+ gitCmd(t, repoPath, "config", "gc.auto", "0")
+
+ workDir, cleanupWork := setupWorkDir(t)
+ defer cleanupWork()
+
+ err := os.WriteFile(filepath.Join(workDir, "file1.txt"), []byte("content1"), 0o644)
+ if err != nil {
+ t.Fatalf("failed to write file1.txt: %v", err)
}
- if err := zw.Close(); err != nil {
- t.Fatalf("compress close: %v", err)
+ err = os.WriteFile(filepath.Join(workDir, "file2.txt"), []byte("content2"), 0o644)
+ if err != nil {
+ t.Fatalf("failed to write file2.txt: %v", err)
}
- return buf.Bytes()
-}
-func TestPackSectionInflate(t *testing.T) {
- payload := []byte("pack payload")
- compressed := compressBytes(t, payload)
- body, err := packSectionInflate(bytes.NewReader(compressed), len(payload))
+ gitCmd(t, repoPath, "--work-tree="+workDir, "add", ".")
+ gitCmd(t, repoPath, "--work-tree="+workDir, "commit", "-m", "Test commit")
+ commitHash := gitCmd(t, repoPath, "rev-parse", "HEAD")
+
+ gitCmd(t, repoPath, "repack", "-a", "-d")
+
+ repo, err := OpenRepository(repoPath)
if err != nil {
- t.Fatalf("packSectionInflate error: %v", err)
+ t.Fatalf("OpenRepository failed: %v", err)
}
- if got := string(body.Bytes()); got != string(payload) {
- t.Fatalf("unexpected inflated data: %q", got)
+ defer func() { _ = repo.Close() }()
+
+ hashObj, _ := repo.ParseHash(commitHash)
+ obj, err := repo.ReadObject(hashObj)
+ if err != nil {
+ t.Fatalf("ReadObject from pack failed: %v", err)
}
- body.Release()
- body, err = packSectionInflate(bytes.NewReader(compressed), 0)
+ commit, ok := obj.(*StoredCommit)
+ if !ok {
+ t.Fatalf("expected *StoredCommit, got %T", obj)
+ }
+
+ treeObj, err := repo.ReadObject(commit.Tree)
if err != nil {
- t.Fatalf("packSectionInflate streaming error: %v", err)
+ t.Fatalf("ReadObject tree failed: %v", err)
}
- if got := string(body.Bytes()); got != string(payload) {
- t.Fatalf("unexpected streaming data: %q", got)
+
+ tree, ok := treeObj.(*StoredTree)
+ if !ok {
+ t.Fatalf("expected *StoredTree, got %T", treeObj)
}
- body.Release()
-}
-func encodePackHeader(ty ObjectType, size int) []byte {
- first := byte((ty & 0x7) << 4)
- first |= byte(size & 0x0f)
- size >>= 4
- if size == 0 {
- return []byte{first}
+ if len(tree.Entries) != 2 {
+ t.Errorf("tree entries: got %d, want 2", len(tree.Entries))
}
- first |= 0x80
- out := []byte{first}
- for size > 0 {
- b := byte(size & 0x7f)
- size >>= 7
- if size != 0 {
- b |= 0x80
+
+ gitLsTree := gitCmd(t, repoPath, "ls-tree", commit.Tree.String())
+ for _, entry := range tree.Entries {
+ if !strings.Contains(gitLsTree, string(entry.Name)) {
+ t.Errorf("git ls-tree doesn't contain %s", entry.Name)
}
- out = append(out, b)
}
- return out
}
-func TestPackHeaderRead(t *testing.T) {
- buf := encodePackHeader(ObjectTypeTree, 0x1fff)
- ty, size, err := packHeaderRead(bytes.NewReader(buf))
- if err != nil {
- t.Fatalf("packHeaderRead error: %v", err)
- }
- if ty != ObjectTypeTree || size != 0x1fff {
- t.Fatalf("unexpected header decode ty=%d size=%d", ty, size)
- }
- if _, _, err := packHeaderRead(bytes.NewReader([]byte{0x80})); err == nil {
- t.Fatal("expected error for truncated header")
+func TestPackfileLarge(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping large packfile test in short mode")
}
-}
-func encodeVarint(value int) []byte {
- var out []byte
- for {
- b := byte(value & 0x7f)
- value >>= 7
- if value != 0 {
- b |= 0x80
- }
- out = append(out, b)
- if value == 0 {
- break
+ repoPath, cleanup := setupTestRepo(t)
+ defer cleanup()
+
+ gitCmd(t, repoPath, "config", "gc.auto", "0")
+
+ workDir, cleanupWork := setupWorkDir(t)
+ defer cleanupWork()
+
+ numFiles := 1000
+ for i := 0; i < numFiles; i++ {
+ filename := filepath.Join(workDir, fmt.Sprintf("file%04d.txt", i))
+ content := fmt.Sprintf("Content for file %d\n", i)
+ err := os.WriteFile(filename, []byte(content), 0o644)
+ if err != nil {
+ t.Fatalf("failed to write %s: %v", filename, err)
}
}
- return out
-}
-func TestPackVarintRead(t *testing.T) {
- buf := encodeVarint(0x3456)
- pos := 0
- val, err := packVarintRead(buf, &pos)
+ gitCmd(t, repoPath, "--work-tree="+workDir, "add", ".")
+ gitCmd(t, repoPath, "--work-tree="+workDir, "commit", "-m", "Large commit")
+ commitHash := gitCmd(t, repoPath, "rev-parse", "HEAD")
+
+ gitCmd(t, repoPath, "repack", "-a", "-d")
+
+ repo, err := OpenRepository(repoPath)
if err != nil {
- t.Fatalf("packVarintRead error: %v", err)
+ t.Fatalf("OpenRepository failed: %v", err)
}
- if val != 0x3456 {
- t.Fatalf("unexpected varint value: %d", val)
+ defer func() { _ = repo.Close() }()
+
+ hashObj, _ := repo.ParseHash(commitHash)
+ obj, _ := repo.ReadObject(hashObj)
+ commit := obj.(*StoredCommit)
+
+ treeObj, _ := repo.ReadObject(commit.Tree)
+ tree := treeObj.(*StoredTree)
+
+ if len(tree.Entries) != numFiles {
+ t.Errorf("tree entries: got %d, want %d", len(tree.Entries), numFiles)
}
- if pos != len(buf) {
- t.Fatalf("expected pos %d, got %d", len(buf), pos)
+
+ gitCount := gitCmd(t, repoPath, "ls-tree", commit.Tree.String())
+ gitLines := strings.Count(gitCount, "\n") + 1
+ if len(tree.Entries) != gitLines {
+ t.Errorf("furgit found %d entries, git found %d", len(tree.Entries), gitLines)
}
- bad := []byte{0x80}
- pos = 0
- if _, err := packVarintRead(bad, &pos); err == nil {
- t.Fatal("expected error for unterminated varint")
+
+ for i := 0; i < 10; i++ {
+ idx := i * (numFiles / 10)
+ expectedName := fmt.Sprintf("file%04d.txt", idx)
+ entry := tree.Entry([]byte(expectedName))
+ if entry == nil {
+ t.Errorf("expected to find entry %s", expectedName)
+ continue
+ }
+
+ blobObj, _ := repo.ReadObject(entry.ID)
+ blob := blobObj.(*StoredBlob)
+
+ expectedContent := fmt.Sprintf("Content for file %d\n", idx)
+ if string(blob.Data) != expectedContent {
+ t.Errorf("blob %s: got %q, want %q", expectedName, blob.Data, expectedContent)
+ }
+
+ gitData := gitCatFile(t, repoPath, "blob", entry.ID.String())
+ if !bytes.Equal(blob.Data, gitData) {
+ t.Errorf("blob %s: furgit data doesn't match git data", expectedName)
+ }
}
}
-func TestPackDeltaApply(t *testing.T) {
- base := bufpool.FromOwned([]byte("abcdefghij"))
- defer base.Release()
- deltaBytes := []byte{0x0a, 0x0a, 0x91, 0x00, 0x03, 0x03, 'X', 'Y', 'Z', 0x91, 0x06, 0x04}
- delta := bufpool.FromOwned(deltaBytes)
- defer delta.Release()
- out, err := packDeltaApply(base, delta)
- if err != nil {
- t.Fatalf("packDeltaApply error: %v", err)
+func TestMultiPackIndex(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping multi-pack-index test in short mode")
}
- if got := string(out.Bytes()); got != "abcXYZghij" {
- t.Fatalf("unexpected delta output: %q", got)
- }
- out.Release()
-}
-func TestPackDeltaApplyMismatchedBaseSize(t *testing.T) {
- base := bufpool.FromOwned([]byte("abc"))
- defer base.Release()
- delta := bufpool.FromOwned([]byte{0x04, 0x04})
- defer delta.Release()
- if _, err := packDeltaApply(base, delta); err == nil {
- t.Fatal("expected error for mismatched base size")
+ repoPath, cleanup := setupTestRepo(t)
+ defer cleanup()
+
+ gitCmd(t, repoPath, "config", "gc.auto", "0")
+
+ workDir, cleanupWork := setupWorkDir(t)
+ defer cleanupWork()
+
+ err := os.WriteFile(filepath.Join(workDir, "file1.txt"), []byte("content1"), 0o644)
+ if err != nil {
+ t.Fatalf("failed to write file1.txt: %v", err)
}
-}
+ gitCmd(t, repoPath, "--work-tree="+workDir, "add", ".")
+ gitCmd(t, repoPath, "--work-tree="+workDir, "commit", "-m", "Commit 1")
+ commit1Hash := gitCmd(t, repoPath, "rev-parse", "HEAD")
+ gitCmd(t, repoPath, "repack", "-d")
-func TestPackDeltaReadOfsDistance(t *testing.T) {
- dist, err := packDeltaReadOfsDistance(bytes.NewReader([]byte{0x81, 0x01}))
+ err = os.WriteFile(filepath.Join(workDir, "file2.txt"), []byte("content2"), 0o644)
if err != nil {
- t.Fatalf("packDeltaReadOfsDistance error: %v", err)
+ t.Fatalf("failed to write file2.txt: %v", err)
}
- if dist != 257 {
- t.Fatalf("unexpected distance: %d", dist)
+ gitCmd(t, repoPath, "--work-tree="+workDir, "add", ".")
+ gitCmd(t, repoPath, "--work-tree="+workDir, "commit", "-m", "Commit 2")
+ commit2Hash := gitCmd(t, repoPath, "rev-parse", "HEAD")
+ gitCmd(t, repoPath, "repack", "-d")
+
+ gitCmd(t, repoPath, "repack", "--write-midx")
+
+ midxPath := filepath.Join(repoPath, "objects", "pack", "multi-pack-index")
+ if _, err := os.Stat(midxPath); os.IsNotExist(err) {
+ t.Fatalf("multi-pack-index file does not exist at %s", midxPath)
}
- if _, err := packDeltaReadOfsDistance(bytes.NewReader([]byte{})); err == nil {
- t.Fatal("expected error for empty reader")
+
+ repo, err := OpenRepository(repoPath)
+ if err != nil {
+ t.Fatalf("OpenRepository failed: %v", err)
}
-}
+ defer func() { _ = repo.Close() }()
-func TestBsearchHash(t *testing.T) {
- h1 := hashWithByte(0x01)
- h2 := hashWithByte(0x03)
- names := append(append([]byte(nil), h1.data[:testHashSize]...), h2.data[:testHashSize]...)
- idx, found := bsearchHash(names, testHashSize, 0, 2, h2)
- if !found || idx != 1 {
- t.Fatalf("expected to find second hash, idx=%d found=%v", idx, found)
+ hash1, _ := repo.ParseHash(commit1Hash)
+ obj1, err := repo.ReadObject(hash1)
+ if err != nil {
+ t.Fatalf("ReadObject commit1 failed: %v", err)
}
- _, found = bsearchHash(names, testHashSize, 0, 2, hashWithByte(0x05))
- if found {
- t.Fatalf("did not expect to find unknown hash")
+ commit1 := obj1.(*StoredCommit)
+ if commit1.Hash() != hash1 {
+ t.Error("commit1 hash mismatch")
}
-}
-func buildTestPackIndexBuffer(hash Hash, offset uint32) []byte {
- fanout := make([]byte, 256*4)
- first := int(hash.data[0])
- for i := 0; i < 256; i++ {
- var val uint32
- if i >= first {
- val = 1
- }
- binary.BigEndian.PutUint32(fanout[i*4:], val)
+ if !bytes.Contains(commit1.Message, []byte("Commit 1")) {
+ t.Errorf("commit1 message doesn't contain 'Commit 1': got %q", commit1.Message)
}
- var buf bytes.Buffer
- _ = binary.Write(&buf, binary.BigEndian, uint32(idxMagic))
- _ = binary.Write(&buf, binary.BigEndian, uint32(idxVersion2))
- buf.Write(fanout)
- buf.Write(hash.data[:testHashSize])
- buf.Write(make([]byte, 4))
- off32 := make([]byte, 4)
- binary.BigEndian.PutUint32(off32, offset)
- buf.Write(off32)
- buf.Write(make([]byte, 2*testHashSize))
- return buf.Bytes()
-}
-func TestPackIndexParse(t *testing.T) {
- h := hashWithByte(0x11)
- data := buildTestPackIndexBuffer(h, 0x12345678)
- pi := &packIndex{repo: &Repository{hashSize: testHashSize}}
- if err := pi.parse(data); err != nil {
- t.Fatalf("parse error: %v", err)
- }
- if pi.numObjects != 1 {
- t.Fatalf("expected 1 object, got %d", pi.numObjects)
+ hash2, _ := repo.ParseHash(commit2Hash)
+ obj2, err := repo.ReadObject(hash2)
+ if err != nil {
+ t.Fatalf("ReadObject commit2 failed: %v", err)
}
- if got, err := pi.offset(0); err != nil || got != 0x12345678 {
- t.Fatalf("unexpected 32-bit offset or error: %d, %v", got, err)
+ commit2 := obj2.(*StoredCommit)
+ if commit2.Hash() != hash2 {
+ t.Error("commit2 hash mismatch")
}
-}
-func TestPackIndexOffset64(t *testing.T) {
- pi := &packIndex{}
- pi.offset32 = make([]byte, 4)
- binary.BigEndian.PutUint32(pi.offset32, 0x80000000)
- pi.offset64 = make([]byte, 8)
- binary.BigEndian.PutUint64(pi.offset64, 0x1_0000_0000)
- if got, err := pi.offset(0); err != nil || got != 0x1_0000_0000 {
- t.Fatalf("unexpected 64-bit offset or error: %d, %v", got, err)
+ if !bytes.Contains(commit2.Message, []byte("Commit 2")) {
+ t.Errorf("commit2 message doesn't contain 'Commit 2': got %q", commit2.Message)
}
}