diff --git a/gitdiff/apply_test.go b/gitdiff/apply_test.go index a78a378..026f0b2 100644 --- a/gitdiff/apply_test.go +++ b/gitdiff/apply_test.go @@ -6,6 +6,7 @@ import ( "io" "io/ioutil" "path/filepath" + "strings" "testing" ) @@ -85,12 +86,7 @@ func TestTextFragmentApplyStrict(t *testing.T) { var dst bytes.Buffer err = frag.ApplyStrict(&dst, NewLineReader(bytes.NewReader(src), 0)) if test.Err != nil { - if err == nil { - t.Fatalf("expected error applying fragment, but got nil") - } - if !errors.Is(err, test.Err) { - t.Fatalf("incorrect apply error: expected: %T (%v), actual: %T (%v)", test.Err, test.Err, err, err) - } + checkApplyError(t, test.Err, err) return } if err != nil { @@ -107,12 +103,41 @@ func TestTextFragmentApplyStrict(t *testing.T) { func TestBinaryFragmentApply(t *testing.T) { tests := map[string]struct { Files applyFiles - Err error + 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 { @@ -135,12 +160,7 @@ func TestBinaryFragmentApply(t *testing.T) { var dst bytes.Buffer err = frag.Apply(&dst, bytes.NewReader(src)) if test.Err != nil { - if err == nil { - t.Fatalf("expected error applying fragment, but got nil") - } - if !errors.Is(err, test.Err) { - t.Fatalf("incorrect apply error: expected: %T (%v), actual: %T (%v)", test.Err, test.Err, err, err) - } + checkApplyError(t, test.Err, err) return } if err != nil { @@ -154,6 +174,25 @@ func TestBinaryFragmentApply(t *testing.T) { } } +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 diff --git a/gitdiff/testdata/apply/bin.go b/gitdiff/testdata/apply/bin.go new file mode 100644 index 0000000..6d39ffd --- /dev/null +++ b/gitdiff/testdata/apply/bin.go @@ -0,0 +1,124 @@ +//+build ignore + +// bin.go is a helper CLI to manipulate binary diff data for testing purposes. +// It can decode patches generated by git using the standard parsing functions +// or it can encode binary data back into the format expected by Git. It +// operates on stdin writes results (possibly binary) to stdout. + +package main + +import ( + "bytes" + "compress/zlib" + "encoding/binary" + "flag" + "io/ioutil" + "log" + "os" + "strings" + + "github.com/bluekeyes/go-gitdiff/gitdiff" +) + +var ( + b85Powers = []uint32{52200625, 614125, 7225, 85, 1} + b85Alpha = []byte( + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "!#$%&()*+-;<=>?@^_`{|}~", + ) +) + +var mode string + +func base85Encode(data []byte) []byte { + chunks, remaining := len(data)/4, len(data)%4 + if remaining > 0 { + data = append(data, make([]byte, 4-remaining)...) + chunks++ + } + + var n int + out := make([]byte, 5*chunks) + + for i := 0; i < len(data); i += 4 { + v := binary.BigEndian.Uint32(data[i : i+4]) + for j := 0; j < 5; j++ { + p := v / b85Powers[j] + out[n+j] = b85Alpha[p] + v -= b85Powers[j] * p + } + n += 5 + } + + return out +} + +func compress(data []byte) ([]byte, error) { + var b bytes.Buffer + w := zlib.NewWriter(&b) + + if _, err := w.Write(data); err != nil { + return nil, err + } + if err := w.Close(); err != nil { + return nil, err + } + + return b.Bytes(), nil +} + +func wrap(data []byte) string { + var s strings.Builder + for i := 0; i < len(data); i += 52 { + c := 52 + if c > len(data)-i { + c = len(data) - i + } + b := (c / 5) * 4 + + if b <= 26 { + s.WriteByte(byte('A' + b - 1)) + } else { + s.WriteByte(byte('a' + b - 27)) + } + s.Write(data[i : i+c]) + s.WriteByte('\n') + } + return s.String() +} + +func init() { + flag.StringVar(&mode, "mode", "parse", "operation mode, one of 'parse' or 'encode'") +} + +func main() { + flag.Parse() + + switch mode { + case "parse": + files, _, err := gitdiff.Parse(os.Stdin) + if err != nil { + log.Fatalf("failed to parse file: %v", err) + } + if len(files) != 1 { + log.Fatalf("patch contains more than one file: %d", len(files)) + } + if files[0].BinaryFragment == nil { + log.Fatalf("patch file does not contain a binary fragment") + } + os.Stdout.Write(files[0].BinaryFragment.Data) + + case "encode": + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatalf("failed to read input: %v", err) + } + data, err = compress(data) + if err != nil { + log.Fatalf("failed to compress data: %v", err) + } + os.Stdout.WriteString(wrap(base85Encode(data))) + + default: + log.Fatalf("unknown mode: %s", mode) + } +} diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src new file mode 100644 index 0000000..d4edf89 Binary files /dev/null and b/gitdiff/testdata/apply/bin_fragment_delta_error.src differ diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size.patch b/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size.patch new file mode 100644 index 0000000..6d5bb42 --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size.patch @@ -0,0 +1,5 @@ +diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src +GIT binary patch +delta 18 +fc${itY+{<=z`_4AtEhVK$zKyatN;N30RR6$D+j^= + diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_incomplete_add.patch b/gitdiff/testdata/apply/bin_fragment_delta_error_incomplete_add.patch new file mode 100644 index 0000000..b8c1835 --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_delta_error_incomplete_add.patch @@ -0,0 +1,5 @@ +diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src +GIT binary patch +delta 11 +Xc${itY+{_?z`_4As|XMP0RR6K8UwQc + diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_incomplete_copy.patch b/gitdiff/testdata/apply/bin_fragment_delta_error_incomplete_copy.patch new file mode 100644 index 0000000..8db8f84 --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_delta_error_incomplete_copy.patch @@ -0,0 +1,5 @@ +diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src +GIT binary patch +delta 17 +fc${itY+{_?z`_4AtEhVK$zKya00961|Nl5!2ZsOv + diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_src_size.patch b/gitdiff/testdata/apply/bin_fragment_delta_error_src_size.patch new file mode 100644 index 0000000..29cb26b --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_delta_error_src_size.patch @@ -0,0 +1,5 @@ +diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src +GIT binary patch +delta 18 +fc${itYGRz=z`_4AtEhVK$zKyatN;N30RR6$EeFB? +