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.
| 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) { |
| 31 | t.Parallel() |
| 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 | } |
| 43 | if first.Language != "markdown" { |
| 44 | t.Fatalf("readme language = %q, want markdown", first.Language) |
| 45 | } |
| 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 | } |
| 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 | } |
| 100 | } |
| 101 | |
| 102 | func TestParseDiffOfABinaryFile(t *testing.T) { |
| 103 | t.Parallel() |
| 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) { |
| 114 | t.Parallel() |
| 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) { |
| 134 | t.Parallel() |
| 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) { |
| 151 | t.Parallel() |
| 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 | |
| 172 | func TestParseDiffToleratesAPatchThatEndsMidHunk(t *testing.T) { |
| 173 | t.Parallel() |
| 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. |
| 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) { |
| 192 | t.Parallel() |
| 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 | } |
| 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 | } |
| 232 |