Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Blame · server/web/diff_test.go

Line attribution on main · 231 line(s)

78912785
Keenan Nemetz
17d ago
1 package web
2
3 import (
4 "strings"
5 "testing"
6 )
7
8 // The parser reads what git prints. These fixtures are real `git diff` output,
9 // because a hand-written approximation is what lets a parser drift from git.
10 const patchTwoFiles = `diff --git a/readme.md b/readme.md
11 index 1111111..2222222 100644
12 --- a/readme.md
13 +++ b/readme.md
14 @@ -1,3 +1,4 @@
15 # Title
16
17 -old line
18 +new line
19 +another line
20 diff --git a/notes.txt b/notes.txt
21 deleted file mode 100644
22 index 3333333..0000000
23 --- a/notes.txt
24 +++ /dev/null
25 @@ -1,2 +0,0 @@
26 -gone
27 -gone too
28 `
29
30 func TestParseDiff(t *testing.T) {
44530e74
Keenan Nemetz
16d ago
31 t.Parallel()
78912785
Keenan Nemetz
17d ago
32 files := parseDiff(patchTwoFiles)
33 if len(files) != 2 {
34 t.Fatalf("parsed %d files, want 2: %+v", len(files), files)
35 }
36 first := files[0]
37 if first.OldPath != "readme.md" || first.NewPath != "readme.md" {
38 t.Fatalf("paths = %q -> %q", first.OldPath, first.NewPath)
39 }
40 if first.Added != 2 || first.Removed != 1 {
41 t.Fatalf("counts = +%d −%d, want +2 −1", first.Added, first.Removed)
42 }
e7982ac5
Keenan Nemetz
12d ago
43 if first.Language != "markdown" {
44 t.Fatalf("readme language = %q, want markdown", first.Language)
45 }
78912785
Keenan Nemetz
17d ago
46 if len(first.Hunks) != 1 || len(first.Hunks[0].Lines) != 5 {
47 t.Fatalf("hunk shape = %+v", first.Hunks)
48 }
49 lines := first.Hunks[0].Lines
50 if lines[0].Kind != "ctx" || lines[0].Text != "# Title" || lines[0].OldNo != 1 || lines[0].NewNo != 1 {
51 t.Fatalf("first line = %+v", lines[0])
52 }
53 if lines[2].Kind != "del" || lines[2].OldNo != 3 || lines[2].NewNo != 0 {
54 t.Fatalf("deleted line = %+v", lines[2])
55 }
56 if lines[3].Kind != "add" || lines[3].NewNo != 3 || lines[3].Text != "new line" {
57 t.Fatalf("added line = %+v", lines[3])
58 }
59 second := files[1]
60 if second.NewPath != "" || second.Label() != "notes.txt" {
61 t.Fatalf("deletion = %+v, label %q", second, second.Label())
62 }
63 if second.Removed != 2 {
64 t.Fatalf("deletion counts = −%d", second.Removed)
65 }
e7982ac5
Keenan Nemetz
12d ago
66 if second.Language != "" {
67 t.Fatalf("plain-text deletion language = %q, want none", second.Language)
68 }
69 }
70
71 // A deletion has no new path, so its language comes from the old one. A rename
72 // uses the new name, which is the file a reviewer sees in the review tree.
73 func TestDiffLanguageUsesTheVisiblePath(t *testing.T) {
74 t.Parallel()
75 files := parseDiff(`diff --git a/gone.go b/gone.go
76 deleted file mode 100644
77 --- a/gone.go
78 +++ /dev/null
79 @@ -1 +0,0 @@
80 -package gone
81 diff --git a/old.py b/new.py
82 similarity index 90%
83 rename from old.py
84 rename to new.py
85 --- a/old.py
86 +++ b/new.py
87 @@ -1 +1 @@
88 -old
89 +new
90 `)
91 if len(files) != 2 {
92 t.Fatalf("parsed %d files, want 2: %+v", len(files), files)
93 }
94 if files[0].Language != "go" {
95 t.Fatalf("deletion language = %q, want go", files[0].Language)
96 }
97 if files[1].Language != "python" {
98 t.Fatalf("rename language = %q, want python", files[1].Language)
99 }
78912785
Keenan Nemetz
17d ago
100 }
101
102 func TestParseDiffOfABinaryFile(t *testing.T) {
44530e74
Keenan Nemetz
16d ago
103 t.Parallel()
78912785
Keenan Nemetz
17d ago
104 files := parseDiff(`diff --git a/logo.png b/logo.png
105 index 4444444..5555555 100644
106 Binary files a/logo.png and b/logo.png differ
107 `)
108 if len(files) != 1 || !files[0].Binary {
109 t.Fatalf("binary file not recognised: %+v", files)
110 }
111 }
112
113 func TestParseDiffOfANewFile(t *testing.T) {
44530e74
Keenan Nemetz
16d ago
114 t.Parallel()
78912785
Keenan Nemetz
17d ago
115 files := parseDiff(`diff --git a/new.txt b/new.txt
116 new file mode 100644
117 index 0000000..6666666
118 --- /dev/null
119 +++ b/new.txt
120 @@ -0,0 +1,2 @@
121 +one
122 +two
123 `)
124 if len(files) != 1 {
125 t.Fatalf("files = %+v", files)
126 }
127 f := files[0]
128 if f.OldPath != "" || f.NewPath != "new.txt" || f.Added != 2 {
129 t.Fatalf("new file = %+v", f)
130 }
131 }
132
133 func TestParseDiffOfARename(t *testing.T) {
44530e74
Keenan Nemetz
16d ago
134 t.Parallel()
78912785
Keenan Nemetz
17d ago
135 files := parseDiff(`diff --git a/old.md b/new.md
136 similarity index 100%
137 rename from old.md
138 rename to new.md
139 `)
140 if len(files) != 1 {
141 t.Fatalf("files = %+v", files)
142 }
143 // A pure rename has no ---/+++ lines, so both paths are empty and the file is
144 // shown under the name git gave it in the diff header.
145 if files[0].OldPath != "" || files[0].NewPath != "" {
146 t.Fatalf("rename paths = %+v", files[0])
147 }
148 }
149
150 func TestParseDiffIgnoresPlumbing(t *testing.T) {
44530e74
Keenan Nemetz
16d ago
151 t.Parallel()
78912785
Keenan Nemetz
17d ago
152 files := parseDiff(`diff --git a/app.go b/app.go
153 index aaa..bbb 100644
154 --- a/app.go
155 +++ b/app.go
156 @@ -10,3 +10,3 @@ func main() {
157 ctx := context.Background()
158 - run(ctx)
159 + run(ctx, cfg)
160 `)
161 got := files[0].Hunks[0].Lines
162 for _, l := range got {
163 if strings.HasPrefix(l.Text, "index aaa") {
164 t.Fatalf("plumbing leaked into the rendered lines: %+v", got)
165 }
166 }
167 if got[1].OldNo != 11 || got[2].NewNo != 11 {
168 t.Fatalf("line numbers = %+v", got)
169 }
170 }
171
f64c77e4
Keenan Nemetz
8d ago
172 func TestParseDiffToleratesAPatchThatEndsMidHunk(t *testing.T) {
44530e74
Keenan Nemetz
16d ago
173 t.Parallel()
f64c77e4
Keenan Nemetz
8d ago
174 // A patch can end mid-hunk, whether git was interrupted or a caller handed
175 // the parser an excerpt: it must render what it has, not panic or claim the
176 // missing lines were unchanged.
78912785
Keenan Nemetz
17d ago
177 files := parseDiff(`diff --git a/big.txt b/big.txt
178 --- a/big.txt
179 +++ b/big.txt
180 @@ -1,2 +1,2 @@
181 - first
182 + second`)
183 if len(files) != 1 {
184 t.Fatalf("files = %+v", files)
185 }
186 if files[0].Added != 1 || files[0].Removed != 1 {
187 t.Fatalf("counts = %+v", files[0])
188 }
189 }
190
191 func TestDiffSummary(t *testing.T) {
44530e74
Keenan Nemetz
16d ago
192 t.Parallel()
78912785
Keenan Nemetz
17d ago
193 added, removed := diffSummary(parseDiff(patchTwoFiles))
194 if added != 2 || removed != 3 {
195 t.Fatalf("summary = +%d −%d, want +2 −3", added, removed)
196 }
197 }
bef0c093
Keenan Nemetz
8d ago
198
199 // TestPathspecNamesTheSideThatExists: git refuses an empty pathspec, and a
200 // deletion has only an old side. Handing it both sides used to fail the page for
201 // any deleted file; handing it none would print the whole commit instead.
202 func TestPathspecNamesTheSideThatExists(t *testing.T) {
203 t.Parallel()
204 cases := []struct {
205 name string
206 file diffFile
207 want []string
208 }{
209 {"modified", diffFile{OldPath: "a.go", NewPath: "a.go"}, []string{"a.go"}},
210 {"added", diffFile{NewPath: "a.go"}, []string{"a.go"}},
211 {"deleted", diffFile{OldPath: "a.go"}, []string{"a.go"}},
212 {"renamed", diffFile{OldPath: "old.go", NewPath: "new.go"}, []string{"old.go", "new.go"}},
213 {"unset", diffFile{}, nil},
214 }
215 for _, tc := range cases {
216 t.Run(tc.name, func(t *testing.T) {
217 got := tc.file.pathspec()
218 if len(got) != len(tc.want) {
219 t.Fatalf("pathspec = %q, want %q", got, tc.want)
220 }
221 for i := range got {
222 if got[i] != tc.want[i] {
223 t.Fatalf("pathspec = %q, want %q", got, tc.want)
224 }
225 if got[i] == "" {
226 t.Fatalf("pathspec holds an empty path: %q", got)
227 }
228 }
229 })
230 }
231 }