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