Skip to content

Commit 7b9443b

Browse files
committed
Add tests for ParseTextFragments
Covers that multiple fragments actually works and the unique error contidions, but otherwise relies on the lower level tests for correctness. Also update some comments and the README based on observations from writing/debugging these tests.
1 parent d32b889 commit 7b9443b

File tree

3 files changed

+176
-13
lines changed

3 files changed

+176
-13
lines changed

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,25 @@ In development, most functionality is currently missing, incomplete, or broken.
3434

3535
[apply.c]: https://github.com/git/git/blob/master/apply.c
3636

37-
## Known Issues and Differences From Git
37+
## Differences From Git
3838

39-
1. Certain types of invalid input that I believe are accepted by `git apply`
40-
generate errors. These include:
39+
1. Certain types of invalid input that are accepted by `git apply` generate
40+
errors. These include:
4141

4242
- Numbers immediately followed by non-numeric characters
4343
- Trailing characters on a line after valid or expected content
4444

45-
2. The translation from C to Go may have introduced inconsistencies in the way
45+
2. Errors for invalid input are generally more verbose and specific than those
46+
from `git apply`.
47+
48+
3. The translation from C to Go may have introduced inconsistencies in the way
4649
Unicode file names are handled; these are bugs, so please report any issues
4750
of this type.
4851

49-
3. When reading headers, there is no validation that OIDs present on an `index`
52+
4. When reading headers, there is no validation that OIDs present on an `index`
5053
line are shorter than or equal to the maximum hash length, as this requires
5154
knowing if the repository used SHA1 or SHA256 hashes.
5255

53-
4. When reading "traditional" patches (those not produced by `git`), prefixes
54-
are not stripped from file names (`git apply` attempts to remove prefixes
55-
that match the current repository directory/prefix.)
56+
5. When reading "traditional" patches (those not produced by `git`), prefixes
57+
are not stripped from file names; `git apply` attempts to remove prefixes
58+
that match the current repository directory/prefix.

gitdiff/parser.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func (p *parser) ParseTextFragments(f *File) (n int, err error) {
134134
}
135135

136136
if err := p.ParseTextChunk(frag); err != nil {
137-
return n, nil
137+
return n, err
138138
}
139139

140140
f.Fragments = append(f.Fragments, frag)
@@ -277,6 +277,11 @@ func (p *parser) ParseTextChunk(frag *Fragment) error {
277277
last.Line = strings.TrimSuffix(last.Line, "\n")
278278
break
279279
}
280+
// TODO(bkeyes): if this is because we hit the next header, it
281+
// would be helpful to return the miscounts line error. We could
282+
// either test for the common headers ("@@ -", "diff --git") or
283+
// assume any invalid op ends the fragment; git returns the same
284+
// generic error in all cases so either is compatible
280285
return p.Errorf(0, "invalid line operation: %q", op)
281286
}
282287

gitdiff/parser_text_test.go

Lines changed: 159 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package gitdiff
22

