v0.30.0 c899179

budget_test.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain
4
5import (
6	"errors"
7	"testing"
8)
9
10func budgetID(t *testing.T, s string) ObjectID {
11	t.Helper()
12	id, err := ParseObjectID(s)
13	if err != nil {
14		t.Fatal(err)
15	}
16	return id
17}
18
19func TestValidateCommitObjects(t *testing.T) {
20	id := budgetID(t, "1111111111111111111111111111111111111111")
21	for _, tt := range []struct {
22		name string
23		ids  []ObjectID
24		max  int
25		want error
26	}{
27		{"empty", nil, 1, nil}, {"boundary", []ObjectID{id}, 1, nil},
28		{"too many", []ObjectID{id, id}, 1, ErrInvalid}, {"zero", []ObjectID{{}}, 1, ErrInvalidObjectID},
29	} {
30		t.Run(tt.name, func(t *testing.T) {
31			if err := ValidateCommitObjects(tt.ids, tt.max); !errors.Is(err, tt.want) {
32				t.Fatalf("got %v, want %v", err, tt.want)
33			}
34		})
35	}
36}
37
38func TestCommitObjectBudget(t *testing.T) {
39	id := budgetID(t, "1111111111111111111111111111111111111111")
40	for _, tt := range []struct {
41		name             string
42		commit           bool
43		size, total      int64
44		seen, take, stop bool
45	}{
46		{"non commit", false, 1, 0, false, false, false},
47		{"oversize", true, MaxCommitObjectBytes + 1, 0, false, false, false},
48		{"exact object", true, MaxCommitObjectBytes, 0, false, true, false},
49		{"exact total", true, 1, MaxCommitObjectsBytes - 1, false, true, false},
50		{"over total", true, 2, MaxCommitObjectsBytes - 1, false, false, true},
51		{"duplicate", true, 1, MaxCommitObjectsBytes, true, false, false},
52		{"negative", true, -1, 0, false, false, false},
53	} {
54		t.Run(tt.name, func(t *testing.T) {
55			b := CommitObjectBudget{total: tt.total}
56			if tt.seen {
57				b.seen = map[ObjectID]bool{id: true}
58			}
59			take, stop := b.Admit(id, tt.commit, tt.size)
60			if take != tt.take || stop != tt.stop {
61				t.Fatalf("got %v,%v", take, stop)
62			}
63			if take {
64				b.Charge(id, tt.size)
65				if !b.Seen(id) || b.total != tt.total+tt.size {
66					t.Fatal("charge not recorded")
67				}
68			}
69		})
70	}
71}
72
73func TestDiffBudgetWindow(t *testing.T) {
74	for _, tt := range []struct {
75		name           string
76		limits         DiffLimits
77		kept, from, to int
78		truncated      bool
79	}{
80		{"unlimited", DiffLimits{}, 5, 0, 5, false},
81		{"cap", DiffLimits{MaxFiles: 3}, 3, 0, 3, true},
82		{"window", DiffLimits{From: 1, To: 4}, 5, 1, 4, false},
83		{"clamped", DiffLimits{From: -1, To: 9, MaxFiles: 3}, 3, 0, 3, true},
84		{"past end", DiffLimits{From: 8}, 5, 8, 5, false},
85		{"reversed", DiffLimits{From: 4, To: 2}, 5, 4, 2, false},
86	} {
87		t.Run(tt.name, func(t *testing.T) {
88			b := NewDiffBudget(tt.limits)
89			k, f, to, tr := b.Window(5)
90			if k != tt.kept || f != tt.from || to != tt.to || tr != tt.truncated {
91				t.Fatalf("got %d,%d,%d,%v", k, f, to, tr)
92			}
93		})
94	}
95}
96
97func TestDiffBudgetAdmit(t *testing.T) {
98	for _, tt := range []struct {
99		name     string
100		limits   DiffLimits
101		binary   bool
102		path     string
103		n        int
104		wantRead bool
105		wantWhy  FileOmission
106	}{
107		{"fits", DiffLimits{MaxFileLines: 3, MaxTotalLines: 3, MaxTotalBytes: 1}, false, "a.go", 3, true, OmitNone},
108		{"binary", DiffLimits{MaxTotalBytes: 1}, true, "a.bin", 3, false, OmitNone},
109		{"no changes", DiffLimits{MaxTotalBytes: 1}, false, "a.go", 0, false, OmitNone},
110		{"file limit", DiffLimits{MaxFileLines: 3, MaxTotalLines: 5, MaxTotalBytes: 1}, false, "a.go", 4, false, OmitLarge},
111		{"total lines", DiffLimits{MaxFileLines: 3, MaxTotalLines: 2, MaxTotalBytes: 1}, false, "a.go", 3, false, OmitBudget},
112		{"no bytes", DiffLimits{MaxFileLines: 3, MaxTotalLines: 3}, false, "a.go", 1, false, OmitBudget},
113		{"generated", DiffLimits{SkipGenerated: true, MaxTotalBytes: 1}, false, "go.sum", 1, false, OmitGenerated},
114		{"generated not skipped", DiffLimits{MaxTotalBytes: 1}, false, "go.sum", 1, true, OmitNone},
115		{"generated before large", DiffLimits{SkipGenerated: true, MaxFileLines: 1, MaxTotalBytes: 1}, false, "go.sum", 9, false, OmitGenerated},
116		{"unlimited lines", DiffLimits{MaxTotalBytes: 1}, false, "a.go", 1 << 20, true, OmitNone},
117	} {
118		t.Run(tt.name, func(t *testing.T) {
119			b := NewDiffBudget(tt.limits)
120			read, why := b.Admit(tt.binary, tt.path, tt.n)
121			if read != tt.wantRead || why != tt.wantWhy {
122				t.Fatalf("got %v,%v", read, why)
123			}
124		})
125	}
126}
127
128func TestDiffBudgetCharge(t *testing.T) {
129	for _, tt := range []struct {
130		name  string
131		bytes int
132		want  FileOmission
133	}{{"exact", 3, OmitNone}, {"over", 4, OmitUnavailable}} {
134		t.Run(tt.name, func(t *testing.T) {
135			b := NewDiffBudget(DiffLimits{MaxFileLines: 2, MaxTotalLines: 1, MaxTotalBytes: 3})
136			before := b
137			if b.FileLines() != 1 {
138				t.Fatal("wrong file limit")
139			}
140			if got := b.ChargeLine(tt.bytes, true); got != tt.want {
141				t.Fatalf("wrong charge result %v", got)
142			}
143			if b.ChargeLine(0, true) == OmitNone {
144				t.Fatal("line budget exceeded")
145			}
146			b = before
147			if read, _ := b.Admit(false, "a", 1); !read {
148				t.Fatal("discarded file consumed budget")
149			}
150		})
151	}
152}
153
154func TestDiffBudgetChargeReasons(t *testing.T) {
155	b := NewDiffBudget(DiffLimits{MaxTotalLines: 1, MaxTotalBytes: 3})
156	if b.ChargeLine(2, false) != OmitNone || b.FileLines() != 1 {
157		t.Fatal("context consumes bytes only")
158	}
159	if b.ChargeLine(1, true) != OmitNone || b.FileLines() != 0 {
160		t.Fatal("first changed line fits")
161	}
162	if got := b.ChargeLine(0, true); got != OmitBudget {
163		t.Fatalf("lines ran out: %v", got)
164	}
165	b = NewDiffBudget(DiffLimits{MaxTotalLines: 9, MaxTotalBytes: 1})
166	if got := b.ChargeLine(2, false); got != OmitUnavailable {
167		t.Fatalf("bytes ran out: %v", got)
168	}
169	free := NewDiffBudget(DiffLimits{MaxTotalBytes: 5})
170	for range 100 {
171		if free.ChargeLine(0, true) != OmitNone {
172			t.Fatal("0 means no line limit")
173		}
174	}
175	if free.FileLines() < 1<<30 {
176		t.Fatal("unlimited FileLines")
177	}
178}
179
180func TestFileOmissionCollapsed(t *testing.T) {
181	for o, want := range map[FileOmission]bool{OmitNone: false, OmitGenerated: true, OmitLarge: true, OmitBudget: true, OmitUnavailable: false} {
182		if o.Collapsed() != want {
183			t.Errorf("%d: got %v", o, !want)
184		}
185	}
186}
187
188func TestOneFileDiffLimits(t *testing.T) {
189	l := OneFileDiffLimits(3)
190	if l.From != 3 || l.To != 4 || l.MaxFileLines != 0 || l.MaxAloneLines != MaxOneFileLines || l.MaxTotalLines != 0 || l.MaxFiles != 0 || l.MaxTotalBytes != DefaultDiffLimits().MaxTotalBytes {
191		t.Fatalf("got %+v", l)
192	}
193	// A negative index (an unchecked IndexFunc) selects no file, not the whole diff.
194	if _, from, to, _ := NewDiffBudget(OneFileDiffLimits(-1)).Window(10); from < to {
195		t.Fatalf("OneFileDiffLimits(-1) selects files [%d, %d)", from, to)
196	}
197}
198
199func TestDiffBudgetBytesOfEarlierFiles(t *testing.T) {
200	b := NewDiffBudget(DiffLimits{MaxTotalBytes: 10})
201	b.StartFile()
202	if b.ChargeLine(4, true) != OmitNone {
203		t.Fatal("the first file fits")
204	}
205	b.StartFile()
206	if got := b.ChargeLine(7, true); got != OmitBudget {
207		t.Fatalf("a file meeting the bytes of earlier files: got %v, want OmitBudget", got)
208	}
209	alone := NewDiffBudget(DiffLimits{MaxTotalBytes: 10})
210	alone.StartFile()
211	if got := alone.ChargeLine(11, true); got != OmitUnavailable {
212		t.Fatalf("a file over the bytes on its own: got %v, want OmitUnavailable", got)
213	}
214}
215
216func TestDiffBudgetContext(t *testing.T) {
217	b := NewDiffBudget(DiffLimits{MaxFileLines: 2, MaxTotalLines: 2, MaxTotalBytes: 6})
218	if b.ChargeLine(2, false) != OmitNone || b.FileLines() != 2 {
219		t.Fatal("context consumed changed-line budget")
220	}
221	first := b.ChargeLine(1, true)
222	second := b.ChargeLine(1, true)
223	if first != OmitNone || second != OmitNone || b.FileLines() != 0 {
224		t.Fatal("additions and deletions must consume changed-line budget")
225	}
226	if b.ChargeLine(2, false) != OmitNone {
227		t.Fatal("context at the byte boundary must fit even without changed lines left")
228	}
229	if b.ChargeLine(1, false) != OmitUnavailable {
230		t.Fatal("context must not exceed the byte budget")
231	}
232}
233
234func TestDiffBudgetAdmitAloneLines(t *testing.T) {
235	b := NewDiffBudget(OneFileDiffLimits(0))
236	if read, why := b.Admit(false, "a.go", MaxOneFileLines); !read || why != OmitNone {
237		t.Fatalf("at the cap: read=%v why=%v", read, why)
238	}
239	if read, why := b.Admit(false, "a.go", MaxOneFileLines+1); read || why != OmitUnavailable {
240		t.Fatalf("over the cap: read=%v why=%v, want OmitUnavailable", read, why)
241	}
242}
243
244func TestDiffCollapseFrom(t *testing.T) {
245	d := Diff{Files: []FileDiff{
246		{Additions: 1},
247		{Additions: 1},
248		{Binary: true, Additions: 1},
249		{Omitted: OmitLarge, Additions: 1},
250		{},
251		{Deletions: 2},
252	}}
253	d.CollapseFrom(1)
254	want := []FileOmission{OmitNone, OmitBudget, OmitNone, OmitLarge, OmitNone, OmitBudget}
255	for i, f := range d.Files {
256		if f.Omitted != want[i] {
257			t.Errorf("file %d: omitted %v, want %v", i, f.Omitted, want[i])
258		}
259	}
260}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 43

↑↓ moveenter openesc close