From a34334d6ef2a4cb27da1d6e5fd6b1e642189f368 Mon Sep 17 00:00:00 2001 From: Billy Keyes Date: Thu, 16 Jan 2020 22:34:19 -0800 Subject: [PATCH] Add error tests for BinaryFragment#Apply These cover some of the possible errors with delta fragments. Many of the same tests could be implemented slightly easier by testing the helper functions, but the infrastructure is already set up to test the full function. This is made easier by the bin.go CLI, which can parse and encode binary patch data. Once parsed, fragments were manipulated with truncate and dd, encoded, and placed in the patch files. --- gitdiff/apply_test.go | 65 +++++++-- gitdiff/testdata/apply/bin.go | 124 ++++++++++++++++++ .../apply/bin_fragment_delta_error.src | Bin 0 -> 130 bytes .../bin_fragment_delta_error_dst_size.patch | 5 + ..._fragment_delta_error_incomplete_add.patch | 5 + ...fragment_delta_error_incomplete_copy.patch | 5 + .../bin_fragment_delta_error_src_size.patch | 5 + 7 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 gitdiff/testdata/apply/bin.go create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error.src create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error_dst_size.patch create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error_incomplete_add.patch create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error_incomplete_copy.patch create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error_src_size.patch 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 0000000000000000000000000000000000000000..d4edf8940855e776201983204f73fb7181d27378 GIT binary patch literal 130 zcmV-|0Db=e0Kasbzf#R}5_(inG$w;{I~tYWSfTR0kGl6KD$?Y(DE>wu6287==3&~0 z^TVYGw3oM&Avlk!l$A;IjwOO?dC%fDscG){+?d$Pmb|mfOV9HfiR%i5lRA_#?H3>k ks<~uF=|JP`B@u%EcpTE3Q2GCS)}?U&