33
import (
4+
"io"
45
"reflect"
56
"testing"
67
)
@@ -55,8 +56,8 @@ func TestParseTextFragmentHeader(t *testing.T) {
5556

5657
frag, err := p.ParseTextFragmentHeader()
5758
if test.Err {
58-
if err == nil {
59-
t.Fatalf("expected error parsing header, but got nil")
59+
if err == nil || err == io.EOF {
60+
t.Fatalf("expected error parsing header, but got %v", err)
6061
}
6162
return
6263
}
@@ -152,6 +153,33 @@ func TestParseTextChunk(t *testing.T) {
152153
TrailingContext: 1,
153154
},
154155
},
156+
"middleContext": {
157+
Input: ` context line
158+
-old line 1
159+
context line
160+
+new line 1
161+
context line
162+
`,
163+
Fragment: Fragment{
164+
OldLines: 4,
165+
NewLines: 4,
166+
},
167+
Output: &Fragment{
168+
OldLines: 4,
169+
NewLines: 4,
170+
Lines: []FragmentLine{
171+
{OpContext, "context line\n"},
172+
{OpDelete, "old line 1\n"},
173+
{OpContext, "context line\n"},
174+
{OpAdd, "new line 1\n"},
175+
{OpContext, "context line\n"},
176+
},
177+
LinesDeleted: 1,
178+
LinesAdded: 1,
179+
LeadingContext: 1,
180+
TrailingContext: 1,
181+
},
182+
},
155183
"deleteFinalNewline": {
156184
Input: ` context line
157185
-old line 1
@@ -298,8 +326,8 @@ func TestParseTextChunk(t *testing.T) {
298326
frag := test.Fragment
299327
err := p.ParseTextChunk(&frag)
300328
if test.Err {
301-
if err == nil {
302-
t.Fatalf("expected error parsing text chunk, but got nil")
329+
if err == nil || err == io.EOF {
330+
t.Fatalf("expected error parsing text chunk, but got %v", err)
303331
}
304332
return
305333
}
@@ -313,3 +341,130 @@ func TestParseTextChunk(t *testing.T) {
313341
})
314342
}
315343
}
344+
345+
func TestParseTextFragments(t *testing.T) {
346+
tests := map[string]struct {
347+
Input string
348+
File File
349+
350+
Fragments []*Fragment
351+
Err bool
352+
}{
353+
"multipleChanges": {
354+
Input: `@@ -1,3 +1,2 @@
355+
context line
356+
-old line 1
357+
context line
358+
@@ -8,3 +7,3 @@
359+
context line
360+
-old line 2
361+
+new line 1
362+
context line
363+
@@ -15,3 +14,4 @@
364+
context line
365+
-old line 3
366+
+new line 2
367+
+new line 3
368+
context line
369+
`,
370+
Fragments: []*Fragment{
371+
{
372+
OldPosition: 1,
373+
OldLines: 3,
374+
NewPosition: 1,
375+
NewLines: 2,
376+
Lines: []FragmentLine{
377+
{OpContext, "context line\n"},
378+
{OpDelete, "old line 1\n"},
379+
{OpContext, "context line\n"},
380+
},
381+
LinesDeleted: 1,
382+
LeadingContext: 1,
383+
TrailingContext: 1,
384+
},
385+
{
386+
OldPosition: 8,
387+
OldLines: 3,
388+
NewPosition: 7,
389+
NewLines: 3,
390+
Lines: []FragmentLine{
391+
{OpContext, "context line\n"},
392+
{OpDelete, "old line 2\n"},
393+
{OpAdd, "new line 1\n"},
394+
{OpContext, "context line\n"},
395+
},
396+
LinesDeleted: 1,
397+
LinesAdded: 1,
398+
LeadingContext: 1,
399+
TrailingContext: 1,
400+
},
401+
{
402+
OldPosition: 15,
403+
OldLines: 3,
404+
NewPosition: 14,
405+
NewLines: 4,
406+
Lines: []FragmentLine{
407+
{OpContext, "context line\n"},
408+
{OpDelete, "old line 3\n"},
409+
{OpAdd, "new line 2\n"},
410+
{OpAdd, "new line 3\n"},
411+
{OpContext, "context line\n"},
412+
},
413+
LinesDeleted: 1,
414+
LinesAdded: 2,
415+
LeadingContext: 1,
416+
TrailingContext: 1,
417+
},
418+
},
419+
},
420+
"badNewFile": {
421+
Input: `@@ -1 +1,2 @@
422+
-old line 1
423+
+new line 1
424+
+new line 2
425+
`,
426+
File: File{
427+
IsNew: true,
428+
},
429+
Err: true,
430+
},
431+
"badDeletedFile": {
432+
Input: `@@ -1,2 +1 @@
433+
-old line 1
434+
context line
435+
`,
436+
File: File{
437+
IsDelete: true,
438+
},
439+
Err: true,
440+
},
441+
}
442+
443+
for name, test := range tests {
444+
t.Run(name, func(t *testing.T) {
445+
p := newTestParser(test.Input, true)
446+
447+
file := test.File
448+
n, err := p.ParseTextFragments(&file)
449+
if test.Err {
450+
if err == nil || err == io.EOF {
451+
t.Fatalf("expected error parsing text fragments, but got %v", err)
452+
}
453+
return
454+
}
455+
if err != nil {
456+
t.Fatalf("error parsing text fragments: %v", err)
457+
}
458+
459+
if len(test.Fragments) != n {
460+
t.Fatalf("incorrect number of added fragments: expected %d, actual %d", len(test.Fragments), n)
461+
}
462+
463+
for i, frag := range test.Fragments {
464+
if !reflect.DeepEqual(frag, file.Fragments[i]) {
465+
t.Errorf("incorrect fragment at position %d\nexpected: %+v\nactual: %+v", i, frag, file.Fragments[i])
466+
}
467+
}
468+
})
469+
}
470+
}

0 commit comments

Comments
 (0)