fork of go-gitdiff with jj support
1package gitdiff
2
3import (
4 "bytes"
5 "errors"
6 "io"
7 "io/ioutil"
8 "path/filepath"
9 "strings"
10 "testing"
11)
12
13func TestApplierInvariants(t *testing.T) {
14 binary := &BinaryFragment{
15 Method: BinaryPatchLiteral,
16 Size: 2,
17 Data: []byte("\xbe\xef"),
18 }
19
20 text := &TextFragment{
21 NewPosition: 1,
22 NewLines: 1,
23 LinesAdded: 1,
24 Lines: []Line{
25 {Op: OpAdd, Line: "new line\n"},
26 },
27 }
28
29 file := &File{
30 TextFragments: []*TextFragment{text},
31 }
32
33 src := bytes.NewReader(nil)
34 dst := ioutil.Discard
35
36 assertInProgress := func(t *testing.T, kind string, err error) {
37 if !errors.Is(err, errApplyInProgress) {
38 t.Fatalf("expected in-progress error for %s apply, but got: %v", kind, err)
39 }
40 }
41
42 t.Run("binaryFirst", func(t *testing.T) {
43 a := NewApplier(src)
44 if err := a.ApplyBinaryFragment(dst, binary); err != nil {
45 t.Fatalf("unexpected error applying fragment: %v", err)
46 }
47 assertInProgress(t, "text", a.ApplyTextFragment(dst, text))
48 assertInProgress(t, "binary", a.ApplyBinaryFragment(dst, binary))
49 assertInProgress(t, "file", a.ApplyFile(dst, file))
50 })
51
52 t.Run("textFirst", func(t *testing.T) {
53 a := NewApplier(src)
54 if err := a.ApplyTextFragment(dst, text); err != nil {
55 t.Fatalf("unexpected error applying fragment: %v", err)
56 }
57 // additional text fragments are allowed
58 if err := a.ApplyTextFragment(dst, text); err != nil {
59 t.Fatalf("unexpected error applying second fragment: %v", err)
60 }
61 assertInProgress(t, "binary", a.ApplyBinaryFragment(dst, binary))
62 assertInProgress(t, "file", a.ApplyFile(dst, file))
63 })
64
65 t.Run("fileFirst", func(t *testing.T) {
66 a := NewApplier(src)
67 if err := a.ApplyFile(dst, file); err != nil {
68 t.Fatalf("unexpected error applying file: %v", err)
69 }
70 assertInProgress(t, "text", a.ApplyTextFragment(dst, text))
71 assertInProgress(t, "binary", a.ApplyBinaryFragment(dst, binary))
72 assertInProgress(t, "file", a.ApplyFile(dst, file))
73 })
74}
75
76func TestApplyTextFragment(t *testing.T) {
77 tests := map[string]applyTest{
78 "createFile": {Files: getApplyFiles("text_fragment_new")},
79 "deleteFile": {Files: getApplyFiles("text_fragment_delete_all")},
80
81 "addStart": {Files: getApplyFiles("text_fragment_add_start")},
82 "addMiddle": {Files: getApplyFiles("text_fragment_add_middle")},
83 "addEnd": {Files: getApplyFiles("text_fragment_add_end")},
84 "addEndNoEOL": {Files: getApplyFiles("text_fragment_add_end_noeol")},
85
86 "changeStart": {Files: getApplyFiles("text_fragment_change_start")},
87 "changeMiddle": {Files: getApplyFiles("text_fragment_change_middle")},
88 "changeEnd": {Files: getApplyFiles("text_fragment_change_end")},
89 "changeExact": {Files: getApplyFiles("text_fragment_change_exact")},
90 "changeSingleNoEOL": {Files: getApplyFiles("text_fragment_change_single_noeol")},
91
92 "errorShortSrcBefore": {
93 Files: applyFiles{
94 Src: "text_fragment_error.src",
95 Patch: "text_fragment_error_short_src_before.patch",
96 },
97 Err: io.ErrUnexpectedEOF,
98 },
99 "errorShortSrc": {
100 Files: applyFiles{
101 Src: "text_fragment_error.src",
102 Patch: "text_fragment_error_short_src.patch",
103 },
104 Err: io.ErrUnexpectedEOF,
105 },
106 "errorContextConflict": {
107 Files: applyFiles{
108 Src: "text_fragment_error.src",
109 Patch: "text_fragment_error_context_conflict.patch",
110 },
111 Err: &Conflict{},
112 },
113 "errorDeleteConflict": {
114 Files: applyFiles{
115 Src: "text_fragment_error.src",
116 Patch: "text_fragment_error_delete_conflict.patch",
117 },
118 Err: &Conflict{},
119 },
120 "errorNewFile": {
121 Files: applyFiles{
122 Src: "text_fragment_error.src",
123 Patch: "text_fragment_error_new_file.patch",
124 },
125 Err: &Conflict{},
126 },
127 }
128
129 for name, test := range tests {
130 t.Run(name, func(t *testing.T) {
131 test.run(t, func(w io.Writer, applier *Applier, file *File) error {
132 if len(file.TextFragments) != 1 {
133 t.Fatalf("patch should contain exactly one fragment, but it has %d", len(file.TextFragments))
134 }
135 return applier.ApplyTextFragment(w, file.TextFragments[0])
136 })
137 })
138 }
139}
140
141func TestApplyBinaryFragment(t *testing.T) {
142 tests := map[string]applyTest{
143 "literalCreate": {Files: getApplyFiles("bin_fragment_literal_create")},
144 "literalModify": {Files: getApplyFiles("bin_fragment_literal_modify")},
145 "deltaModify": {Files: getApplyFiles("bin_fragment_delta_modify")},
146 "deltaModifyLarge": {Files: getApplyFiles("bin_fragment_delta_modify_large")},
147
148 "errorIncompleteAdd": {
149 Files: applyFiles{
150 Src: "bin_fragment_delta_error.src",
151 Patch: "bin_fragment_delta_error_incomplete_add.patch",
152 },
153 Err: "incomplete add",
154 },
155 "errorIncompleteCopy": {
156 Files: applyFiles{
157 Src: "bin_fragment_delta_error.src",
158 Patch: "bin_fragment_delta_error_incomplete_copy.patch",
159 },
160 Err: "incomplete copy",
161 },
162 "errorSrcSize": {
163 Files: applyFiles{
164 Src: "bin_fragment_delta_error.src",
165 Patch: "bin_fragment_delta_error_src_size.patch",
166 },
167 Err: &Conflict{},
168 },
169 "errorDstSize": {
170 Files: applyFiles{
171 Src: "bin_fragment_delta_error.src",
172 Patch: "bin_fragment_delta_error_dst_size.patch",
173 },
174 Err: "insufficient or extra data",
175 },
176 }
177
178 for name, test := range tests {
179 t.Run(name, func(t *testing.T) {
180 test.run(t, func(w io.Writer, applier *Applier, file *File) error {
181 return applier.ApplyBinaryFragment(w, file.BinaryFragment)
182 })
183 })
184 }
185}
186
187func TestApplyFile(t *testing.T) {
188 tests := map[string]applyTest{
189 "textModify": {Files: getApplyFiles("text_file_modify")},
190 "binaryModify": {Files: getApplyFiles("bin_file_modify")},
191 "modeChange": {Files: getApplyFiles("file_mode_change")},
192 }
193
194 for name, test := range tests {
195 t.Run(name, func(t *testing.T) {
196 test.run(t, func(w io.Writer, applier *Applier, file *File) error {
197 return applier.ApplyFile(w, file)
198 })
199 })
200 }
201}
202
203type applyTest struct {
204 Files applyFiles
205 Err interface{}
206}
207
208func (at applyTest) run(t *testing.T, apply func(io.Writer, *Applier, *File) error) {
209 src, patch, out := at.Files.Load(t)
210
211 files, _, err := Parse(bytes.NewReader(patch))
212 if err != nil {
213 t.Fatalf("failed to parse patch file: %v", err)
214 }
215 if len(files) != 1 {
216 t.Fatalf("patch should contain exactly one file, but it has %d", len(files))
217 }
218
219 applier := NewApplier(bytes.NewReader(src))
220
221 var dst bytes.Buffer
222 err = apply(&dst, applier, files[0])
223 if at.Err != nil {
224 at.assertError(t, err)
225 return
226 }
227 if err != nil {
228 var aerr *ApplyError
229 if errors.As(err, &aerr) {
230 t.Fatalf("unexpected error applying: at %d: fragment %d at %d: %v", aerr.Line, aerr.Fragment, aerr.FragmentLine, err)
231 } else {
232 t.Fatalf("unexpected error applying: %v", err)
233 }
234 }
235
236 if !bytes.Equal(out, dst.Bytes()) {
237 t.Errorf("incorrect result after apply\nexpected:\n%x\nactual:\n%x", out, dst.Bytes())
238 }
239}
240
241func (at applyTest) assertError(t *testing.T, err error) {
242 if err == nil {
243 t.Fatalf("expected error applying fragment, but got nil")
244 }
245
246 switch terr := at.Err.(type) {
247 case string:
248 if !strings.Contains(err.Error(), terr) {
249 t.Fatalf("incorrect apply error: %q does not contain %q", err.Error(), terr)
250 }
251 case error:
252 if !errors.Is(err, terr) {
253 t.Fatalf("incorrect apply error: expected: %T (%v), actual: %T (%v)", terr, terr, err, err)
254 }
255 default:
256 t.Fatalf("unsupported expected error type: %T", terr)
257 }
258}
259
260type applyFiles struct {
261 Src string
262 Patch string
263 Out string
264}
265
266func getApplyFiles(name string) applyFiles {
267 return applyFiles{
268 Src: name + ".src",
269 Patch: name + ".patch",
270 Out: name + ".out",
271 }
272}
273
274func (f applyFiles) Load(t *testing.T) (src []byte, patch []byte, out []byte) {
275 load := func(name, kind string) []byte {
276 d, err := ioutil.ReadFile(filepath.Join("testdata", "apply", name))
277 if err != nil {
278 t.Fatalf("failed to read %s file: %v", kind, err)
279 }
280 return d
281 }
282
283 if f.Src != "" {
284 src = load(f.Src, "source")
285 }
286 if f.Patch != "" {
287 patch = load(f.Patch, "patch")
288 }
289 if f.Out != "" {
290 out = load(f.Out, "output")
291 }
292 return
293}