summaryrefslogtreecommitdiff
path: root/pkg/gitdiff/apply_test.go
diff options
context:
space:
mode:
authorAnton Medvedev <anton@medv.io>2025-11-30 12:46:34 +0100
committerAnton Medvedev <anton@medv.io>2025-11-30 12:46:34 +0100
commitf6b0f38af648d028422a7494378b5dabdc90573f (patch)
tree3c26cfc269c021300a2d1e4e02623dd440c20226 /pkg/gitdiff/apply_test.go
First commit
Diffstat (limited to 'pkg/gitdiff/apply_test.go')
-rw-r--r--pkg/gitdiff/apply_test.go235
1 files changed, 235 insertions, 0 deletions
diff --git a/pkg/gitdiff/apply_test.go b/pkg/gitdiff/apply_test.go
new file mode 100644
index 0000000..05915ba
--- /dev/null
+++ b/pkg/gitdiff/apply_test.go
@@ -0,0 +1,235 @@
+package gitdiff
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "io/ioutil"
+ "path/filepath"
+ "testing"
+)
+
+func TestApplyTextFragment(t *testing.T) {
+ tests := map[string]applyTest{
+ "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")},
+ "changeEndEOL": {Files: getApplyFiles("text_fragment_change_end_eol")},
+ "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: &Conflict{},
+ },
+ "errorShortSrc": {
+ Files: applyFiles{
+ Src: "text_fragment_error.src",
+ Patch: "text_fragment_error_short_src.patch",
+ },
+ Err: &Conflict{},
+ },
+ "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) {
+ test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
+ if len(file.TextFragments) != 1 {
+ t.Fatalf("patch should contain exactly one fragment, but it has %d", len(file.TextFragments))
+ }
+ applier := NewTextApplier(dst, src)
+ return applier.ApplyFragment(file.TextFragments[0])
+ })
+ })
+ }
+}
+
+func TestApplyBinaryFragment(t *testing.T) {
+ tests := map[string]applyTest{
+ "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) {
+ test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
+ applier := NewBinaryApplier(dst, src)
+ return applier.ApplyFragment(file.BinaryFragment)
+ })
+ })
+ }
+}
+
+func TestApplyFile(t *testing.T) {
+ tests := map[string]applyTest{
+ "textModify": {
+ Files: applyFiles{
+ Src: "file_text.src",
+ Patch: "file_text_modify.patch",
+ Out: "file_text_modify.out",
+ },
+ },
+ "textDelete": {
+ Files: applyFiles{
+ Src: "file_text.src",
+ Patch: "file_text_delete.patch",
+ Out: "file_text_delete.out",
+ },
+ },
+ "textErrorPartialDelete": {
+ Files: applyFiles{
+ Src: "file_text.src",
+ Patch: "file_text_error_partial_delete.patch",
+ },
+ Err: &Conflict{},
+ },
+ "binaryModify": {
+ Files: getApplyFiles("file_bin_modify"),
+ },
+ "modeChange": {
+ Files: getApplyFiles("file_mode_change"),
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
+ return Apply(dst, src, file)
+ })
+ })
+ }
+}
+
+type applyTest struct {
+ Files applyFiles
+ Err interface{}
+}
+
+func (at applyTest) run(t *testing.T, apply func(io.Writer, io.ReaderAt, *File) error) {
+ src, patch, out := at.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))
+ }
+
+ var dst bytes.Buffer
+ err = apply(&dst, bytes.NewReader(src), files[0])
+ if at.Err != nil {
+ assertError(t, at.Err, err, "applying fragment")
+ return
+ }
+ if err != nil {
+ var aerr *ApplyError
+ if errors.As(err, &aerr) {
+ t.Fatalf("unexpected error applying: at %d: fragment %d at %d: %v", aerr.Line, aerr.Fragment, aerr.FragmentLine, err)
+ } else {
+ t.Fatalf("unexpected error applying: %v", err)
+ }
+ }
+
+ if !bytes.Equal(out, dst.Bytes()) {
+ t.Errorf("incorrect result after apply\nexpected:\n%q\nactual:\n%q", out, dst.Bytes())
+ }
+}
+
+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
+}