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