fork of go-gitdiff with jj support
at v0.3.0 7.5 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": {Files: getApplyFiles("text_file_modify")}, 189 "binaryModify": {Files: getApplyFiles("bin_file_modify")}, 190 "modeChange": {Files: getApplyFiles("file_mode_change")}, 191 } 192 193 for name, test := range tests { 194 t.Run(name, func(t *testing.T) { 195 test.run(t, func(w io.Writer, applier *Applier, file *File) error { 196 return applier.ApplyFile(w, file) 197 }) 198 }) 199 } 200} 201 202type applyTest struct { 203 Files applyFiles 204 Err interface{} 205} 206 207func (at applyTest) run(t *testing.T, apply func(io.Writer, *Applier, *File) error) { 208 src, patch, out := at.Files.Load(t) 209 210 files, _, err := Parse(bytes.NewReader(patch)) 211 if err != nil { 212 t.Fatalf("failed to parse patch file: %v", err) 213 } 214 if len(files) != 1 { 215 t.Fatalf("patch should contain exactly one file, but it has %d", len(files)) 216 } 217 218 applier := NewApplier(bytes.NewReader(src)) 219 220 var dst bytes.Buffer 221 err = apply(&dst, applier, files[0]) 222 if at.Err != nil { 223 assertError(t, at.Err, err, "applying fragment") 224 return 225 } 226 if err != nil { 227 var aerr *ApplyError 228 if errors.As(err, &aerr) { 229 t.Fatalf("unexpected error applying: at %d: fragment %d at %d: %v", aerr.Line, aerr.Fragment, aerr.FragmentLine, err) 230 } else { 231 t.Fatalf("unexpected error applying: %v", err) 232 } 233 } 234 235 if !bytes.Equal(out, dst.Bytes()) { 236 t.Errorf("incorrect result after apply\nexpected:\n%x\nactual:\n%x", out, dst.Bytes()) 237 } 238} 239 240type applyFiles struct { 241 Src string 242 Patch string 243 Out string 244} 245 246func getApplyFiles(name string) applyFiles { 247 return applyFiles{ 248 Src: name + ".src", 249 Patch: name + ".patch", 250 Out: name + ".out", 251 } 252} 253 254func (f applyFiles) Load(t *testing.T) (src []byte, patch []byte, out []byte) { 255 load := func(name, kind string) []byte { 256 d, err := ioutil.ReadFile(filepath.Join("testdata", "apply", name)) 257 if err != nil { 258 t.Fatalf("failed to read %s file: %v", kind, err) 259 } 260 return d 261 } 262 263 if f.Src != "" { 264 src = load(f.Src, "source") 265 } 266 if f.Patch != "" { 267 patch = load(f.Patch, "patch") 268 } 269 if f.Out != "" { 270 out = load(f.Out, "output") 271 } 272 return 273}