File view with blame information shown in the left gutter beside each line.

barerepo / server / e2e/budget_test.go
260 lines · 9.2kb · master
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
package e2e
133728e barerepo 1mo
2
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"context"
133728e barerepo 1mo
5
"fmt"
133728e barerepo 1mo
6
"net/http"
133728e barerepo 1mo
7
"net/http/httptest"
133728e barerepo 1mo
8
"os/exec"
133728e barerepo 1mo
9
"sort"
133728e barerepo 1mo
10
"strings"
133728e barerepo 1mo
11
"testing"
133728e barerepo 1mo
12
"time"
133728e barerepo 1mo
13
133728e barerepo 1mo
14
"github.com/barerepo/server/internal/cache"
133728e barerepo 1mo
15
"github.com/barerepo/server/internal/gitread"
133728e barerepo 1mo
16
"github.com/barerepo/server/internal/httpd"
133728e barerepo 1mo
17
"github.com/barerepo/server/internal/repocfg"
133728e barerepo 1mo
18
runpkg "github.com/barerepo/server/internal/run"
133728e barerepo 1mo
19
"github.com/barerepo/server/internal/search"
133728e barerepo 1mo
20
"github.com/barerepo/server/internal/thread"
133728e barerepo 1mo
21
"github.com/barerepo/server/internal/transport"
133728e barerepo 1mo
22
)
133728e barerepo 1mo
23
133728e barerepo 1mo
24
// Chapter 25's budget as build-failing thresholds, in server-side render time only.
133728e barerepo 1mo
25
var budget = []struct {
133728e barerepo 1mo
26
page string
133728e barerepo 1mo
27
path string
133728e barerepo 1mo
28
time time.Duration
133728e barerepo 1mo
29
payload int
133728e barerepo 1mo
30
}{
133728e barerepo 1mo
31
{"file tree", "/john/big/files/master/src", 10 * time.Millisecond, 15 << 10},
133728e barerepo 1mo
32
{"log", "/john/big", 20 * time.Millisecond, 30 << 10},
133728e barerepo 1mo
33
{"file view with blame", "/john/big/file/master/src/large.go", 20 * time.Millisecond, 40 << 10},
133728e barerepo 1mo
34
{"threads list", "/john/big/threads", 10 * time.Millisecond, 15 << 10},
133728e barerepo 1mo
35
{"one thread", "/john/big/thread/1", 10 * time.Millisecond, 15 << 10},
133728e barerepo 1mo
36
{"releases", "/john/big/releases", 10 * time.Millisecond, 15 << 10},
133728e barerepo 1mo
37
{"search", "/search?q=file0500", 10 * time.Millisecond, 15 << 10},
133728e barerepo 1mo
38
{"runs", "/john/big/runs", 10 * time.Millisecond, 15 << 10},
133728e barerepo 1mo
39
// The run, commit and profile pages are over in SPEC-NOTES and are not listed yet, because a row that fails on purpose tells the suite nothing.
133728e barerepo 1mo
40
}
133728e barerepo 1mo
41
133728e barerepo 1mo
42
// TestBudget fails on purpose, because a threshold raised to match the code measures nothing.
133728e barerepo 1mo
43
func TestBudget(t *testing.T) {
133728e barerepo 1mo
44
if testing.Short() {
133728e barerepo 1mo
45
t.Skip("building a large repository takes a while")
133728e barerepo 1mo
46
}
133728e barerepo 1mo
47
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
48
t.Skip("git is not installed")
133728e barerepo 1mo
49
}
133728e barerepo 1mo
50
in := newInstance(t)
133728e barerepo 1mo
51
in.account("john")
133728e barerepo 1mo
52
dir := buildBig(t, in, "john", "big", 1000, 200)
133728e barerepo 1mo
53
buildThreads(t, dir, 50)
133728e barerepo 1mo
54
buildTags(t, dir, 100)
133728e barerepo 1mo
55
// The sweep gcs every repository, which packs its refs, so that is the state to measure in.
133728e barerepo 1mo
56
run(t, dir, "git", "pack-refs", "--all")
133728e barerepo 1mo
57
indexBig(t, in, dir, "john", "big")
133728e barerepo 1mo
58
// A build that said a great deal, because a quiet one measures the chrome and not the page.
133728e barerepo 1mo
59
head := strings.TrimSpace(run(t, dir, "git", "rev-parse", "master"))
133728e barerepo 1mo
60
buildRuns(t, in, dir, "john/big", head)
133728e barerepo 1mo
61
for i := range budget {
133728e barerepo 1mo
62
budget[i].path = strings.ReplaceAll(budget[i].path, "HEADSHA", head)
133728e barerepo 1mo
63
}
133728e barerepo 1mo
64
133728e barerepo 1mo
65
shared := cache.New(in.cfg.Paths.Cache)
133728e barerepo 1mo
66
gitread.Cache = shared
133728e barerepo 1mo
67
repocfg.Cache = shared
133728e barerepo 1mo
68
thread.Cache = shared
133728e barerepo 1mo
69
httpd.Cache = shared
133728e barerepo 1mo
70
srv := &httpd.Server{Cfg: in.cfg, DB: in.db,
133728e barerepo 1mo
71
Transport: &transport.Server{Cfg: in.cfg, DB: in.db, Bin: binary}}
133728e barerepo 1mo
72
handler := srv.Handler()
133728e barerepo 1mo
73
133728e barerepo 1mo
74
for _, b := range budget {
133728e barerepo 1mo
75
// Warm once, because the budget is what a reader waits for, not a cold start.
133728e barerepo 1mo
76
record(handler, b.path)
133728e barerepo 1mo
77
133728e barerepo 1mo
78
var best time.Duration
133728e barerepo 1mo
79
var size int
133728e barerepo 1mo
80
for i := 0; i < 5; i++ {
133728e barerepo 1mo
81
start := time.Now()
133728e barerepo 1mo
82
rec := record(handler, b.path)
133728e barerepo 1mo
83
took := time.Since(start)
133728e barerepo 1mo
84
if i == 0 || took < best {
133728e barerepo 1mo
85
best = took
133728e barerepo 1mo
86
}
133728e barerepo 1mo
87
size = rec.Body.Len()
133728e barerepo 1mo
88
if rec.Code != http.StatusOK {
133728e barerepo 1mo
89
t.Fatalf("%s: %s answered %d", b.page, b.path, rec.Code)
133728e barerepo 1mo
90
}
133728e barerepo 1mo
91
}
133728e barerepo 1mo
92
if best > b.time {
133728e barerepo 1mo
93
t.Errorf("%s took %s, over the %s budget in chapter 25", b.page, best.Round(time.Millisecond), b.time)
133728e barerepo 1mo
94
}
133728e barerepo 1mo
95
if size > b.payload {
133728e barerepo 1mo
96
t.Errorf("%s sent %dkb, over the %dkb budget", b.page, size>>10, b.payload>>10)
133728e barerepo 1mo
97
}
133728e barerepo 1mo
98
t.Logf("%-22s %6s %5dkb (budget %s, %dkb)",
133728e barerepo 1mo
99
b.page, best.Round(100*time.Microsecond), size>>10, b.time, b.payload>>10)
133728e barerepo 1mo
100
}
133728e barerepo 1mo
101
}
133728e barerepo 1mo
102
133728e barerepo 1mo
103
func record(h http.Handler, path string) *httptest.ResponseRecorder {
133728e barerepo 1mo
104
req := httptest.NewRequest(http.MethodGet, path, nil)
133728e barerepo 1mo
105
rec := httptest.NewRecorder()
133728e barerepo 1mo
106
h.ServeHTTP(rec, req)
133728e barerepo 1mo
107
return rec
133728e barerepo 1mo
108
}
133728e barerepo 1mo
109
133728e barerepo 1mo
110
// buildBig uses plumbing, because a working tree would measure git's index rather than barerepo.
133728e barerepo 1mo
111
func buildBig(t *testing.T, in *instance, owner, name string, files, commits int) string {
133728e barerepo 1mo
112
t.Helper()
133728e barerepo 1mo
113
work := t.TempDir()
133728e barerepo 1mo
114
run(t, work, "git", "init", "-q", "--bare", "-b", "master")
133728e barerepo 1mo
115
133728e barerepo 1mo
116
// One blob reused, so the setup is about tree and commit count, not a thousand files.
133728e barerepo 1mo
117
var entries strings.Builder
133728e barerepo 1mo
118
for i := 0; i < files; i++ {
133728e barerepo 1mo
119
body := fmt.Sprintf("package src\n\n// file %d\nfunc F%d() int {\n\treturn %d\n}\n", i, i, i)
133728e barerepo 1mo
120
entries.WriteString("100644 blob " + hashObject(t, work, body) + "\t" + fmt.Sprintf("file%04d.go", i) + "\n")
133728e barerepo 1mo
121
}
133728e barerepo 1mo
122
// One file long enough to measure blame on, since a page of six lines measures the chrome.
133728e barerepo 1mo
123
var large strings.Builder
133728e barerepo 1mo
124
large.WriteString("package src\n\n")
133728e barerepo 1mo
125
for i := 0; i < 800; i++ {
133728e barerepo 1mo
126
fmt.Fprintf(&large, "func Large%d() string { return \"line %d of a file somebody reads\" }\n", i, i)
133728e barerepo 1mo
127
}
133728e barerepo 1mo
128
entries.WriteString("100644 blob " + hashObject(t, work, large.String()) + "\tlarge.go\n")
133728e barerepo 1mo
129
srcTree := mktree(t, work, entries.String())
133728e barerepo 1mo
130
config := hashObject(t, work, "[repo]\nvisibility = \"public\"\n")
133728e barerepo 1mo
131
133728e barerepo 1mo
132
// current carries every blob forward, or a commit reverts what the one before it wrote.
133728e barerepo 1mo
133
current := map[string]string{}
133728e barerepo 1mo
134
for _, line := range strings.Split(strings.TrimRight(entriesOf(t, work, srcTree), "\n"), "\n") {
133728e barerepo 1mo
135
if line == "" {
133728e barerepo 1mo
136
continue
133728e barerepo 1mo
137
}
133728e barerepo 1mo
138
current[line[strings.LastIndex(line, "\t")+1:]] = strings.Fields(line)[2]
133728e barerepo 1mo
139
}
133728e barerepo 1mo
140
133728e barerepo 1mo
141
parent := ""
133728e barerepo 1mo
142
for c := 0; c < commits; c++ {
133728e barerepo 1mo
143
// Three files change by ten lines each, which is under the per-commit collapse rule.
133728e barerepo 1mo
144
for k := 0; k < 3; k++ {
133728e barerepo 1mo
145
name := fmt.Sprintf("file%04d.go", (c*3+k)%files)
133728e barerepo 1mo
146
var body strings.Builder
133728e barerepo 1mo
147
fmt.Fprintf(&body, "package src\n\n// %s\n", name)
133728e barerepo 1mo
148
for line := 0; line < 40; line++ {
133728e barerepo 1mo
149
value := line
133728e barerepo 1mo
150
if line >= 10 && line < 20 {
133728e barerepo 1mo
151
value = line * (c + 1)
133728e barerepo 1mo
152
}
133728e barerepo 1mo
153
fmt.Fprintf(&body, "func F%d() int { return %d }\n", line, value)
133728e barerepo 1mo
154
}
133728e barerepo 1mo
155
current[name] = hashObject(t, work, body.String())
133728e barerepo 1mo
156
}
133728e barerepo 1mo
157
names := make([]string, 0, len(current))
133728e barerepo 1mo
158
for name := range current {
133728e barerepo 1mo
159
names = append(names, name)
133728e barerepo 1mo
160
}
133728e barerepo 1mo
161
sort.Strings(names)
133728e barerepo 1mo
162
var e strings.Builder
133728e barerepo 1mo
163
for _, name := range names {
133728e barerepo 1mo
164
e.WriteString("100644 blob " + current[name] + "\t" + name + "\n")
133728e barerepo 1mo
165
}
133728e barerepo 1mo
166
tree := mktree(t, work,
133728e barerepo 1mo
167
"040000 tree "+mktree(t, work, e.String())+"\tsrc\n"+
133728e barerepo 1mo
168
"040000 tree "+mktree(t, work, "100644 blob "+config+"\tconfig\n")+"\t.barerepo\n")
133728e barerepo 1mo
169
args := []string{"commit-tree", tree, "-m", fmt.Sprintf("change %d", c)}
133728e barerepo 1mo
170
if parent != "" {
133728e barerepo 1mo
171
args = append(args, "-p", parent)
133728e barerepo 1mo
172
}
133728e barerepo 1mo
173
parent = run(t, work, "git", args...)
133728e barerepo 1mo
174
}
133728e barerepo 1mo
175
run(t, work, "git", "update-ref", "refs/heads/master", parent)
133728e barerepo 1mo
176
133728e barerepo 1mo
177
dst := repoDir(t, in, owner, name)
133728e barerepo 1mo
178
run(t, "", "git", "clone", "-q", "--bare", work, dst)
133728e barerepo 1mo
179
run(t, dst, "git", "symbolic-ref", "HEAD", "refs/heads/master")
133728e barerepo 1mo
180
if _, err := in.db.ExecContext(t.Context(),
133728e barerepo 1mo
181
`INSERT INTO repos (owner, name, created_at) VALUES (?, ?, ?)`, owner, name, 0); err != nil {
133728e barerepo 1mo
182
t.Fatal(err)
133728e barerepo 1mo
183
}
133728e barerepo 1mo
184
return dst
133728e barerepo 1mo
185
}
133728e barerepo 1mo
186
133728e barerepo 1mo
187
func entriesOf(t *testing.T, dir, tree string) string {
133728e barerepo 1mo
188
t.Helper()
133728e barerepo 1mo
189
return run(t, dir, "git", "ls-tree", tree)
133728e barerepo 1mo
190
}
133728e barerepo 1mo
191
133728e barerepo 1mo
192
// buildThreads writes enough discussion to measure, since the old cost was per thread.
133728e barerepo 1mo
193
func buildThreads(t *testing.T, dir string, count int) {
133728e barerepo 1mo
194
t.Helper()
133728e barerepo 1mo
195
ctx := context.Background()
133728e barerepo 1mo
196
tip := strings.TrimSpace(run(t, dir, "git", "rev-parse", "master"))
133728e barerepo 1mo
197
for n := 1; n <= count; n++ {
133728e barerepo 1mo
198
meta := thread.Meta{
133728e barerepo 1mo
199
Title: fmt.Sprintf("thread number %d", n), State: thread.Open,
133728e barerepo 1mo
200
Author: "lisa", Opened: time.Now(),
133728e barerepo 1mo
201
}
133728e barerepo 1mo
202
if err := thread.Write(ctx, dir, n, "open", func(tr *thread.Tree) { tr.Meta = meta }); err != nil {
133728e barerepo 1mo
203
t.Fatal(err)
133728e barerepo 1mo
204
}
133728e barerepo 1mo
205
// The first thread is the one the budget reads, so it carries a discussion and not a stub.
133728e barerepo 1mo
206
replies := 3
133728e barerepo 1mo
207
if n == 1 {
133728e barerepo 1mo
208
replies = 40
133728e barerepo 1mo
209
}
133728e barerepo 1mo
210
for i := 0; i < replies; i++ {
133728e barerepo 1mo
211
c := thread.Comment{Author: "mark", Time: time.Now(), Body: fmt.Sprintf(
211 of 260 lines · the whole file
history · rawbarerepo 0.1.0