package patchutil import ( "errors" "reflect" "testing" "tangled.org/core/types" ) func TestIsPatchValid(t *testing.T) { tests := []struct { name string patch string expected error }{ { name: `empty patch`, patch: ``, expected: EmptyPatchError, }, { name: `single line patch`, patch: `single line`, expected: EmptyPatchError, }, { name: `valid diff patch`, patch: `diff --git a/file.txt b/file.txt index abc..def 100644 --- a/file.txt +++ b/file.txt @@ -1,3 +1,3 @@ -old line +new line context`, expected: nil, }, { name: `valid patch starting with ---`, patch: `--- a/file.txt +++ b/file.txt @@ -1,3 +1,3 @@ -old line +new line context`, expected: nil, }, { name: `valid patch starting with Index`, patch: `Index: file.txt ========== --- a/file.txt +++ b/file.txt @@ -1,3 +1,3 @@ -old line +new line context`, expected: nil, }, { name: `valid patch starting with +++`, patch: `+++ b/file.txt --- a/file.txt @@ -1,3 +1,3 @@ -old line +new line context`, expected: nil, }, { name: `valid patch starting with @@`, patch: `@@ -1,3 +1,3 @@ -old line +new line context `, expected: nil, }, { name: `valid format patch`, patch: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 From: Author Date: Wed, 16 Apr 2025 11:01:00 +0300 Subject: [PATCH] Example patch diff --git a/file.txt b/file.txt index 123456..789012 100644 --- a/file.txt +++ b/file.txt @@ -1 +1 @@ -old content +new content -- 2.48.1`, expected: nil, }, { name: `invalid format patch`, patch: `From 1234567890123456789012345678901234567890 Mon Sep 17 00:00:00 2001 From: Author This is not a valid patch format`, expected: FormatPatchError, }, { name: `not a patch at all`, patch: `This is just some random text that isn't a patch`, expected: GenericPatchError, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := IsPatchValid(tt.patch) if !errors.Is(result, tt.expected) { t.Errorf("IsPatchValid() = %v, want %v", result, tt.expected) } }) } } func TestSplitPatches(t *testing.T) { tests := []struct { name string input string expected []string }{ { name: "Empty input", input: "", expected: []string{}, }, { name: "No valid patches", input: "This is not a \nJust some random text", expected: []string{}, }, { name: "Single patch", input: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 From: Author Date: Wed, 16 Apr 2025 11:01:00 +0300 Subject: [PATCH] Example patch diff --git a/file.txt b/file.txt index 123456..789012 100644 --- a/file.txt +++ b/file.txt @@ -1 +1 @@ -old content +new content -- 2.48.1`, expected: []string{ `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 From: Author Date: Wed, 16 Apr 2025 11:01:00 +0300 Subject: [PATCH] Example patch diff --git a/file.txt b/file.txt index 123456..789012 100644 --- a/file.txt +++ b/file.txt @@ -1 +1 @@ -old content +new content -- 2.48.1`, }, }, { name: "Two patches", input: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 From: Author Date: Wed, 16 Apr 2025 11:01:00 +0300 Subject: [PATCH 1/2] First patch diff --git a/file1.txt b/file1.txt index 123456..789012 100644 --- a/file1.txt +++ b/file1.txt @@ -1 +1 @@ -old content +new content -- 2.48.1 From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 From: Author Date: Wed, 16 Apr 2025 11:03:11 +0300 Subject: [PATCH 2/2] Second patch diff --git a/file2.txt b/file2.txt index abcdef..ghijkl 100644 --- a/file2.txt +++ b/file2.txt @@ -1 +1 @@ -foo bar +baz qux -- 2.48.1`, expected: []string{ `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 From: Author Date: Wed, 16 Apr 2025 11:01:00 +0300 Subject: [PATCH 1/2] First patch diff --git a/file1.txt b/file1.txt index 123456..789012 100644 --- a/file1.txt +++ b/file1.txt @@ -1 +1 @@ -old content +new content -- 2.48.1`, `From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 From: Author Date: Wed, 16 Apr 2025 11:03:11 +0300 Subject: [PATCH 2/2] Second patch diff --git a/file2.txt b/file2.txt index abcdef..ghijkl 100644 --- a/file2.txt +++ b/file2.txt @@ -1 +1 @@ -foo bar +baz qux -- 2.48.1`, }, }, { name: "Patches with additional text between them", input: `Some text before the patches From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 From: Author Subject: [PATCH] First patch diff content here -- 2.48.1 Some text between patches From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 From: Author Subject: [PATCH] Second patch more diff content -- 2.48.1 Text after patches`, expected: []string{ `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 From: Author Subject: [PATCH] First patch diff content here -- 2.48.1 Some text between patches`, `From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 From: Author Subject: [PATCH] Second patch more diff content -- 2.48.1 Text after patches`, }, }, { name: "Patches with whitespace padding", input: ` From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 From: Author Subject: Patch content -- 2.48.1 From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 From: Author Subject: Another patch content -- 2.48.1 `, expected: []string{ `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 From: Author Subject: Patch content -- 2.48.1`, `From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 From: Author Subject: Another patch content -- 2.48.1`, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := splitFormatPatch(tt.input) if !reflect.DeepEqual(result, tt.expected) { t.Errorf("splitPatches() = %v, want %v", result, tt.expected) } }) } } func TestIsFormatPatch(t *testing.T) { tests := []struct { name string patch string want bool }{ // fast path: sentinel timestamp { name: "sentinel timestamp", patch: "From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001\nFrom: Author \n", want: true, }, // header-count path: various two-header combinations { name: "From and Date headers", patch: "From: Author \nDate: Mon, 1 Jan 2024 00:00:00 +0000\n", want: true, }, { name: "From and Subject headers", patch: "From: Author \nSubject: [PATCH] fix thing\n", want: true, }, { name: "Subject and Date headers", patch: "Subject: [PATCH] fix thing\nDate: Mon, 1 Jan 2024 00:00:00 +0000\n", want: true, }, { name: "commit and From headers", patch: "commit abc123\nFrom: Author \n", want: true, }, // boundary: headers at lines 9 and 10 (0-indexed 8 and 9, last scanned) { name: "headers at lines 9 and 10", patch: "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nFrom: Author \nSubject: [PATCH] fix\n", want: true, }, // false cases { name: "empty string", patch: "", want: false, }, { name: "single line", patch: "From: Author ", want: false, }, { name: "plain diff", patch: "diff --git a/f.txt b/f.txt\n--- a/f.txt\n+++ b/f.txt\n", want: false, }, { name: "From prefix but wrong timestamp falls through to header count of 1", patch: "From 3c5035488318164b81f60fe3adcd6c9199d76331 Tue Oct 10 12:00:00 2023\nFrom: Author \n", want: false, }, { name: "only one recognized header", patch: "Subject: [PATCH] fix thing\nsome other line\n", want: false, }, { name: "headers pushed past line 10 are not counted", patch: "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nFrom: Author \nSubject: [PATCH] fix\n", want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsFormatPatch(tt.patch); got != tt.want { t.Errorf("IsFormatPatch() = %v, want %v", got, tt.want) } }) } } func TestImplsInterfaces(t *testing.T) { id := &InterdiffResult{} _ = isDiffsRenderer(id) } func isDiffsRenderer[S types.DiffRenderer](S) bool { return true }