main range_diff_test.go
Eric Bower  ·  2026-02-25
  1package patchbin
  2
  3import (
  4	"fmt"
  5	"strings"
  6	"testing"
  7
  8	"github.com/picosh/patchbin/fixtures"
  9)
 10
 11func bail(err error) {
 12	if err != nil {
 13		panic(bail)
 14	}
 15}
 16
 17func cmp(afile, bfile string) string {
 18	a, err := fixtures.Fixtures.Open(afile)
 19	bail(err)
 20	b, err := fixtures.Fixtures.Open(bfile)
 21	bail(err)
 22	aPatches, err := ParsePatchset(a)
 23	bail(err)
 24	bPatches, err := ParsePatchset(b)
 25	bail(err)
 26	actual := RangeDiff(aPatches, bPatches)
 27	return RangeDiffToStr(actual)
 28}
 29
 30func fail(expected, actual string) string {
 31	return fmt.Sprintf("expected:[\n%s] actual:[\n%s]", expected, actual)
 32}
 33
 34// https://git.kernel.org/tree/t/t3206-range-diff.sh?id=d19b6cd2dd72dc811f19df4b32c7ed223256c3ee
 35
 36// simple A..B A..C (unmodified)
 37/*
 38	1:  $(test_oid t1) = 1:  $(test_oid u1) s/5/A/
 39	2:  $(test_oid t2) = 2:  $(test_oid u2) s/4/A/
 40	3:  $(test_oid t3) = 3:  $(test_oid u3) s/11/B/
 41	4:  $(test_oid t4) = 4:  $(test_oid u4) s/12/B/
 42*/
 43func TestRangeDiffUnmodified(t *testing.T) {
 44	actual := cmp("a_b.patch", "a_c.patch")
 45	expected := "1:  33c682a = 1:  1668484 chore: add torch and create random tensor\n"
 46	if expected != actual {
 47		t.Fatal(fail(expected, actual))
 48	}
 49}
 50
 51// trivial reordering
 52/*
 53	1:  $(test_oid t1) = 1:  $(test_oid r1) s/5/A/
 54	3:  $(test_oid t3) = 2:  $(test_oid r2) s/11/B/
 55	4:  $(test_oid t4) = 3:  $(test_oid r3) s/12/B/
 56	2:  $(test_oid t2) = 4:  $(test_oid r4) s/4/A/
 57*/
 58func TestRangeDiffTrivialReordering(t *testing.T) {
 59	actual := cmp("a_b_reorder.patch", "a_c_reorder.patch")
 60	expected := `2:  22dde12 = 1:  7dbb94c docs: readme
 611:  33c682a = 2:  ad17587 chore: add torch and create random tensor
 62`
 63	if expected != actual {
 64		t.Fatal(fail(expected, actual))
 65	}
 66}
 67
 68// removed commit
 69/*
 70	1:  $(test_oid t1) = 1:  $(test_oid d1) s/5/A/
 71	2:  $(test_oid t2) < -:  $(test_oid __) s/4/A/
 72	3:  $(test_oid t3) = 2:  $(test_oid d2) s/11/B/
 73	4:  $(test_oid t4) = 3:  $(test_oid d3) s/12/B/
 74*/
 75func TestRangeDiffRemovedCommit(t *testing.T) {
 76	actual := cmp("a_b_reorder.patch", "a_c_rm_commit.patch")
 77	if !strings.Contains(actual, "1:  33c682a < -:  ------- chore: add torch and create random tensor") {
 78		t.Fatal("expected removed commit header not found")
 79	}
 80	if !strings.Contains(actual, "2:  22dde12 = 1:  7dbb94c docs: readme") {
 81		t.Fatal("expected equal commit header not found")
 82	}
 83	if !strings.Contains(actual, "- requirements.txt") {
 84		t.Fatal("expected removed file for removed commit")
 85	}
 86}
 87
 88// added commit
 89/*
 90	1:  $(test_oid t1) = 1:  $(test_oid a1) s/5/A/
 91	2:  $(test_oid t2) = 2:  $(test_oid a2) s/4/A/
 92	-:  $(test_oid __) > 3:  $(test_oid a3) s/6/A/
 93	3:  $(test_oid t3) = 4:  $(test_oid a4) s/11/B/
 94	4:  $(test_oid t4) = 5:  $(test_oid a5) s/12/B/
 95*/
 96func TestRangeDiffAddedCommit(t *testing.T) {
 97	actual := cmp("a_b_reorder.patch", "a_c_added_commit.patch")
 98	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a chore: add torch and create random tensor") {
 99		t.Fatal("expected first equal commit header not found")
100	}
101	if !strings.Contains(actual, "2:  22dde12 = 2:  22dde12 docs: readme") {
102		t.Fatal("expected second equal commit header not found")
103	}
104	if !strings.Contains(actual, "-:  ------- > 3:  b248060 chore: make tensor 6x6") {
105		t.Fatal("expected added commit header not found")
106	}
107	if !strings.Contains(actual, "train.py") {
108		t.Fatal("expected file diff for added commit")
109	}
110}
111
112// changed commit
113/*
114	1:  $(test_oid t1) = 1:  $(test_oid c1) s/5/A/
115	2:  $(test_oid t2) = 2:  $(test_oid c2) s/4/A/
116	3:  $(test_oid t3) ! 3:  $(test_oid c3) s/11/B/
117	    @@ file: A
118	      9
119	      10
120	     -11
121	    -+B
122	    ++BB
123	      12
124	      13
125	      14
126	4:  $(test_oid t4) ! 4:  $(test_oid c4) s/12/B/
127	    @@ file
128	     @@ file: A
129	      9
130	      10
131	    - B
132	    + BB
133	     -12
134	     +B
135	      13
136*/
137func TestRangeDiffChangedCommit(t *testing.T) {
138	actual := cmp("a_b_reorder.patch", "a_c_changed_commit.patch")
139	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a chore: add torch and create random tensor") {
140		t.Fatal("expected first commit to be equal")
141	}
142	if !strings.Contains(actual, "2:  22dde12 ! 2:  dce20e7 docs: readme") {
143		t.Fatal("expected second commit to show changed marker")
144	}
145	if !strings.Contains(actual, "~ README.md") {
146		t.Fatal("expected changed file README.md")
147	}
148}
149
150// renamed file
151/*
152	1:  $(test_oid t1) = 1:  $(test_oid n1) s/5/A/
153	2:  $(test_oid t2) ! 2:  $(test_oid n2) s/4/A/
154	    @@ Metadata
155	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
156	    Z
157	    Z ## Commit message ##
158	    -    s/4/A/
159	    +    s/4/A/ + rename file
160	    Z
161	    - ## file ##
162	    + ## file => renamed-file ##
163	    Z@@
164	    Z 1
165	    Z 2
166	3:  $(test_oid t3) ! 3:  $(test_oid n3) s/11/B/
167	    @@ Metadata
168	    Z ## Commit message ##
169	    Z    s/11/B/
170	    Z
171	    - ## file ##
172	    -@@ file: A
173	    + ## renamed-file ##
174	    +@@ renamed-file: A
175	    Z 8
176	    Z 9
177	    Z 10
178	4:  $(test_oid t4) ! 4:  $(test_oid n4) s/12/B/
179	    @@ Metadata
180	    Z ## Commit message ##
181	    Z    s/12/B/
182	    Z
183	    - ## file ##
184	    -@@ file: A
185	    + ## renamed-file ##
186	    +@@ renamed-file: A
187	    Z 9
188	    Z 10
189	    Z B
190*/
191func TestRangeDiffRenamedFile(t *testing.T) {
192	actual := cmp("a_b_reorder.patch", "a_c_renamed_file.patch")
193	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a") {
194		t.Fatal("expected first commit to be equal")
195	}
196	if !strings.Contains(actual, "2:  22dde12 ! 2:  aabbcc1") {
197		t.Fatal("expected second commit to show diff marker")
198	}
199	if !strings.Contains(actual, "DOCS.md") {
200		t.Fatal("expected renamed file DOCS.md in output")
201	}
202}
203
204// file with mode only change
205/*
206	1:  $(test_oid t2) ! 1:  $(test_oid o1) s/4/A/
207	    @@ Metadata
208	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
209	    Z
210	    Z ## Commit message ##
211	    -    s/4/A/
212	    +    s/4/A/ + add other-file
213	    Z
214	    Z ## file ##
215	    Z@@
216	    @@ file
217	    Z A
218	    Z 6
219	    Z 7
220	    +
221	    + ## other-file (new) ##
222	2:  $(test_oid t3) ! 2:  $(test_oid o2) s/11/B/
223	    @@ Metadata
224	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
225	    Z
226	    Z ## Commit message ##
227	    -    s/11/B/
228	    +    s/11/B/ + mode change other-file
229	    Z
230	    Z ## file ##
231	    Z@@ file: A
232	    @@ file: A
233	    Z 12
234	    Z 13
235	    Z 14
236	    +
237	    + ## other-file (mode change 100644 => 100755) ##
238	3:  $(test_oid t4) = 3:  $(test_oid o3) s/12/B/
239*/
240func TestRangeDiffFileWithModeOnlyChange(t *testing.T) {
241	actual := cmp("a_b_reorder.patch", "a_c_mode_change.patch")
242	if !strings.Contains(actual, "1:  33c682a ! 1:  33c682a") {
243		t.Fatal("expected first commit to show diff marker due to new file")
244	}
245	if !strings.Contains(actual, "run.sh") {
246		t.Fatal("expected run.sh script in output")
247	}
248}
249
250// file added and later removed
251/*
252	1:  $(test_oid t1) = 1:  $(test_oid s1) s/5/A/
253	2:  $(test_oid t2) ! 2:  $(test_oid s2) s/4/A/
254	    @@ Metadata
255	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
256	    Z
257	    Z ## Commit message ##
258	    -    s/4/A/
259	    +    s/4/A/ + new-file
260	    Z
261	    Z ## file ##
262	    Z@@
263	    @@ file
264	    Z A
265	    Z 6
266	    Z 7
267	    +
268	    + ## new-file (new) ##
269	3:  $(test_oid t3) ! 3:  $(test_oid s3) s/11/B/
270	    @@ Metadata
271	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
272	    Z
273	    Z ## Commit message ##
274	    -    s/11/B/
275	    +    s/11/B/ + remove file
276	    Z
277	    Z ## file ##
278	    Z@@ file: A
279	    @@ file: A
280	    Z 12
281	    Z 13
282	    Z 14
283	    +
284	    + ## new-file (deleted) ##
285	4:  $(test_oid t4) = 4:  $(test_oid s4) s/12/B/
286*/
287func TestRangeDiffFileAddedThenRemoved(t *testing.T) {
288	actual := cmp("a_b_reorder.patch", "a_c_file_added_removed.patch")
289	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a") {
290		t.Fatal("expected first commit to be equal")
291	}
292	if !strings.Contains(actual, "temp.txt") {
293		t.Fatal("expected temp.txt in output")
294	}
295	if !strings.Contains(actual, "-:  ------- >") {
296		t.Fatal("expected added commit marker")
297	}
298	if !strings.Contains(actual, "ccddee1") {
299		t.Fatal("expected commit ccddee1 in output")
300	}
301}
302
303// changed message
304/*
305	1:  $(test_oid t1) = 1:  $(test_oid m1) s/5/A/
306	2:  $(test_oid t2) ! 2:  $(test_oid m2) s/4/A/
307	    @@ Metadata
308	    Z ## Commit message ##
309	    Z    s/4/A/
310	    Z
311	    +    Also a silly comment here!
312	    +
313	    Z ## file ##
314	    Z@@
315	    Z 1
316	3:  $(test_oid t3) = 3:  $(test_oid m3) s/11/B/
317	4:  $(test_oid t4) = 4:  $(test_oid m4) s/12/B/
318*/
319func TestRangeDiffChangedMessage(t *testing.T) {
320	actual := cmp("a_b_reorder.patch", "a_c_changed_message.patch")
321	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a") {
322		t.Fatal("expected first commit to be equal")
323	}
324	if !strings.Contains(actual, "2:  22dde12 ! 2:  ddeeff1") {
325		t.Fatal("expected second commit to show diff marker due to message change")
326	}
327}
328
329func TestRangeDiffEmptyPatchset(t *testing.T) {
330	a, err := fixtures.Fixtures.Open("a_b_reorder.patch")
331	bail(err)
332	aPatches, err := ParsePatchset(a)
333	bail(err)
334	bPatches := []*Patch{}
335
336	actual := RangeDiff(aPatches, bPatches)
337	result := RangeDiffToStr(actual)
338
339	if len(aPatches) != 2 {
340		t.Fatalf("expected 2 patches in a, got %d", len(aPatches))
341	}
342	if !strings.Contains(result, "< -:") {
343		t.Fatal("expected removed commit markers when comparing to empty patchset")
344	}
345	if !strings.Contains(result, "1:  33c682a < -:") {
346		t.Fatal("expected first commit to show as removed")
347	}
348	if !strings.Contains(result, "2:  22dde12 < -:") {
349		t.Fatal("expected second commit to show as removed")
350	}
351}
352
353func TestRangeDiffEmptyToNonEmpty(t *testing.T) {
354	b, err := fixtures.Fixtures.Open("a_b_reorder.patch")
355	bail(err)
356	bPatches, err := ParsePatchset(b)
357	bail(err)
358	aPatches := []*Patch{}
359
360	actual := RangeDiff(aPatches, bPatches)
361	result := RangeDiffToStr(actual)
362
363	if len(bPatches) != 2 {
364		t.Fatalf("expected 2 patches in b, got %d", len(bPatches))
365	}
366	if !strings.Contains(result, "> 1:") {
367		t.Fatal("expected added commit markers when comparing from empty patchset")
368	}
369	if !strings.Contains(result, "-:  ------- > 1:  33c682a") {
370		t.Fatal("expected first commit to show as added")
371	}
372}
373
374func TestRangeDiffSquashedCommits(t *testing.T) {
375	actual := cmp("a_b_reorder.patch", "a_c_squashed.patch")
376
377	if !strings.Contains(actual, "< -:") {
378		t.Fatal("expected at least one commit to show as removed (squashed away)")
379	}
380	if !strings.Contains(actual, "aabbccd") {
381		t.Fatal("expected squashed commit sha in output")
382	}
383}
384
385func TestRangeDiffSplitCommits(t *testing.T) {
386	actual := cmp("a_b_reorder.patch", "a_c_split.patch")
387
388	if !strings.Contains(actual, "-:  ------- >") {
389		t.Fatal("expected added commit marker for split commit")
390	}
391	if !strings.Contains(actual, "aabb112") {
392		t.Fatal("expected new split commit sha (aabb112) in output")
393	}
394	if !strings.Contains(actual, "33c682a") {
395		t.Fatal("expected original commit sha in output")
396	}
397}
398
399func TestRangeDiffDifferentAuthor(t *testing.T) {
400	actual := cmp("a_b_reorder.patch", "a_c_different_author.patch")
401
402	if !strings.Contains(actual, "!") {
403		t.Fatal("expected diff marker (!) for commits with different author")
404	}
405	if !strings.Contains(actual, "33c682a") && !strings.Contains(actual, "22dde12") {
406		t.Fatal("expected commit shas in output")
407	}
408}
409
410func TestRangeDiffMultipleFilesInCommit(t *testing.T) {
411	actual := cmp("a_b_reorder.patch", "a_c_multi_file_change.patch")
412
413	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a") {
414		t.Fatal("expected first commit to be equal")
415	}
416	if !strings.Contains(actual, "2:  22dde12 ! 2:  bbccdd1") {
417		t.Fatal("expected second commit to show diff marker")
418	}
419	if !strings.Contains(actual, "CONTRIBUTING.md") {
420		t.Fatal("expected CONTRIBUTING.md in diff output")
421	}
422	if !strings.Contains(actual, "LICENSE.md") {
423		t.Fatal("expected LICENSE.md in diff output")
424	}
425}
426
427func TestRangeDiffIgnoresContextLines(t *testing.T) {
428	actual := cmp("context_lines_v1.patch", "context_lines_v2.patch")
429
430	if !strings.Contains(actual, "=") {
431		t.Fatal("expected equal marker (=) since +/- lines are identical")
432	}
433	if strings.Contains(actual, "!") {
434		t.Fatal("should not show diff marker (!) when only context lines differ")
435	}
436	if strings.Contains(actual, "old_value") || strings.Contains(actual, "new_value") {
437		t.Fatal("should not have file diff output when changes are equal")
438	}
439}
440
441func TestRangeDiffNormalizesHunkHeaders(t *testing.T) {
442	actual := cmp("hunk_header_v1.patch", "hunk_header_v2.patch")
443
444	if !strings.Contains(actual, "=") {
445		t.Fatal("expected equal marker (=) since changes are identical despite different hunk headers")
446	}
447	if strings.Contains(actual, "!") {
448		t.Fatal("should not show diff marker (!) when only hunk header line numbers differ")
449	}
450	if strings.Contains(actual, "@@ server.go") {
451		t.Fatal("should not have file diff output when changes are equal")
452	}
453}