Files
bluekeyes-go-gitdiff/gitdiff/apply_test.go
T
Billy Keyes 18058d1e15 Move apply methods to an Applier type
This removes the distinction between "strict" and "fuzzy" application
by allowing future methods on Applier that control settings. It also
avoids state tracking in the text fragment apply signature by moving it
into the Applier type.

While in practice, an Applier will be used once and discarded, the
capability is provided to reset it for multiple uses.
2020-01-25 17:01:43 -08:00

227 lines
5.9 KiB
Go

package gitdiff
import (
"bytes"
"errors"
"io"
"io/ioutil"
"path/filepath"
"strings"
"testing"
)
func TestTextFragmentApplyStrict(t *testing.T) {
tests := map[string]struct {
Files applyFiles
Err error
}{
"createFile": {Files: getApplyFiles("text_fragment_new")},
"deleteFile": {Files: getApplyFiles("text_fragment_delete_all")},
"addStart": {Files: getApplyFiles("text_fragment_add_start")},
"addMiddle": {Files: getApplyFiles("text_fragment_add_middle")},
"addEnd": {Files: getApplyFiles("text_fragment_add_end")},
"addEndNoEOL": {Files: getApplyFiles("text_fragment_add_end_noeol")},
"changeStart": {Files: getApplyFiles("text_fragment_change_start")},
"changeMiddle": {Files: getApplyFiles("text_fragment_change_middle")},
"changeEnd": {Files: getApplyFiles("text_fragment_change_end")},
"changeExact": {Files: getApplyFiles("text_fragment_change_exact")},
"changeSingleNoEOL": {Files: getApplyFiles("text_fragment_change_single_noeol")},
"errorShortSrcBefore": {
Files: applyFiles{
Src: "text_fragment_error.src",
Patch: "text_fragment_error_short_src_before.patch",
},
Err: io.ErrUnexpectedEOF,
},
"errorShortSrc": {
Files: applyFiles{
Src: "text_fragment_error.src",
Patch: "text_fragment_error_short_src.patch",
},
Err: io.ErrUnexpectedEOF,
},
"errorContextConflict": {
Files: applyFiles{
Src: "text_fragment_error.src",
Patch: "text_fragment_error_context_conflict.patch",
},
Err: &Conflict{},
},
"errorDeleteConflict": {
Files: applyFiles{
Src: "text_fragment_error.src",
Patch: "text_fragment_error_delete_conflict.patch",
},
Err: &Conflict{},
},
"errorNewFile": {
Files: applyFiles{
Src: "text_fragment_error.src",
Patch: "text_fragment_error_new_file.patch",
},
Err: &Conflict{},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
src, patch, out := test.Files.Load(t)
files, _, err := Parse(bytes.NewReader(patch))
if err != nil {
t.Fatalf("failed to parse patch file: %v", err)
}
if len(files) != 1 {
t.Fatalf("patch should contain exactly one file, but it has %d", len(files))
}
if len(files[0].TextFragments) != 1 {
t.Fatalf("patch should contain exactly one fragment, but it has %d", len(files[0].TextFragments))
}
applier := NewApplier(bytes.NewReader(src))
var dst bytes.Buffer
err = applier.ApplyTextFragment(&dst, files[0].TextFragments[0])
if test.Err != nil {
checkApplyError(t, test.Err, err)
return
}
if err != nil {
t.Fatalf("unexpected error applying fragment: %v", err)
}
if !bytes.Equal(out, dst.Bytes()) {
t.Errorf("incorrect result after apply\nexpected:\n%s\nactual:\n%s", out, dst.Bytes())
}
})
}
}
func TestBinaryFragmentApply(t *testing.T) {
tests := map[string]struct {
Files applyFiles
Err interface{}
}{
"literalCreate": {Files: getApplyFiles("bin_fragment_literal_create")},
"literalModify": {Files: getApplyFiles("bin_fragment_literal_modify")},
"deltaModify": {Files: getApplyFiles("bin_fragment_delta_modify")},
"deltaModifyLarge": {Files: getApplyFiles("bin_fragment_delta_modify_large")},
"errorIncompleteAdd": {
Files: applyFiles{
Src: "bin_fragment_delta_error.src",
Patch: "bin_fragment_delta_error_incomplete_add.patch",
},
Err: "incomplete add",
},
"errorIncompleteCopy": {
Files: applyFiles{
Src: "bin_fragment_delta_error.src",
Patch: "bin_fragment_delta_error_incomplete_copy.patch",
},
Err: "incomplete copy",
},
"errorSrcSize": {
Files: applyFiles{
Src: "bin_fragment_delta_error.src",
Patch: "bin_fragment_delta_error_src_size.patch",
},
Err: &Conflict{},
},
"errorDstSize": {
Files: applyFiles{
Src: "bin_fragment_delta_error.src",
Patch: "bin_fragment_delta_error_dst_size.patch",
},
Err: "insufficient or extra data",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
src, patch, out := test.Files.Load(t)
files, _, err := Parse(bytes.NewReader(patch))
if err != nil {
t.Fatalf("failed to parse patch file: %v", err)
}
if len(files) != 1 {
t.Fatalf("patch should contain exactly one file, but it has %d", len(files))
}
applier := NewApplier(bytes.NewReader(src))
var dst bytes.Buffer
err = applier.ApplyBinaryFragment(&dst, files[0].BinaryFragment)
if test.Err != nil {
checkApplyError(t, test.Err, err)
return
}
if err != nil {
t.Fatalf("unexpected error applying fragment: %v", err)
}
if !bytes.Equal(out, dst.Bytes()) {
t.Errorf("incorrect result after apply\nexpected:\n%x\nactual:\n%x", out, dst.Bytes())
}
})
}
}
func checkApplyError(t *testing.T, terr interface{}, err error) {
if err == nil {
t.Fatalf("expected error applying fragment, but got nil")
}
switch terr := terr.(type) {
case string:
if !strings.Contains(err.Error(), terr) {
t.Fatalf("incorrect apply error: %q does not contain %q", err.Error(), terr)
}
case error:
if !errors.Is(err, terr) {
t.Fatalf("incorrect apply error: expected: %T (%v), actual: %T (%v)", terr, terr, err, err)
}
default:
t.Fatalf("unsupported error type: %T", terr)
}
}
type applyFiles struct {
Src string
Patch string
Out string
}
func getApplyFiles(name string) applyFiles {
return applyFiles{
Src: name + ".src",
Patch: name + ".patch",
Out: name + ".out",
}
}
func (f applyFiles) Load(t *testing.T) (src []byte, patch []byte, out []byte) {
load := func(name, kind string) []byte {
d, err := ioutil.ReadFile(filepath.Join("testdata", "apply", name))
if err != nil {
t.Fatalf("failed to read %s file: %v", kind, err)
}
return d
}
if f.Src != "" {
src = load(f.Src, "source")
}
if f.Patch != "" {
patch = load(f.Patch, "patch")
}
if f.Out != "" {
out = load(f.Out, "output")
}
return
}