github.com/friedemannf/reviewdog@v0.14.0/diff/parse_test.go (about) 1 package diff 2 3 import ( 4 "bufio" 5 "encoding/json" 6 "io" 7 "os" 8 "path/filepath" 9 "reflect" 10 "strings" 11 "testing" 12 ) 13 14 //go:generate go run testdata/gen.go 15 16 func TestParseMultiFile(t *testing.T) { 17 files, err := filepath.Glob("testdata/*.diff") 18 if err != nil { 19 t.Fatal(err) 20 } 21 for _, fname := range files { 22 t.Log(fname) 23 f, err := os.Open(fname) 24 if err != nil { 25 t.Fatal(err) 26 } 27 difffiles, err := ParseMultiFile(f) 28 if err != nil { 29 t.Errorf("%v: %v", fname, err) 30 } 31 32 wantfile, err := os.Open(fname + ".json") 33 if err != nil { 34 t.Fatal(err) 35 } 36 dec := json.NewDecoder(wantfile) 37 var want []*FileDiff 38 if err := dec.Decode(&want); err != nil { 39 t.Fatal(err) 40 } 41 if !reflect.DeepEqual(difffiles, want) { 42 l := len(difffiles) 43 if len(want) > l { 44 l = len(want) 45 } 46 for i := 0; i < l; i++ { 47 var a, b *FileDiff 48 if i < len(want) { 49 a = want[i] 50 } 51 if i < len(difffiles) { 52 b = difffiles[i] 53 } 54 t.Errorf("want %#v, got %#v", a, b) 55 } 56 } 57 58 wantfile.Close() 59 f.Close() 60 } 61 } 62 63 func TestParseMultiFile_sample(t *testing.T) { 64 content := `--- sample.old.txt 2016-10-13 05:09:35.820791185 +0900 65 +++ sample.new.txt 2016-10-13 05:15:26.839245048 +0900 66 @@ -1,3 +1,4 @@ 67 unchanged, contextual line 68 -deleted line 69 +added line 70 +added line 71 unchanged, contextual line 72 --- nonewline.old.txt 2016-10-13 15:34:14.931778318 +0900 73 +++ nonewline.new.txt 2016-10-13 15:34:14.868444672 +0900 74 @@ -1,4 +1,4 @@ 75 " vim: nofixeol noendofline 76 No newline at end of both the old and new file 77 -a 78 -a 79 \ No newline at end of file 80 +b 81 +b 82 \ No newline at end of file 83 ` 84 85 got, err := ParseMultiFile(strings.NewReader(content)) 86 if err != nil { 87 t.Fatal(err) 88 } 89 want := []*FileDiff{ 90 { 91 PathOld: "sample.old.txt", 92 PathNew: "sample.new.txt", 93 TimeOld: "2016-10-13 05:09:35.820791185 +0900", 94 TimeNew: "2016-10-13 05:15:26.839245048 +0900", 95 Hunks: []*Hunk{ 96 { 97 StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4, 98 Lines: []*Line{ 99 {Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1}, 100 {Type: 2, Content: "deleted line", LnumDiff: 2, LnumOld: 2, LnumNew: 0}, 101 {Type: 1, Content: "added line", LnumDiff: 3, LnumOld: 0, LnumNew: 2}, 102 {Type: 1, Content: "added line", LnumDiff: 4, LnumOld: 0, LnumNew: 3}, 103 {Type: 0, Content: "unchanged, contextual line", LnumDiff: 5, LnumOld: 3, LnumNew: 4}, 104 }, 105 }, 106 }, 107 }, 108 { 109 PathOld: "nonewline.old.txt", 110 PathNew: "nonewline.new.txt", 111 TimeOld: "2016-10-13 15:34:14.931778318 +0900", 112 TimeNew: "2016-10-13 15:34:14.868444672 +0900", 113 Hunks: []*Hunk{ 114 { 115 StartLineOld: 1, LineLengthOld: 4, StartLineNew: 1, LineLengthNew: 4, 116 Lines: []*Line{ 117 {Type: 0, Content: "\" vim: nofixeol noendofline", LnumDiff: 1, LnumOld: 1, LnumNew: 1}, 118 {Type: 0, Content: "No newline at end of both the old and new file", LnumDiff: 2, LnumOld: 2, LnumNew: 2}, 119 {Type: 2, Content: "a", LnumDiff: 3, LnumOld: 3, LnumNew: 0}, 120 {Type: 2, Content: "a", LnumDiff: 4, LnumOld: 4, LnumNew: 0}, 121 {Type: 1, Content: "b", LnumDiff: 5, LnumOld: 0, LnumNew: 3}, 122 {Type: 1, Content: "b", LnumDiff: 6, LnumOld: 0, LnumNew: 4}, 123 }, 124 }, 125 }, 126 }, 127 } 128 if !reflect.DeepEqual(got, want) { 129 t.Errorf("error. in:\n%v", content) 130 for _, fd := range got { 131 t.Logf("FileDiff: %#v\n", fd) 132 for _, h := range fd.Hunks { 133 t.Logf(" Hunk%#v\n", h) 134 for _, l := range h.Lines { 135 t.Logf(" Line%#v\n", l) 136 } 137 } 138 } 139 } 140 } 141 142 func TestFileParser_Parse(t *testing.T) { 143 tests := []struct { 144 in string 145 want *FileDiff 146 }{ 147 { 148 in: "", 149 want: nil, 150 }, 151 { 152 in: `diff --git a/empty.txt b/empty.txtq 153 deleted file mode 100644 154 index e69de29..0000000 155 `, 156 want: &FileDiff{ 157 Extended: []string{ 158 "diff --git a/empty.txt b/empty.txtq", 159 "deleted file mode 100644", 160 "index e69de29..0000000", 161 }, 162 }, 163 }, 164 { 165 in: `--- sample.old.txt 2016-10-13 05:09:35.820791185 +0900 166 +++ sample.new.txt 2016-10-13 05:15:26.839245048 +0900 167 @@ -1,3 +1,4 @@ 168 unchanged, contextual line 169 -deleted line 170 +added line 171 +added line 172 unchanged, contextual line 173 `, 174 want: &FileDiff{ 175 PathOld: "sample.old.txt", 176 PathNew: "sample.new.txt", 177 TimeOld: "2016-10-13 05:09:35.820791185 +0900", 178 TimeNew: "2016-10-13 05:15:26.839245048 +0900", 179 Hunks: []*Hunk{ 180 { 181 StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4, 182 Lines: []*Line{ 183 {Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1}, 184 {Type: 2, Content: "deleted line", LnumDiff: 2, LnumOld: 2, LnumNew: 0}, 185 {Type: 1, Content: "added line", LnumDiff: 3, LnumOld: 0, LnumNew: 2}, 186 {Type: 1, Content: "added line", LnumDiff: 4, LnumOld: 0, LnumNew: 3}, 187 {Type: 0, Content: "unchanged, contextual line", LnumDiff: 5, LnumOld: 3, LnumNew: 4}, 188 }, 189 }, 190 }, 191 }, 192 }, 193 { 194 in: `--- sample.old.txt 2016-10-13 05:09:35.820791185 +0900 195 +++ sample.new.txt 2016-10-13 05:15:26.839245048 +0900 196 @@ -1,1 +1,1 @@ 197 unchanged, contextual line 198 @@ -2,1 +2,1 @@ 199 unchanged, contextual line 200 `, 201 want: &FileDiff{ 202 PathOld: "sample.old.txt", 203 PathNew: "sample.new.txt", 204 TimeOld: "2016-10-13 05:09:35.820791185 +0900", 205 TimeNew: "2016-10-13 05:15:26.839245048 +0900", 206 Hunks: []*Hunk{ 207 { 208 StartLineOld: 1, LineLengthOld: 1, StartLineNew: 1, LineLengthNew: 1, 209 Lines: []*Line{ 210 {Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1}, 211 }, 212 }, 213 { 214 StartLineOld: 2, LineLengthOld: 1, StartLineNew: 2, LineLengthNew: 1, 215 Lines: []*Line{ 216 {Type: 0, Content: "unchanged, contextual line", LnumDiff: 3, LnumOld: 2, LnumNew: 2}, 217 }, 218 }, 219 }, 220 }, 221 }, 222 } 223 for _, tt := range tests { 224 p := &fileParser{r: bufio.NewReader(strings.NewReader(tt.in))} 225 got, err := p.Parse() 226 if err != nil { 227 t.Errorf("got error %v for in:\n %v", err, tt.in) 228 } 229 if !reflect.DeepEqual(got, tt.want) { 230 t.Errorf("fileParser.Parse() = %#v, want %#v\nin: %v", got, tt.want, tt.in) 231 t.Log("got:") 232 for _, h := range got.Hunks { 233 for _, l := range h.Lines { 234 t.Logf("%#v", l) 235 } 236 } 237 t.Log("want:") 238 for _, h := range tt.want.Hunks { 239 for _, l := range h.Lines { 240 t.Logf("%#v", l) 241 } 242 } 243 } 244 } 245 } 246 247 func TestParseFileHeader(t *testing.T) { 248 tests := []struct { 249 in string 250 filename string 251 timestamp string 252 }{ 253 { 254 in: "--- sample.old.txt 2016-10-13 05:09:35.820791185 +0900", 255 filename: "sample.old.txt", 256 timestamp: "2016-10-13 05:09:35.820791185 +0900", 257 }, 258 { 259 in: "+++ sample.old.txt", 260 filename: "sample.old.txt", 261 timestamp: "", 262 }, 263 } 264 for _, tt := range tests { 265 gotf, gott := parseFileHeader(tt.in) 266 if gotf != tt.filename || gott != tt.timestamp { 267 t.Errorf("parseFileHeader(%v) = (%v, %v), want (%v, %v)", tt.in, gotf, gott, tt.filename, tt.timestamp) 268 } 269 } 270 } 271 272 func TestParseExtendedHeader(t *testing.T) { 273 tests := []struct { 274 in string 275 want []string 276 }{ 277 { 278 in: `diff --git a/sample.txt b/sample.txt 279 index a949a96..769bdae 100644 280 --- a/sample.old.txt 281 +++ b/sample.new.txt 282 @@ -1,3 +1,4 @@ 283 `, 284 want: []string{"diff --git a/sample.txt b/sample.txt", "index a949a96..769bdae 100644"}, 285 }, 286 { 287 in: `diff --git a/sample.txt b/sample.txt 288 deleted file mode 100644 289 index e69de29..0000000 290 `, 291 want: []string{"diff --git a/sample.txt b/sample.txt", "deleted file mode 100644", "index e69de29..0000000"}, 292 }, 293 { 294 in: `diff --git a/sample.txt b/sample.txt 295 new file mode 100644 296 index 0000000..e69de29 297 diff --git a/sample2.txt b/sample2.txt 298 new file mode 100644 299 index 0000000..ee946eb 300 `, 301 want: []string{"diff --git a/sample.txt b/sample.txt", "new file mode 100644", "index 0000000..e69de29"}, 302 }, 303 { 304 in: `--- a/sample.old.txt 305 +++ b/sample.new.txt 306 @@ -1,3 +1,4 @@ 307 `, 308 want: nil, 309 }, 310 } 311 for _, tt := range tests { 312 got := parseExtendedHeader(bufio.NewReader(strings.NewReader(tt.in))) 313 if !reflect.DeepEqual(got, tt.want) { 314 t.Errorf("in:\n%v\ngot:\n%v\nwant:\n%v", tt.in, strings.Join(got, "\n"), strings.Join(tt.want, "\n")) 315 } 316 } 317 } 318 319 func TestHunkParser_Parse(t *testing.T) { 320 tests := []struct { 321 in string 322 lnumdiff int 323 want *Hunk 324 }{ 325 { 326 in: `@@ -1,3 +1,4 @@ optional section heading 327 unchanged, contextual line 328 -deleted line 329 +added line 330 +added line 331 unchanged, contextual line 332 `, 333 want: &Hunk{ 334 StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4, 335 Section: "optional section heading", 336 Lines: []*Line{ 337 {Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1}, 338 {Type: 2, Content: "deleted line", LnumDiff: 2, LnumOld: 2, LnumNew: 0}, 339 {Type: 1, Content: "added line", LnumDiff: 3, LnumOld: 0, LnumNew: 2}, 340 {Type: 1, Content: "added line", LnumDiff: 4, LnumOld: 0, LnumNew: 3}, 341 {Type: 0, Content: "unchanged, contextual line", LnumDiff: 5, LnumOld: 3, LnumNew: 4}, 342 }, 343 }, 344 }, 345 { 346 in: `@@ -1,3 +1,4 @@ 347 unchanged, contextual line 348 -deleted line 349 +added line 350 +added line 351 unchanged, contextual line 352 @@ -1,3 +1,4 @@ 353 `, 354 lnumdiff: 14, 355 want: &Hunk{ 356 StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4, 357 Section: "", 358 Lines: []*Line{ 359 {Type: 0, Content: "unchanged, contextual line", LnumDiff: 15, LnumOld: 1, LnumNew: 1}, 360 {Type: 2, Content: "deleted line", LnumDiff: 16, LnumOld: 2, LnumNew: 0}, 361 {Type: 1, Content: "added line", LnumDiff: 17, LnumOld: 0, LnumNew: 2}, 362 {Type: 1, Content: "added line", LnumDiff: 18, LnumOld: 0, LnumNew: 3}, 363 {Type: 0, Content: "unchanged, contextual line", LnumDiff: 19, LnumOld: 3, LnumNew: 4}, 364 }, 365 }, 366 }, 367 } 368 for _, tt := range tests { 369 got, err := (&hunkParser{r: bufio.NewReader(strings.NewReader(tt.in)), lnumdiff: tt.lnumdiff}).Parse() 370 if err != nil { 371 t.Errorf("hunkParser.Parse(%v) got an unexpected err %v", tt.in, err) 372 } 373 if !reflect.DeepEqual(got, tt.want) { 374 t.Errorf("hunkParser.Parse(%v) = \n%#v\n, want \n%#v", tt.in, got, tt.want) 375 t.Logf("got lines:") 376 for _, l := range got.Lines { 377 t.Logf("%#v", l) 378 } 379 t.Logf("want lines:") 380 for _, l := range tt.want.Lines { 381 t.Logf("%#v", l) 382 } 383 } 384 } 385 } 386 387 func TestParseHunkRange(t *testing.T) { 388 tests := []struct { 389 in string 390 want *hunkrange 391 }{ 392 { 393 in: "@@ -1,3 +1,4 @@", 394 want: &hunkrange{lold: 1, sold: 3, lnew: 1, snew: 4}, 395 }, 396 { 397 in: "@@ -1 +1 @@", 398 want: &hunkrange{lold: 1, sold: 1, lnew: 1, snew: 1}, 399 }, 400 { 401 in: "@@ -1,3 +1,4 @@ optional section", 402 want: &hunkrange{lold: 1, sold: 3, lnew: 1, snew: 4, section: "optional section"}, 403 }, 404 } 405 for _, tt := range tests { 406 got, err := parseHunkRange(tt.in) 407 if err != nil { 408 t.Errorf("parseHunkRange(%v) got an unexpected err %v", tt.in, err) 409 } 410 if !reflect.DeepEqual(got, tt.want) { 411 t.Errorf("parseHunkRange(%v) = %#v, want %#v", tt.in, got, tt.want) 412 } 413 } 414 } 415 416 func TestParseLS(t *testing.T) { 417 tests := []struct { 418 in string 419 l int 420 s int 421 }{ 422 {in: "1,3", l: 1, s: 3}, 423 {in: "14", l: 14, s: 1}, 424 } 425 for _, tt := range tests { 426 gotl, gots, err := parseLS(tt.in) 427 if err != nil { 428 t.Errorf("parseLS(%v) got an unexpected err %v", tt.in, err) 429 } 430 if gotl != tt.l || gots != tt.s { 431 t.Errorf("parseLS(%v) = (%v, %v, _), want (%v, %v, _)", tt.in, gotl, gots, tt.l, tt.s) 432 } 433 } 434 } 435 436 func TestReadline(t *testing.T) { 437 text := `line1 438 line2 439 line3` 440 r := bufio.NewReader(strings.NewReader(text)) 441 { 442 got, err := readline(r) 443 if err != nil { 444 t.Error(err) 445 } 446 if got != "line1" { 447 t.Errorf("got %v, want line1", got) 448 } 449 } 450 { 451 got, err := readline(r) 452 if err != nil { 453 t.Error(err) 454 } 455 if got != "line2" { 456 t.Errorf("got %v, want line2", got) 457 } 458 } 459 { 460 got, err := readline(r) 461 if err != nil { 462 t.Error(err) 463 } 464 if got != "line3" { 465 t.Errorf("got %v, want line3", got) 466 } 467 } 468 { 469 if _, err := readline(r); err != io.EOF { 470 t.Errorf("got err %v, want io.EOF", err) 471 } 472 } 473 } 474 475 func TestUnquoteCStyle(t *testing.T) { 476 tests := []struct { 477 in string 478 out string 479 }{ 480 {in: `no need to unquote`, out: `no need to unquote`}, 481 {in: `"C-escapes \a\b\t\n\v\f\r\"\\"`, out: "C-escapes \a\b\t\n\v\f\r\"\\"}, 482 483 // from https://github.com/git/git/blob/041f5ea1cf987a4068ef5f39ba0a09be85952064/t/t3902-quoted.sh#L48-L76 484 {in: `Name`, out: `Name`}, 485 {in: `"Name and a\nLF"`, out: "Name and a\nLF"}, 486 {in: `"Name and an\tHT"`, out: "Name and an\tHT"}, 487 {in: `"Name\""`, out: `Name"`}, 488 {in: `With SP in it`, out: `With SP in it`}, 489 {in: `"\346\277\261\351\207\216\t\347\264\224"`, out: "濱野\t純"}, 490 {in: `"\346\277\261\351\207\216\n\347\264\224"`, out: "濱野\n純"}, 491 {in: `"\346\277\261\351\207\216 \347\264\224"`, out: `濱野 純`}, 492 {in: `"\346\277\261\351\207\216\"\347\264\224"`, out: "濱野\"純"}, 493 {in: `"\346\277\261\351\207\216/file"`, out: `濱野/file`}, 494 {in: `"\346\277\261\351\207\216\347\264\224"`, out: `濱野純`}, 495 496 // Edge cases of ill-formed diff file name. 497 {in: `\347\264\224`, out: `\347\264\224`}, // no need to unquote 498 {in: `"\34a"`, out: "34a"}, 499 {in: `"\14"`, out: `14`}, 500 } 501 502 for _, tt := range tests { 503 if got := unquoteCStyle(tt.in); got != tt.out { 504 t.Errorf("unquoteCStyle(%q) = %q, want %q", tt.in, got, tt.out) 505 } 506 } 507 }