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