Skip to content

Commit b39ea67

Browse files
committed
Add a test of measuring a Git bomb
1 parent 20aab08 commit b39ea67

File tree

2 files changed

+220
-2
lines changed

2 files changed

+220
-2
lines changed

git/git.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"fmt"
99
"io"
10+
"io/ioutil"
1011
"os"
1112
"os/exec"
1213
"path/filepath"
@@ -46,6 +47,10 @@ func (oid OID) String() string {
4647
return hex.EncodeToString(oid.v[:])
4748
}
4849

50+
func (oid OID) Bytes() []byte {
51+
return oid.v[:]
52+
}
53+
4954
func (oid OID) MarshalJSON() ([]byte, error) {
5055
src := oid.v[:]
5156
dst := make([]byte, hex.EncodedLen(len(src))+2)
@@ -92,6 +97,10 @@ func NewRepository(path string) (*Repository, error) {
9297
return repo, nil
9398
}
9499

100+
func (repo *Repository) Path() string {
101+
return repo.path
102+
}
103+
95104
func (repo *Repository) Close() error {
96105
return nil
97106
}
@@ -444,6 +453,71 @@ func (repo *Repository) NewObjectIter(args ...string) (
444453
}, in1, nil
445454
}
446455

456+
// CreateObject creates a new Git object, of the specified type, in
457+
// `Repository`. `writer` is a function that writes the object in `git
458+
// hash-object` input format. This is used for testing only.
459+
func (repo *Repository) CreateObject(t ObjectType, writer func(io.Writer) error) (OID, error) {
460+
cmd := exec.Command(
461+
"git", "-C", repo.path,
462+
"hash-object", "-w", "-t", string(t), "--stdin",
463+
)
464+
in, err := cmd.StdinPipe()
465+
if err != nil {
466+
return OID{}, err
467+
}
468+
469+
out, err := cmd.StdoutPipe()
470+
if err != nil {
471+
return OID{}, err
472+
}
473+
474+
cmd.Stderr = os.Stderr
475+
476+
err = cmd.Start()
477+
if err != nil {
478+
return OID{}, err
479+
}
480+
481+
err = writer(in)
482+
err2 := in.Close()
483+
if err != nil {
484+
cmd.Wait()
485+
return OID{}, err
486+
}
487+
if err2 != nil {
488+
cmd.Wait()
489+
return OID{}, err2
490+
}
491+
492+
output, err := ioutil.ReadAll(out)
493+
err2 = cmd.Wait()
494+
if err != nil {
495+
return OID{}, err
496+
}
497+
if err2 != nil {
498+
return OID{}, err2
499+
}
500+
501+
return NewOID(string(bytes.TrimSpace(output)))
502+
}
503+
504+
func (repo *Repository) UpdateRef(refname string, oid OID) error {
505+
var cmd *exec.Cmd
506+
507+
if oid == NullOID {
508+
cmd = exec.Command(
509+
"git", "-C", repo.path,
510+
"update-ref", "-d", refname,
511+
)
512+
} else {
513+
cmd = exec.Command(
514+
"git", "-C", repo.path,
515+
"update-ref", refname, oid.String(),
516+
)
517+
}
518+
return cmd.Run()
519+
}
520+
447521
// Next returns the next object, or EOF when done.
448522
func (l *ObjectIter) Next() (OID, ObjectType, counts.Count32, error) {
449523
line, err := l.f.ReadString('\n')

git_sizer_test.go

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,159 @@
11
package main_test
22

33
import (
4+
"fmt"
5+
"io"
6+
"io/ioutil"
7+
"os"
48
"os/exec"
59
"testing"
10+
11+
"github.com/github/git-sizer/counts"
12+
"github.com/github/git-sizer/git"
13+
"github.com/github/git-sizer/sizes"
14+
15+
"github.com/stretchr/testify/assert"
616
)
717

818
// Smoke test that the program runs.
919
func TestExec(t *testing.T) {
10-
command := exec.Command("bin/git-sizer")
11-
output, err := command.CombinedOutput()
20+
cmd := exec.Command("bin/git-sizer")
21+
output, err := cmd.CombinedOutput()
1222
if err != nil {
1323
t.Errorf("command failed (%s); output: %#v", err, string(output))
1424
}
1525
}
26+
27+
func newGitBomb(
28+
repoName string, depth, breadth int, body string,
29+
) (repo *git.Repository, err error) {
30+
path, err := ioutil.TempDir("", repoName)
31+
if err != nil {
32+
return nil, err
33+
}
34+
35+
defer func() {
36+
if err != nil {
37+
os.RemoveAll(path)
38+
}
39+
}()
40+
41+
cmd := exec.Command("git", "init", "--bare", path)
42+
err = cmd.Run()
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
repo, err = git.NewRepository(path)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
oid, err := repo.CreateObject("blob", func(w io.Writer) error {
53+
_, err := io.WriteString(w, body)
54+
return err
55+
})
56+
57+
digits := len(fmt.Sprintf("%d", breadth-1))
58+
59+
mode := "100644"
60+
prefix := "f"
61+
62+
for ; depth > 0; depth-- {
63+
oid, err = repo.CreateObject("tree", func(w io.Writer) error {
64+
for i := 0; i < breadth; i++ {
65+
_, err = fmt.Fprintf(
66+
w, "%s %s%0*d\x00%s",
67+
mode, prefix, digits, i, oid.Bytes(),
68+
)
69+
if err != nil {
70+
return err
71+
}
72+
}
73+
return nil
74+
})
75+
if err != nil {
76+
return nil, err
77+
}
78+
79+
mode = "40000"
80+
prefix = "d"
81+
}
82+
83+
oid, err = repo.CreateObject("commit", func(w io.Writer) error {
84+
_, err := fmt.Fprintf(
85+
w,
86+
"tree %s\n"+
87+
"author Example <[email protected]> 1112911993 -0700\n"+
88+
"committer Example <[email protected]> 1112911993 -0700\n"+
89+
"\n"+
90+
"Mwahahaha!\n",
91+
oid,
92+
)
93+
return err
94+
})
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
err = repo.UpdateRef("refs/heads/master", oid)
100+
if err != nil {
101+
return nil, err
102+
}
103+
104+
return repo, nil
105+
}
106+
107+
func pow(x uint64, n int) uint64 {
108+
p := uint64(1)
109+
for ; n > 0; n-- {
110+
p *= x
111+
}
112+
return p
113+
}
114+
115+
func TestBomb(t *testing.T) {
116+
assert := assert.New(t)
117+
118+
repo, err := newGitBomb("bomb", 10, 10, "boom!\n")
119+
if err != nil {
120+
t.Errorf("failed to create bomb: %s", err)
121+
}
122+
defer os.RemoveAll(repo.Path())
123+
124+
h, err := sizes.ScanRepositoryUsingGraph(
125+
repo, git.AllReferencesFilter, sizes.NameStyleNone, false,
126+
)
127+
if !assert.NoError(err) {
128+
return
129+
}
130+
131+
assert.Equal(counts.Count32(1), h.UniqueCommitCount, "unique commit count")
132+
assert.Equal(counts.Count64(169), h.UniqueCommitSize, "unique commit size")
133+
assert.Equal(counts.Count32(169), h.MaxCommitSize, "max commit size")
134+
assert.Equal(counts.Count32(1), h.MaxHistoryDepth, "max history depth")
135+
assert.Equal(counts.Count32(0), h.MaxParentCount, "max parent count")
136+
137+
assert.Equal(counts.Count32(10), h.UniqueTreeCount, "unique tree count")
138+
assert.Equal(counts.Count64(2910), h.UniqueTreeSize, "unique tree size")
139+
assert.Equal(counts.Count64(100), h.UniqueTreeEntries, "unique tree entries")
140+
assert.Equal(counts.Count32(10), h.MaxTreeEntries, "max tree entries")
141+
142+
assert.Equal(counts.Count32(1), h.UniqueBlobCount, "unique blob count")
143+
assert.Equal(counts.Count64(6), h.UniqueBlobSize, "unique blob size")
144+
assert.Equal(counts.Count32(6), h.MaxBlobSize, "max blob size")
145+
146+
assert.Equal(counts.Count32(0), h.UniqueTagCount, "unique tag count")
147+
assert.Equal(counts.Count32(0), h.MaxTagDepth, "max tag depth")
148+
149+
assert.Equal(counts.Count32(1), h.ReferenceCount, "reference count")
150+
151+
assert.Equal(counts.Count32(11), h.MaxPathDepth, "max path depth")
152+
assert.Equal(counts.Count32(29), h.MaxPathLength, "max path length")
153+
154+
assert.Equal(counts.Count32((pow(10, 10)-1)/(10-1)), h.MaxExpandedTreeCount, "max expanded tree count")
155+
assert.Equal(counts.Count32(0xffffffff), h.MaxExpandedBlobCount, "max expanded blob count")
156+
assert.Equal(counts.Count64(6*pow(10, 10)), h.MaxExpandedBlobSize, "max expanded blob size")
157+
assert.Equal(counts.Count32(0), h.MaxExpandedLinkCount, "max expanded link count")
158+
assert.Equal(counts.Count32(0), h.MaxExpandedSubmoduleCount, "max expanded submodule count")
159+
}

0 commit comments

Comments
 (0)