fork of go-gitdiff with jj support
at v0.5.1 7.9 kB view raw
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}