1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
package git
import (
"fmt"
"path/filepath"
"sync"
"testing"
"time"
"github.com/go-git/go-billy/v5/util"
"github.com/stretchr/testify/require"
"github.com/go-git/go-git/v5/plumbing/object"
)
// setupBenchmarkRepo creates a test repository with the specified number of files.
// It returns the worktree for benchmarking.
func setupBenchmarkRepo(b *testing.B, numFiles, numSubdirs, numGoroutines int) *Worktree {
b.Helper()
tmpDir := b.TempDir()
repoDir := filepath.Join(tmpDir, "repo")
repo, err := PlainInit(repoDir, false)
require.NoError(b, err)
wt, err := repo.Worktree()
require.NoError(b, err)
content := []byte("test content for benchmark\n")
var wg sync.WaitGroup
fileChan := make(chan string, numFiles)
for range numGoroutines {
wg.Add(1)
go func() {
defer wg.Done()
for filePath := range fileChan {
dir := filepath.Dir(filePath)
err := wt.Filesystem.MkdirAll(dir, 0o755)
if err != nil {
b.Errorf("failed to create directory %s: %v", dir, err)
continue
}
err = util.WriteFile(wt.Filesystem, filePath, content, 0o644)
if err != nil {
b.Errorf("failed to write file %s: %v", filePath, err)
}
}
}()
}
for i := range numFiles {
subdir := fmt.Sprintf("dir%d", i%numSubdirs)
fileName := fmt.Sprintf("file%04d.txt", i)
filePath := filepath.Join(subdir, fileName)
fileChan <- filePath
}
close(fileChan)
wg.Wait()
for i := range numSubdirs {
err = wt.AddGlob(fmt.Sprintf("dir%d/*", i))
require.NoError(b, err)
}
sig := &object.Signature{
Name: "Benchmark",
Email: "benchmark@test.com",
When: time.Now(),
}
_, err = wt.Commit("Initial commit with many files", &CommitOptions{
Author: sig,
Committer: sig,
})
require.NoError(b, err)
return wt
}
// BenchmarkStatus benchmarks Status() on a repository with 2000 files.
// It includes sub-benchmarks for clean and modified scenarios.
func BenchmarkStatus(b *testing.B) {
const (
numFiles = 2000
numSubdirs = 10
numGoroutines = 10
)
wt := setupBenchmarkRepo(b, numFiles, numSubdirs, numGoroutines)
b.Run("Clean", benchmarkStatusClean(wt))
b.Run("Modified", benchmarkStatusModified(wt, numFiles, numSubdirs))
}
// benchmarkStatusClean returns a benchmark function for testing Status() on a clean repository.
// This represents the worst-case scenario for the current implementation where
// every file's hash is computed unnecessarily since nothing has changed.
func benchmarkStatusClean(wt *Worktree) func(b *testing.B) {
return func(b *testing.B) {
for b.Loop() {
status, err := wt.Status()
if err != nil {
b.Fatalf("failed to get status: %v", err)
}
if !status.IsClean() {
b.Fatalf("expected clean status, got: %v", status)
}
}
}
}
// benchmarkStatusModified returns a benchmark function for testing Status() on a repository
// with some modified files. This represents a more realistic scenario where a small
// percentage of files have changed.
func benchmarkStatusModified(wt *Worktree, numFiles, numSubdirs int) func(b *testing.B) {
return func(b *testing.B) {
const modifiedPercent = 1
numModified := (numFiles * modifiedPercent) / 100
if numModified == 0 {
numModified = 1
}
modifiedContent := []byte("modified content\n")
for i := range numModified {
subdir := fmt.Sprintf("dir%d", i%numSubdirs)
fileName := fmt.Sprintf("file%04d.txt", i)
filePath := filepath.Join(subdir, fileName)
err := util.WriteFile(wt.Filesystem, filePath, modifiedContent, 0o644)
require.NoError(b, err)
}
for b.Loop() {
status, err := wt.Status()
if err != nil {
b.Fatalf("failed to get status: %v", err)
}
if status.IsClean() {
b.Fatalf("expected modified status, got clean")
}
modCount := 0
for _, fileStatus := range status {
if fileStatus.Worktree == Modified {
modCount++
}
}
if modCount != numModified {
b.Fatalf("expected %d modified files, got %d", numModified, modCount)
}
}
}
}
// BenchmarkStatusLarge benchmarks Status() on a large repository with 5000 files.
func BenchmarkStatusLarge(b *testing.B) {
const (
numFiles = 5000
numSubdirs = 20
numGoroutines = 10
)
wt := setupBenchmarkRepo(b, numFiles, numSubdirs, numGoroutines)
b.Run("Clean", benchmarkStatusClean(wt))
}
|