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 TestApplyTextFragment(t *testing.T) {
13 tests := map[string]applyTest{
14 "createFile": {Files: getApplyFiles("text_fragment_new")},
15 "deleteFile": {Files: getApplyFiles("text_fragment_delete_all")},
16
17 "addStart": {Files: getApplyFiles("text_fragment_add_start")},
18 "addMiddle": {Files: getApplyFiles("text_fragment_add_middle")},
19 "addEnd": {Files: getApplyFiles("text_fragment_add_end")},
20 "addEndNoEOL": {Files: getApplyFiles("text_fragment_add_end_noeol")},
21
22 "changeStart": {Files: getApplyFiles("text_fragment_change_start")},
23 "changeMiddle": {Files: getApplyFiles("text_fragment_change_middle")},
24 "changeEnd": {Files: getApplyFiles("text_fragment_change_end")},
25 "changeEndEOL": {Files: getApplyFiles("text_fragment_change_end_eol")},
26 "changeExact": {Files: getApplyFiles("text_fragment_change_exact")},
27 "changeSingleNoEOL": {Files: getApplyFiles("text_fragment_change_single_noeol")},
28
29 "errorShortSrcBefore": {
30 Files: applyFiles{
31 Src: "text_fragment_error.src",
32 Patch: "text_fragment_error_short_src_before.patch",
33 },
34 Err: io.ErrUnexpectedEOF,
35 },
36 "errorShortSrc": {
37 Files: applyFiles{
38 Src: "text_fragment_error.src",
39 Patch: "text_fragment_error_short_src.patch",
40 },
41 Err: io.ErrUnexpectedEOF,
42 },
43 "errorContextConflict": {
44 Files: applyFiles{
45 Src: "text_fragment_error.src",
46 Patch: "text_fragment_error_context_conflict.patch",
47 },
48 Err: &Conflict{},
49 },
50 "errorDeleteConflict": {
51 Files: applyFiles{
52 Src: "text_fragment_error.src",
53 Patch: "text_fragment_error_delete_conflict.patch",
54 },
55 Err: &Conflict{},
56 },
57 "errorNewFile": {
58 Files: applyFiles{
59 Src: "text_fragment_error.src",
60 Patch: "text_fragment_error_new_file.patch",
61 },
62 Err: &Conflict{},
63 },
64 }
65
66 for name, test := range tests {
67 t.Run(name, func(t *testing.T) {
68 test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
69 if len(file.TextFragments) != 1 {
70 t.Fatalf("patch should contain exactly one fragment, but it has %d", len(file.TextFragments))
71 }
72 applier := NewTextApplier(dst, src)
73 return applier.ApplyFragment(file.TextFragments[0])
74 })
75 })
76 }
77}
78
79func TestApplyBinaryFragment(t *testing.T) {
80 tests := map[string]applyTest{
81 "literalCreate": {Files: getApplyFiles("bin_fragment_literal_create")},
82 "literalModify": {Files: getApplyFiles("bin_fragment_literal_modify")},
83 "deltaModify": {Files: getApplyFiles("bin_fragment_delta_modify")},
84 "deltaModifyLarge": {Files: getApplyFiles("bin_fragment_delta_modify_large")},
85
86 "errorIncompleteAdd": {
87 Files: applyFiles{
88 Src: "bin_fragment_delta_error.src",
89 Patch: "bin_fragment_delta_error_incomplete_add.patch",
90 },
91 Err: "incomplete add",
92 },
93 "errorIncompleteCopy": {
94 Files: applyFiles{
95 Src: "bin_fragment_delta_error.src",
96 Patch: "bin_fragment_delta_error_incomplete_copy.patch",
97 },
98 Err: "incomplete copy",
99 },
100 "errorSrcSize": {
101 Files: applyFiles{
102 Src: "bin_fragment_delta_error.src",
103 Patch: "bin_fragment_delta_error_src_size.patch",
104 },
105 Err: &Conflict{},
106 },
107 "errorDstSize": {
108 Files: applyFiles{
109 Src: "bin_fragment_delta_error.src",
110 Patch: "bin_fragment_delta_error_dst_size.patch",
111 },
112 Err: "insufficient or extra data",
113 },
114 }
115
116 for name, test := range tests {
117 t.Run(name, func(t *testing.T) {
118 test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
119 applier := NewBinaryApplier(dst, src)
120 return applier.ApplyFragment(file.BinaryFragment)
121 })
122 })
123 }
124}
125
126func TestApplyFile(t *testing.T) {
127 tests := map[string]applyTest{
128 "textModify": {
129 Files: applyFiles{
130 Src: "file_text.src",
131 Patch: "file_text_modify.patch",
132 Out: "file_text_modify.out",
133 },
134 },
135 "textDelete": {
136 Files: applyFiles{
137 Src: "file_text.src",
138 Patch: "file_text_delete.patch",
139 Out: "file_text_delete.out",
140 },
141 },
142 "textErrorPartialDelete": {
143 Files: applyFiles{
144 Src: "file_text.src",
145 Patch: "file_text_error_partial_delete.patch",
146 },
147 Err: &Conflict{},
148 },
149 "binaryModify": {
150 Files: getApplyFiles("file_bin_modify"),
151 },
152 "modeChange": {
153 Files: getApplyFiles("file_mode_change"),
154 },
155 }
156
157 for name, test := range tests {
158 t.Run(name, func(t *testing.T) {
159 test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
160 return Apply(dst, src, file)
161 })
162 })
163 }
164}
165
166type applyTest struct {
167 Files applyFiles
168 Err interface{}
169}
170
171func (at applyTest) run(t *testing.T, apply func(io.Writer, io.ReaderAt, *File) error) {
172 src, patch, out := at.Files.Load(t)
173
174 files, _, err := Parse(bytes.NewReader(patch))
175 if err != nil {
176 t.Fatalf("failed to parse patch file: %v", err)
177 }
178 if len(files) != 1 {
179 t.Fatalf("patch should contain exactly one file, but it has %d", len(files))
180 }
181
182 var dst bytes.Buffer
183 err = apply(&dst, bytes.NewReader(src), files[0])
184 if at.Err != nil {
185 assertError(t, at.Err, err, "applying fragment")
186 return
187 }
188 if err != nil {
189 var aerr *ApplyError
190 if errors.As(err, &aerr) {
191 t.Fatalf("unexpected error applying: at %d: fragment %d at %d: %v", aerr.Line, aerr.Fragment, aerr.FragmentLine, err)
192 } else {
193 t.Fatalf("unexpected error applying: %v", err)
194 }
195 }
196
197 if !bytes.Equal(out, dst.Bytes()) {
198 t.Errorf("incorrect result after apply\nexpected:\n%q\nactual:\n%q", out, dst.Bytes())
199 }
200}
201
202type applyFiles struct {
203 Src string
204 Patch string
205 Out string
206}
207
208func getApplyFiles(name string) applyFiles {
209 return applyFiles{
210 Src: name + ".src",
211 Patch: name + ".patch",
212 Out: name + ".out",
213 }
214}
215
216func (f applyFiles) Load(t *testing.T) (src []byte, patch []byte, out []byte) {
217 load := func(name, kind string) []byte {
218 d, err := ioutil.ReadFile(filepath.Join("testdata", "apply", name))
219 if err != nil {
220 t.Fatalf("failed to read %s file: %v", kind, err)
221 }
222 return d
223 }
224
225 if f.Src != "" {
226 src = load(f.Src, "source")
227 }
228 if f.Patch != "" {
229 patch = load(f.Patch, "patch")
230 }
231 if f.Out != "" {
232 out = load(f.Out, "output")
233 }
234 return
235}