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