master
  1package gitdiff
  2
  3import (
  4	"testing"
  5	"time"
  6)
  7
  8func TestParsePatchDate(t *testing.T) {
  9	expected := time.Date(2020, 4, 9, 8, 7, 6, 0, time.UTC)
 10
 11	tests := map[string]struct {
 12		Input  string
 13		Output time.Time
 14		Err    interface{}
 15	}{
 16		"default": {
 17			Input:  "Thu Apr 9 01:07:06 2020 -0700",
 18			Output: expected,
 19		},
 20		"defaultLocal": {
 21			Input:  "Thu Apr 9 01:07:06 2020",
 22			Output: time.Date(2020, 4, 9, 1, 7, 6, 0, time.Local),
 23		},
 24		"iso": {
 25			Input:  "2020-04-09 01:07:06 -0700",
 26			Output: expected,
 27		},
 28		"isoStrict": {
 29			Input:  "2020-04-09T01:07:06-07:00",
 30			Output: expected,
 31		},
 32		"rfc": {
 33			Input:  "Thu, 9 Apr 2020 01:07:06 -0700",
 34			Output: expected,
 35		},
 36		"short": {
 37			Input:  "2020-04-09",
 38			Output: time.Date(2020, 4, 9, 0, 0, 0, 0, time.Local),
 39		},
 40		"raw": {
 41			Input:  "1586419626 -0700",
 42			Output: expected,
 43		},
 44		"unix": {
 45			Input:  "1586419626",
 46			Output: expected,
 47		},
 48		"unknownFormat": {
 49			Input: "4/9/2020 01:07:06 PDT",
 50			Err:   "unknown date format",
 51		},
 52		"empty": {
 53			Input: "",
 54		},
 55	}
 56
 57	for name, test := range tests {
 58		t.Run(name, func(t *testing.T) {
 59			d, err := ParsePatchDate(test.Input)
 60			if test.Err != nil {
 61				assertError(t, test.Err, err, "parsing date")
 62				return
 63			}
 64			if err != nil {
 65				t.Fatalf("unexpected error parsing date: %v", err)
 66			}
 67			if !test.Output.Equal(d) {
 68				t.Errorf("incorrect parsed date: expected %v, actual %v", test.Output, d)
 69			}
 70		})
 71	}
 72}
 73
 74func TestParsePatchHeader(t *testing.T) {
 75	expectedSHA := "61f5cd90bed4d204ee3feb3aa41ee91d4734855b"
 76	expectedIdentity := &PatchIdentity{
 77		Name:  "Morton Haypenny",
 78		Email: "mhaypenny@example.com",
 79	}
 80	expectedDate := time.Date(2020, 04, 11, 15, 21, 23, 0, time.FixedZone("PDT", -7*60*60))
 81	expectedTitle := "A sample commit to test header parsing"
 82	expectedEmojiOneLineTitle := "🤖 Enabling auto-merging"
 83	expectedEmojiMultiLineTitle := "[IA64] Put ia64 config files on the Uwe Kleine-König diet"
 84	expectedBody := "The medium format shows the body, which\nmay wrap on to multiple lines.\n\nAnother body line."
 85	expectedBodyAppendix := "CC: Joe Smith <joe.smith@company.com>"
 86
 87	tests := map[string]struct {
 88		Input   string
 89		Options []PatchHeaderOption
 90		Header  PatchHeader
 91		Err     interface{}
 92	}{
 93		"prettyShort": {
 94			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
 95Author: Morton Haypenny <mhaypenny@example.com>
 96
 97    A sample commit to test header parsing
 98`,
 99			Header: PatchHeader{
100				SHA:    expectedSHA,
101				Author: expectedIdentity,
102				Title:  expectedTitle,
103			},
104		},
105		"prettyMedium": {
106			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
107Author: Morton Haypenny <mhaypenny@example.com>
108Date:   Sat Apr 11 15:21:23 2020 -0700
109
110    A sample commit to test header parsing
111
112    The medium format shows the body, which
113    may wrap on to multiple lines.
114
115    Another body line.
116`,
117			Header: PatchHeader{
118				SHA:        expectedSHA,
119				Author:     expectedIdentity,
120				AuthorDate: expectedDate,
121				Title:      expectedTitle,
122				Body:       expectedBody,
123			},
124		},
125		"prettyFull": {
126			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
127Author: Morton Haypenny <mhaypenny@example.com>
128Commit: Morton Haypenny <mhaypenny@example.com>
129
130    A sample commit to test header parsing
131
132    The medium format shows the body, which
133    may wrap on to multiple lines.
134
135    Another body line.
136`,
137			Header: PatchHeader{
138				SHA:       expectedSHA,
139				Author:    expectedIdentity,
140				Committer: expectedIdentity,
141				Title:     expectedTitle,
142				Body:      expectedBody,
143			},
144		},
145		"prettyFuller": {
146			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
147Author:     Morton Haypenny <mhaypenny@example.com>
148AuthorDate: Sat Apr 11 15:21:23 2020 -0700
149Commit:     Morton Haypenny <mhaypenny@example.com>
150CommitDate: Sat Apr 11 15:21:23 2020 -0700
151
152    A sample commit to test header parsing
153
154    The medium format shows the body, which
155    may wrap on to multiple lines.
156
157    Another body line.
158`,
159			Header: PatchHeader{
160				SHA:           expectedSHA,
161				Author:        expectedIdentity,
162				AuthorDate:    expectedDate,
163				Committer:     expectedIdentity,
164				CommitterDate: expectedDate,
165				Title:         expectedTitle,
166				Body:          expectedBody,
167			},
168		},
169		"prettyAppendix": {
170			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
171Author:     Morton Haypenny <mhaypenny@example.com>
172AuthorDate: Sat Apr 11 15:21:23 2020 -0700
173Commit:     Morton Haypenny <mhaypenny@example.com>
174CommitDate: Sat Apr 11 15:21:23 2020 -0700
175
176    A sample commit to test header parsing
177
178    The medium format shows the body, which
179    may wrap on to multiple lines.
180
181    Another body line.
182    ---
183    CC: Joe Smith <joe.smith@company.com>
184`,
185			Header: PatchHeader{
186				SHA:           expectedSHA,
187				Author:        expectedIdentity,
188				AuthorDate:    expectedDate,
189				Committer:     expectedIdentity,
190				CommitterDate: expectedDate,
191				Title:         expectedTitle,
192				Body:          expectedBody + "\n---\n" + expectedBodyAppendix,
193			},
194		},
195		"mailbox": {
196			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
197From: Morton Haypenny <mhaypenny@example.com>
198Date: Sat, 11 Apr 2020 15:21:23 -0700
199Subject: [PATCH] A sample commit to test header parsing
200
201The medium format shows the body, which
202may wrap on to multiple lines.
203
204Another body line.
205`,
206			Header: PatchHeader{
207				SHA:        expectedSHA,
208				Author:     expectedIdentity,
209				AuthorDate: expectedDate,
210				Title:      expectedTitle,
211				Body:       expectedBody,
212			},
213		},
214		"mailboxPatchOnly": {
215			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
216From: Morton Haypenny <mhaypenny@example.com>
217Date: Sat, 11 Apr 2020 15:21:23 -0700
218Subject: [PATCH] [BUG-123] A sample commit to test header parsing
219
220The medium format shows the body, which
221may wrap on to multiple lines.
222
223Another body line.
224`,
225			Options: []PatchHeaderOption{
226				WithSubjectCleanMode(SubjectCleanPatchOnly),
227			},
228			Header: PatchHeader{
229				SHA:        expectedSHA,
230				Author:     expectedIdentity,
231				AuthorDate: expectedDate,
232				Title:      "[BUG-123] " + expectedTitle,
233				Body:       expectedBody,
234			},
235		},
236		"mailboxEmojiOneLine": {
237			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
238From: Morton Haypenny <mhaypenny@example.com>
239Date: Sat, 11 Apr 2020 15:21:23 -0700
240Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Enabling=20auto-merging?=
241
242The medium format shows the body, which
243may wrap on to multiple lines.
244
245Another body line.
246`,
247			Header: PatchHeader{
248				SHA:        expectedSHA,
249				Author:     expectedIdentity,
250				AuthorDate: expectedDate,
251				Title:      expectedEmojiOneLineTitle,
252				Body:       expectedBody,
253			},
254		},
255		"mailboxEmojiMultiLine": {
256			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
257From: Morton Haypenny <mhaypenny@example.com>
258Date: Sat, 11 Apr 2020 15:21:23 -0700
259Subject: [PATCH] =?UTF-8?q?[IA64]=20Put=20ia64=20config=20files=20on=20the=20?=
260 =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20diet?=
261
262The medium format shows the body, which
263may wrap on to multiple lines.
264
265Another body line.
266`,
267			Header: PatchHeader{
268				SHA:        expectedSHA,
269				Author:     expectedIdentity,
270				AuthorDate: expectedDate,
271				Title:      expectedEmojiMultiLineTitle,
272				Body:       expectedBody,
273			},
274		},
275		"mailboxRFC5322SpecialCharacters": {
276			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
277From: "dependabot[bot]" <12345+dependabot[bot]@users.noreply.github.com>
278Date: Sat, 11 Apr 2020 15:21:23 -0700
279Subject: [PATCH] A sample commit to test header parsing
280
281The medium format shows the body, which
282may wrap on to multiple lines.
283
284Another body line.
285`,
286			Header: PatchHeader{
287				SHA: expectedSHA,
288				Author: &PatchIdentity{
289					Name:  "dependabot[bot]",
290					Email: "12345+dependabot[bot]@users.noreply.github.com",
291				},
292				AuthorDate: expectedDate,
293				Title:      expectedTitle,
294				Body:       expectedBody,
295			},
296		},
297		"mailboxAppendix": {
298			Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
299From: Morton Haypenny <mhaypenny@example.com>
300Date: Sat, 11 Apr 2020 15:21:23 -0700
301Subject: [PATCH] A sample commit to test header parsing
302
303The medium format shows the body, which
304may wrap on to multiple lines.
305
306Another body line.
307---
308CC: Joe Smith <joe.smith@company.com>
309`,
310			Header: PatchHeader{
311				SHA:          expectedSHA,
312				Author:       expectedIdentity,
313				AuthorDate:   expectedDate,
314				Title:        expectedTitle,
315				Body:         expectedBody,
316				BodyAppendix: expectedBodyAppendix,
317			},
318		},
319		"mailboxMinimalNoName": {
320			Input: `From: <mhaypenny@example.com>
321Subject: [PATCH] A sample commit to test header parsing
322
323The medium format shows the body, which
324may wrap on to multiple lines.
325
326Another body line.
327`,
328			Header: PatchHeader{
329				Author: &PatchIdentity{expectedIdentity.Email, expectedIdentity.Email},
330				Title:  expectedTitle,
331				Body:   expectedBody,
332			},
333		},
334		"mailboxMinimal": {
335			Input: `From: Morton Haypenny <mhaypenny@example.com>
336Subject: [PATCH] A sample commit to test header parsing
337
338The medium format shows the body, which
339may wrap on to multiple lines.
340
341Another body line.
342`,
343			Header: PatchHeader{
344				Author: expectedIdentity,
345				Title:  expectedTitle,
346				Body:   expectedBody,
347			},
348		},
349		"unwrapTitle": {
350			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
351Author: Morton Haypenny <mhaypenny@example.com>
352Date:   Sat Apr 11 15:21:23 2020 -0700
353
354    A sample commit to test header parsing with a long
355	title that is wrapped.
356`,
357			Header: PatchHeader{
358				SHA:        expectedSHA,
359				Author:     expectedIdentity,
360				AuthorDate: expectedDate,
361				Title:      expectedTitle + " with a long title that is wrapped.",
362			},
363		},
364		"normalizeBodySpace": {
365			Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
366Author: Morton Haypenny <mhaypenny@example.com>
367Date:   Sat Apr 11 15:21:23 2020 -0700
368
369    A sample commit to test header parsing
370
371
372    The medium format shows the body, which
373    may wrap on to multiple lines.
374
375
376    Another body line.
377
378
379`,
380			Header: PatchHeader{
381				SHA:        expectedSHA,
382				Author:     expectedIdentity,
383				AuthorDate: expectedDate,
384				Title:      expectedTitle,
385				Body:       expectedBody,
386			},
387		},
388		"ignoreLeadingBlankLines": {
389			Input: `
390
391` + "    " + `
392commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
393Author: Morton Haypenny <mhaypenny@example.com>
394
395    A sample commit to test header parsing
396`,
397			Header: PatchHeader{
398				SHA:    expectedSHA,
399				Author: expectedIdentity,
400				Title:  expectedTitle,
401			},
402		},
403		"emptyHeader": {
404			Input:  "",
405			Header: PatchHeader{},
406		},
407	}
408
409	for name, test := range tests {
410		t.Run(name, func(t *testing.T) {
411			h, err := ParsePatchHeader(test.Input, test.Options...)
412			if test.Err != nil {
413				assertError(t, test.Err, err, "parsing patch header")
414				return
415			}
416			if err != nil {
417				t.Fatalf("unexpected error parsing patch header: %v", err)
418			}
419			if h == nil {
420				t.Fatalf("expected non-nil header, but got nil")
421			}
422
423			exp := test.Header
424			act := *h
425
426			if exp.SHA != act.SHA {
427				t.Errorf("incorrect parsed SHA: expected %q, actual %q", exp.SHA, act.SHA)
428			}
429
430			assertPatchIdentity(t, "author", exp.Author, act.Author)
431			if !exp.AuthorDate.Equal(act.AuthorDate) {
432				t.Errorf("incorrect parsed author date: expected %v, but got %v", exp.AuthorDate, act.AuthorDate)
433			}
434
435			assertPatchIdentity(t, "committer", exp.Committer, act.Committer)
436			if !exp.CommitterDate.Equal(act.CommitterDate) {
437				t.Errorf("incorrect parsed committer date: expected %v, but got %v", exp.CommitterDate, act.CommitterDate)
438			}
439
440			if exp.Title != act.Title {
441				t.Errorf("incorrect parsed title:\n  expected: %q\n    actual: %q", exp.Title, act.Title)
442			}
443			if exp.Body != act.Body {
444				t.Errorf("incorrect parsed body:\n  expected: %q\n    actual: %q", exp.Body, act.Body)
445			}
446			if exp.BodyAppendix != act.BodyAppendix {
447				t.Errorf("incorrect parsed body appendix:\n  expected: %q\n    actual: %q",
448					exp.BodyAppendix, act.BodyAppendix)
449			}
450		})
451	}
452}
453
454func assertPatchIdentity(t *testing.T, kind string, exp, act *PatchIdentity) {
455	switch {
456	case exp == nil && act == nil:
457	case exp == nil && act != nil:
458		t.Errorf("incorrect parsed %s: expected nil, but got %+v", kind, act)
459	case exp != nil && act == nil:
460		t.Errorf("incorrect parsed %s: expected %+v, but got nil", kind, exp)
461	case exp.Name != act.Name || exp.Email != act.Email:
462		t.Errorf("incorrect parsed %s, expected %+v, bot got %+v", kind, exp, act)
463	}
464}
465
466func TestCleanSubject(t *testing.T) {
467	expectedSubject := "A sample commit to test header parsing"
468
469	tests := map[string]struct {
470		Input   string
471		Mode    SubjectCleanMode
472		Prefix  string
473		Subject string
474	}{
475		"CleanAll/noPrefix": {
476			Input:   expectedSubject,
477			Mode:    SubjectCleanAll,
478			Subject: expectedSubject,
479		},
480		"CleanAll/patchPrefix": {
481			Input:   "[PATCH] " + expectedSubject,
482			Mode:    SubjectCleanAll,
483			Prefix:  "[PATCH] ",
484			Subject: expectedSubject,
485		},
486		"CleanAll/patchPrefixNoSpace": {
487			Input:   "[PATCH]" + expectedSubject,
488			Mode:    SubjectCleanAll,
489			Prefix:  "[PATCH]",
490			Subject: expectedSubject,
491		},
492		"CleanAll/patchPrefixContent": {
493			Input:   "[PATCH 3/7] " + expectedSubject,
494			Mode:    SubjectCleanAll,
495			Prefix:  "[PATCH 3/7] ",
496			Subject: expectedSubject,
497		},
498		"CleanAll/spacePrefix": {
499			Input:   "   " + expectedSubject,
500			Mode:    SubjectCleanAll,
501			Subject: expectedSubject,
502		},
503		"CleanAll/replyLowerPrefix": {
504			Input:   "re: " + expectedSubject,
505			Mode:    SubjectCleanAll,
506			Prefix:  "re: ",
507			Subject: expectedSubject,
508		},
509		"CleanAll/replyMixedPrefix": {
510			Input:   "Re: " + expectedSubject,
511			Mode:    SubjectCleanAll,
512			Prefix:  "Re: ",
513			Subject: expectedSubject,
514		},
515		"CleanAll/replyCapsPrefix": {
516			Input:   "RE: " + expectedSubject,
517			Mode:    SubjectCleanAll,
518			Prefix:  "RE: ",
519			Subject: expectedSubject,
520		},
521		"CleanAll/replyDoublePrefix": {
522			Input:   "Re: re: " + expectedSubject,
523			Mode:    SubjectCleanAll,
524			Prefix:  "Re: re: ",
525			Subject: expectedSubject,
526		},
527		"CleanAll/noPrefixSubjectHasRe": {
528			Input:   "Reimplement parsing",
529			Mode:    SubjectCleanAll,
530			Subject: "Reimplement parsing",
531		},
532		"CleanAll/patchPrefixSubjectHasRe": {
533			Input:   "[PATCH 1/2] Reimplement parsing",
534			Mode:    SubjectCleanAll,
535			Prefix:  "[PATCH 1/2] ",
536			Subject: "Reimplement parsing",
537		},
538		"CleanAll/unclosedPrefix": {
539			Input:   "[Just to annoy people",
540			Mode:    SubjectCleanAll,
541			Subject: "[Just to annoy people",
542		},
543		"CleanAll/multiplePrefix": {
544			Input:   " Re:Re: [PATCH 1/2][DRAFT] " + expectedSubject + "  ",
545			Mode:    SubjectCleanAll,
546			Prefix:  "Re:Re: [PATCH 1/2][DRAFT] ",
547			Subject: expectedSubject,
548		},
549		"CleanPatchOnly/patchPrefix": {
550			Input:   "[PATCH] " + expectedSubject,
551			Mode:    SubjectCleanPatchOnly,
552			Prefix:  "[PATCH] ",
553			Subject: expectedSubject,
554		},
555		"CleanPatchOnly/mixedPrefix": {
556			Input:   "[PATCH] [TICKET-123] " + expectedSubject,
557			Mode:    SubjectCleanPatchOnly,
558			Prefix:  "[PATCH] ",
559			Subject: "[TICKET-123] " + expectedSubject,
560		},
561		"CleanPatchOnly/multiplePrefix": {
562			Input:   "Re:Re: [PATCH 1/2][DRAFT] " + expectedSubject,
563			Mode:    SubjectCleanPatchOnly,
564			Prefix:  "Re:Re: [PATCH 1/2]",
565			Subject: "[DRAFT] " + expectedSubject,
566		},
567		"CleanWhitespace/leadingSpace": {
568			Input:   "    [PATCH] " + expectedSubject,
569			Mode:    SubjectCleanWhitespace,
570			Subject: "[PATCH] " + expectedSubject,
571		},
572		"CleanWhitespace/trailingSpace": {
573			Input:   "[PATCH] " + expectedSubject + "   ",
574			Mode:    SubjectCleanWhitespace,
575			Subject: "[PATCH] " + expectedSubject,
576		},
577	}
578
579	for name, test := range tests {
580		t.Run(name, func(t *testing.T) {
581			prefix, subject := cleanSubject(test.Input, test.Mode)
582			if prefix != test.Prefix {
583				t.Errorf("incorrect prefix: expected %q, actual %q", test.Prefix, prefix)
584			}
585			if subject != test.Subject {
586				t.Errorf("incorrect subject: expected %q, actual %q", test.Subject, subject)
587			}
588		})
589	}
590}