vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
|
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
9d ago
|
172 | func TestParseDiffToleratesAPatchThatEndsMidHunk(t *testing.T) { |
|
44530e74
Keenan Nemetz
16d ago
|
173 | t.Parallel() |
|
f64c77e4
Keenan Nemetz
9d 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
9d 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 | } |