master
  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: &Conflict{},
 35		},
 36		"errorShortSrc": {
 37			Files: applyFiles{
 38				Src:   "text_fragment_error.src",
 39				Patch: "text_fragment_error_short_src.patch",
 40			},
 41			Err: &Conflict{},
 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}