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