Monorepo for Tangled tangled.org
at sl/comment 415 lines 8.8 kB view raw
1package patchutil 2 3import ( 4 "errors" 5 "reflect" 6 "testing" 7 8 "tangled.org/core/types" 9) 10 11func TestIsPatchValid(t *testing.T) { 12 tests := []struct { 13 name string 14 patch string 15 expected error 16 }{ 17 { 18 name: `empty patch`, 19 patch: ``, 20 expected: EmptyPatchError, 21 }, 22 { 23 name: `single line patch`, 24 patch: `single line`, 25 expected: EmptyPatchError, 26 }, 27 { 28 name: `valid diff patch`, 29 patch: `diff --git a/file.txt b/file.txt 30index abc..def 100644 31--- a/file.txt 32+++ b/file.txt 33@@ -1,3 +1,3 @@ 34-old line 35+new line 36 context`, 37 expected: nil, 38 }, 39 { 40 name: `valid patch starting with ---`, 41 patch: `--- a/file.txt 42+++ b/file.txt 43@@ -1,3 +1,3 @@ 44-old line 45+new line 46 context`, 47 expected: nil, 48 }, 49 { 50 name: `valid patch starting with Index`, 51 patch: `Index: file.txt 52========== 53--- a/file.txt 54+++ b/file.txt 55@@ -1,3 +1,3 @@ 56-old line 57+new line 58 context`, 59 expected: nil, 60 }, 61 { 62 name: `valid patch starting with +++`, 63 patch: `+++ b/file.txt 64--- a/file.txt 65@@ -1,3 +1,3 @@ 66-old line 67+new line 68 context`, 69 expected: nil, 70 }, 71 { 72 name: `valid patch starting with @@`, 73 patch: `@@ -1,3 +1,3 @@ 74-old line 75+new line 76 context 77`, 78 expected: nil, 79 }, 80 { 81 name: `valid format patch`, 82 patch: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 83From: Author <author@example.com> 84Date: Wed, 16 Apr 2025 11:01:00 +0300 85Subject: [PATCH] Example patch 86 87diff --git a/file.txt b/file.txt 88index 123456..789012 100644 89--- a/file.txt 90+++ b/file.txt 91@@ -1 +1 @@ 92-old content 93+new content 94-- 952.48.1`, 96 expected: nil, 97 }, 98 { 99 name: `invalid format patch`, 100 patch: `From 1234567890123456789012345678901234567890 Mon Sep 17 00:00:00 2001 101From: Author <author@example.com> 102This is not a valid patch format`, 103 expected: FormatPatchError, 104 }, 105 { 106 name: `not a patch at all`, 107 patch: `This is 108just some 109random text 110that isn't a patch`, 111 expected: GenericPatchError, 112 }, 113 } 114 115 for _, tt := range tests { 116 t.Run(tt.name, func(t *testing.T) { 117 result := IsPatchValid(tt.patch) 118 if !errors.Is(result, tt.expected) { 119 t.Errorf("IsPatchValid() = %v, want %v", result, tt.expected) 120 } 121 }) 122 } 123} 124 125func TestSplitPatches(t *testing.T) { 126 tests := []struct { 127 name string 128 input string 129 expected []string 130 }{ 131 { 132 name: "Empty input", 133 input: "", 134 expected: []string{}, 135 }, 136 { 137 name: "No valid patches", 138 input: "This is not a \nJust some random text", 139 expected: []string{}, 140 }, 141 { 142 name: "Single patch", 143 input: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 144From: Author <author@example.com> 145Date: Wed, 16 Apr 2025 11:01:00 +0300 146Subject: [PATCH] Example patch 147 148diff --git a/file.txt b/file.txt 149index 123456..789012 100644 150--- a/file.txt 151+++ b/file.txt 152@@ -1 +1 @@ 153-old content 154+new content 155-- 1562.48.1`, 157 expected: []string{ 158 `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 159From: Author <author@example.com> 160Date: Wed, 16 Apr 2025 11:01:00 +0300 161Subject: [PATCH] Example patch 162 163diff --git a/file.txt b/file.txt 164index 123456..789012 100644 165--- a/file.txt 166+++ b/file.txt 167@@ -1 +1 @@ 168-old content 169+new content 170-- 1712.48.1`, 172 }, 173 }, 174 { 175 name: "Two patches", 176 input: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 177From: Author <author@example.com> 178Date: Wed, 16 Apr 2025 11:01:00 +0300 179Subject: [PATCH 1/2] First patch 180 181diff --git a/file1.txt b/file1.txt 182index 123456..789012 100644 183--- a/file1.txt 184+++ b/file1.txt 185@@ -1 +1 @@ 186-old content 187+new content 188-- 1892.48.1 190From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 191From: Author <author@example.com> 192Date: Wed, 16 Apr 2025 11:03:11 +0300 193Subject: [PATCH 2/2] Second patch 194 195diff --git a/file2.txt b/file2.txt 196index abcdef..ghijkl 100644 197--- a/file2.txt 198+++ b/file2.txt 199@@ -1 +1 @@ 200-foo bar 201+baz qux 202-- 2032.48.1`, 204 expected: []string{ 205 `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 206From: Author <author@example.com> 207Date: Wed, 16 Apr 2025 11:01:00 +0300 208Subject: [PATCH 1/2] First patch 209 210diff --git a/file1.txt b/file1.txt 211index 123456..789012 100644 212--- a/file1.txt 213+++ b/file1.txt 214@@ -1 +1 @@ 215-old content 216+new content 217-- 2182.48.1`, 219 `From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 220From: Author <author@example.com> 221Date: Wed, 16 Apr 2025 11:03:11 +0300 222Subject: [PATCH 2/2] Second patch 223 224diff --git a/file2.txt b/file2.txt 225index abcdef..ghijkl 100644 226--- a/file2.txt 227+++ b/file2.txt 228@@ -1 +1 @@ 229-foo bar 230+baz qux 231-- 2322.48.1`, 233 }, 234 }, 235 { 236 name: "Patches with additional text between them", 237 input: `Some text before the patches 238 239From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 240From: Author <author@example.com> 241Subject: [PATCH] First patch 242 243diff content here 244-- 2452.48.1 246 247Some text between patches 248 249From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 250From: Author <author@example.com> 251Subject: [PATCH] Second patch 252 253more diff content 254-- 2552.48.1 256 257Text after patches`, 258 expected: []string{ 259 `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 260From: Author <author@example.com> 261Subject: [PATCH] First patch 262 263diff content here 264-- 2652.48.1 266 267Some text between patches`, 268 `From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 269From: Author <author@example.com> 270Subject: [PATCH] Second patch 271 272more diff content 273-- 2742.48.1 275 276Text after patches`, 277 }, 278 }, 279 { 280 name: "Patches with whitespace padding", 281 input: ` 282 283From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 284From: Author <author@example.com> 285Subject: Patch 286 287content 288-- 2892.48.1 290 291 292From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 293From: Author <author@example.com> 294Subject: Another patch 295 296content 297-- 2982.48.1 299 `, 300 expected: []string{ 301 `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 302From: Author <author@example.com> 303Subject: Patch 304 305content 306-- 3072.48.1`, 308 `From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 309From: Author <author@example.com> 310Subject: Another patch 311 312content 313-- 3142.48.1`, 315 }, 316 }, 317 } 318 319 for _, tt := range tests { 320 t.Run(tt.name, func(t *testing.T) { 321 result := splitFormatPatch(tt.input) 322 if !reflect.DeepEqual(result, tt.expected) { 323 t.Errorf("splitPatches() = %v, want %v", result, tt.expected) 324 } 325 }) 326 } 327} 328 329func TestIsFormatPatch(t *testing.T) { 330 tests := []struct { 331 name string 332 patch string 333 want bool 334 }{ 335 // fast path: sentinel timestamp 336 { 337 name: "sentinel timestamp", 338 patch: "From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001\nFrom: Author <a@example.com>\n", 339 want: true, 340 }, 341 // header-count path: various two-header combinations 342 { 343 name: "From and Date headers", 344 patch: "From: Author <a@example.com>\nDate: Mon, 1 Jan 2024 00:00:00 +0000\n", 345 want: true, 346 }, 347 { 348 name: "From and Subject headers", 349 patch: "From: Author <a@example.com>\nSubject: [PATCH] fix thing\n", 350 want: true, 351 }, 352 { 353 name: "Subject and Date headers", 354 patch: "Subject: [PATCH] fix thing\nDate: Mon, 1 Jan 2024 00:00:00 +0000\n", 355 want: true, 356 }, 357 { 358 name: "commit and From headers", 359 patch: "commit abc123\nFrom: Author <a@example.com>\n", 360 want: true, 361 }, 362 // boundary: headers at lines 9 and 10 (0-indexed 8 and 9, last scanned) 363 { 364 name: "headers at lines 9 and 10", 365 patch: "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nFrom: Author <a@example.com>\nSubject: [PATCH] fix\n", 366 want: true, 367 }, 368 // false cases 369 { 370 name: "empty string", 371 patch: "", 372 want: false, 373 }, 374 { 375 name: "single line", 376 patch: "From: Author <a@example.com>", 377 want: false, 378 }, 379 { 380 name: "plain diff", 381 patch: "diff --git a/f.txt b/f.txt\n--- a/f.txt\n+++ b/f.txt\n", 382 want: false, 383 }, 384 { 385 name: "From prefix but wrong timestamp falls through to header count of 1", 386 patch: "From 3c5035488318164b81f60fe3adcd6c9199d76331 Tue Oct 10 12:00:00 2023\nFrom: Author <a@example.com>\n", 387 want: false, 388 }, 389 { 390 name: "only one recognized header", 391 patch: "Subject: [PATCH] fix thing\nsome other line\n", 392 want: false, 393 }, 394 { 395 name: "headers pushed past line 10 are not counted", 396 patch: "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nFrom: Author <a@example.com>\nSubject: [PATCH] fix\n", 397 want: false, 398 }, 399 } 400 401 for _, tt := range tests { 402 t.Run(tt.name, func(t *testing.T) { 403 if got := IsFormatPatch(tt.patch); got != tt.want { 404 t.Errorf("IsFormatPatch() = %v, want %v", got, tt.want) 405 } 406 }) 407 } 408} 409 410func TestImplsInterfaces(t *testing.T) { 411 id := &InterdiffResult{} 412 _ = isDiffsRenderer(id) 413} 414 415func isDiffsRenderer[S types.DiffRenderer](S) bool { return true }