1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6 "bufio"
7 "bytes"
8 "errors"
9 "io"
10 "os"
11 "os/exec"
12 "strconv"
13 "strings"
14 "testing"
15)
16
17// TestRealRepository walks every object of the repository named by
18// ERMITE_GIT_BENCH_REPO and compares it with git cat-file --batch.
19func TestRealRepository(t *testing.T) {
20 dir := os.Getenv("ERMITE_GIT_BENCH_REPO")
21 if dir == "" {
22 t.Skip("ERMITE_GIT_BENCH_REPO is not set")
23 }
24 requireGit(t)
25 r, err := Open(dir)
26 if err != nil {
27 t.Fatal(err)
28 }
29 defer func() { _ = r.Close() }()
30
31 cmd := exec.Command("git", "-C", dir, "cat-file", "--batch-all-objects", "--batch")
32 cmd.Env = gitEnv()
33 stdout, err := cmd.StdoutPipe()
34 if err != nil {
35 t.Fatal(err)
36 }
37 if err := cmd.Start(); err != nil {
38 t.Fatal(err)
39 }
40 br := bufio.NewReaderSize(stdout, 1<<20)
41 var buf, want []byte
42 counts := map[Type]int{}
43 n := 0
44 for {
45 hdr, err := br.ReadString('\n')
46 if errors.Is(err, io.EOF) {
47 break
48 }
49 if err != nil {
50 t.Fatal(err)
51 }
52 f := strings.Fields(hdr)
53 size, _ := strconv.Atoi(f[2])
54 want = grow(want, size+1)
55 if _, err := io.ReadFull(br, want); err != nil {
56 t.Fatal(err)
57 }
58 want = want[:size]
59 id := mustID(t, f[0])
60 typ, got, err := r.ReadObject(id, buf)
61 if err != nil {
62 t.Fatalf("ReadObject(%s): %v", id, err)
63 }
64 buf = got
65 if typ.String() != f[1] || !bytes.Equal(got, want) {
66 t.Fatalf("object %s differs: %s/%d vs %s/%d", id, typ, len(got), f[1], size)
67 }
68 htyp, hsize, err := r.ObjectHeader(id)
69 if err != nil || htyp != typ || hsize != int64(size) {
70 t.Fatalf("ObjectHeader(%s) = %s %d %v, want %s %d", id, htyp, hsize, err, f[1], size)
71 }
72 if typ == TypeBlob && n%7 == 0 {
73 bsize, rc, err := r.OpenBlob(id)
74 if err != nil {
75 t.Fatal(err)
76 }
77 data, err := io.ReadAll(rc)
78 _ = rc.Close()
79 if err != nil || bsize != int64(size) || !bytes.Equal(data, want) {
80 t.Fatalf("OpenBlob(%s) differs: %v", id, err)
81 }
82 }
83 if typ == TypeCommit {
84 var c Commit
85 if err := ParseCommit(got, &c); err != nil {
86 t.Fatalf("ParseCommit(%s): %v", id, err)
87 }
88 }
89 if typ == TypeTree {
90 if err := ParseTree(got, func(Entry) bool { return true }); err != nil {
91 t.Fatalf("ParseTree(%s): %v", id, err)
92 }
93 }
94 counts[typ]++
95 n++
96 }
97 if err := cmd.Wait(); err != nil {
98 t.Fatal(err)
99 }
100 t.Logf("%d objects identical to git cat-file: %v", n, counts)
101 if n == 0 {
102 t.Fatal("no object")
103 }
104
105 // Refs and HEAD against git.
106 checkRefsAgainstGit(t, r, dir)
107}
108
109func checkRefsAgainstGit(t *testing.T, r *Repo, dir string) {
110 t.Helper()
111 out, err := exec.Command("git", "-C", dir, "for-each-ref", "--format=%(refname) %(objectname) %(*objectname)").Output()
112 if err != nil {
113 t.Fatal(err)
114 }
115 var want, got []string
116 for _, l := range lines(string(out)) {
117 want = append(want, strings.TrimRight(l, " "))
118 }
119 for ref, err := range r.Refs("") {
120 if err != nil {
121 t.Fatal(err)
122 }
123 peeled := ""
124 if typ, _, err := r.ObjectHeader(ref.ID); err == nil && typ == TypeTag {
125 p, _, err := r.Peel(ref.ID)
126 if err != nil {
127 t.Fatal(err)
128 }
129 peeled = p.String()
130 }
131 got = append(got, strings.TrimRight(ref.Name+" "+ref.ID.String()+" "+peeled, " "))
132 }
133 if strings.Join(got, "\n") != strings.Join(want, "\n") {
134 t.Fatalf("refs differ:\n%d got vs %d want", len(got), len(want))
135 }
136 t.Logf("%d refs identical to git for-each-ref", len(got))
137}